//

//  MRGraphView.m

//  MyCustomGraphView

//

//  Created by Marco Rotatori on 02/02/10.

//  Copyright 2010 Marco Rotatori. All rights reserved.

//


#import "MRGraphView.h"



@implementation MRGraphView


/*====================================================================================*\

 Il metodo init... ritorna un oggetto inizializzato con tutte le variabili

 impostate ai loro valori di default

\*====================================================================================*/


- (id)initWithFrame:(NSRect)rect {

// chiamo il metodo di inizializzazione della superclasse

self = [super initWithFrame:rect];

// se il puntatore è valido imposto tutte le variabili

if(self) {

values = NULL;

valuesNumber = 0;

yStart = 0.0;

yIncrement = 10.0;

xStart = 0.0;

xIncrement = 1.0;

border = 20.0;

yBorder = 50.0;

xBorder = 30.0;

maxY = 150.0;

straight = NO;

showPoints = YES;

pointType = circle;

pointScale = 1.0;

pointStrokeColor = [[NSColor blueColor] retain];

pointFillColor = [[NSColor greenColor] retain];

pointStrokeWidth = 3.0;

defaultStrokeWidth = 2.0;

backgroundColor = [[NSColor whiteColor] retain];

graphStrokeColor = [[NSColor redColor] retain];

graphStrokeWidth = 2.0;

showVerticalStrokes = YES;

showHorizontalStrokes = YES;

showYValues = YES;

showXValues = YES;

yTitle = nil;

xTitle = nil;

fontAttr = [[NSMutableDictionary alloc] init];

float fontSize = 10.0;

NSFont *font = [NSFont fontWithName:@"Lucida Grande" size:fontSize];

NSColor *fontColor = [NSColor blackColor];

[fontAttr setObject:font forKey:NSFontAttributeName];

[fontAttr setObject:fontColor forKey:NSForegroundColorAttributeName];

}

// ritorno il puntatore all'oggetto appena creato

return self;

}




/*====================================================================================*\

 Il metodo dealloc va sovrascritto per rilasciare tutte le risorse ancora usate

\*====================================================================================*/


- (void)dealloc {

// libero la memoria allocata con malloc() per questi puntatori

// se il puntatore è NULL non succede niente

free(values);

// rilascio gli oggetti di cui ho fatto il retain

[pointStrokeColor release];

[pointFillColor release];

[backgroundColor release];

[graphStrokeColor release];

[yTitle release];

[xTitle release];

[fontAttr release];

// chiamo il metodo della superclasse affinché si occupi di fare quanto necessario

[super dealloc];

}




/*====================================================================================*\

 All'interno di questo metodo si svolgono le operazioni di disegno

\*====================================================================================*/


- (void)drawRect:(NSRect)rect {

// ottengo il Graphic Context che mi servirà in tutto il metodo di disegno

CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

// imposto lo spessore della traccia al valore standard

CGContextSetLineWidth(context, defaultStrokeWidth);

// COLORO LO SFONDO DELLA VIEW

// imposto come colore corrente quello di background

[backgroundColor set];

// riempo il rettangolo della view con il colore appena impostato

CGContextFillRect(context, CGRectFromNSRect([self frame]));

// CALCOLO IL RETTANGOLO CHE OCCUPERÀ IL GRAFICO

// --------------------

// creo un rettangolo "vuoto"

NSRect graphRect = NSZeroRect;

// imposto il punto 0 di x

graphRect.origin.x = border;

// se devo visualizzare i valori dell'asse Y...

if(showYValues) {

graphRect.origin.x += yBorder;

}

// imposto il punto 0 di y

graphRect.origin.y = border;

// se devo visualizzare i valori dell'asse X...

if(showXValues) {

graphRect.origin.y += xBorder;

}

// imposto larghezza e altezza

graphRect.size.width = [self frame].size.width - graphRect.origin.x - border;

graphRect.size.height = [self frame].size.height - graphRect.origin.y - border;

// CALCOLIAMO LA MISURA DEGLI STEP

// --------------------

float yStep = graphRect.size.height / (maxY - yStart);

// ==============

// DA CONTROLLARE

// PROBABILMENTE SERVE SOLO SE ESISTE L'ARRAY DELL'ASSE X

// ==============

float xStep = graphRect.size.width / (valuesNumber - 1);

// DISEGNAMO I TRATTINI

// --------------------

[[NSColor darkGrayColor] set];


// poi gli incrementi maggiori

float yInc = 0.0;

while(yInc <= ((maxY - yStart) * yStep) /* e per arrotondare... */ + 1.0) {

CGContextMoveToPoint(context, graphRect.origin.x, graphRect.origin.y + yInc);

CGContextAddLineToPoint(context, graphRect.origin.x - 5.0, graphRect.origin.y + yInc);

CGContextStrokePath(context);

NSString *nString = [NSString stringWithFormat:@"%g", (yInc / yStep) + yStart];

NSPoint point = NSMakePoint(graphRect.origin.x - [nString sizeWithAttributes:fontAttr].width - 10.0, graphRect.origin.y + yInc - 5.0);

[nString drawAtPoint:point withAttributes:fontAttr];

yInc += yIncrement * yStep;

}

// PER IL MOMENTO STAMPIAMO SOLAMENTE I PUNTI RELATIVI AD OGNI ELEMENTO

float xInc = 0.0;

while(xInc <= graphRect.size.width /* e per arrotondare... */ + 1.0) {

CGContextMoveToPoint(context, graphRect.origin.x + xInc, graphRect.origin.y);

CGContextAddLineToPoint(context, graphRect.origin.x + xInc, graphRect.origin.y - 5.0);

CGContextStrokePath(context);

NSString *nString = [NSString stringWithFormat:@"%g", ((xInc / xStep) * xIncrement) + xStart];

NSPoint point = NSMakePoint(graphRect.origin.x + xInc - [nString sizeWithAttributes:fontAttr].width / 2, graphRect.origin.y - 25.0);

[nString drawAtPoint:point withAttributes:fontAttr];

xInc += xStep;

}

// DISEGNAMO LE LINEE VERTICALI

// --------------------

if(showVerticalStrokes) {

int n;

for(n=0; n<valuesNumber; n++) {

CGContextMoveToPoint(context, graphRect.origin.x + xStep * n, graphRect.origin.y);

CGContextAddLineToPoint(context, graphRect.origin.x + xStep * n, graphRect.origin.y - yStart * yStep + values[n] * yStep);

[[NSColor lightGrayColor] set];

CGContextStrokePath(context);

}

}

// DISEGNAMO LE LINEE ORIZZONTALI

// --------------------

if(showHorizontalStrokes) {

int n;

for(n=0; n<valuesNumber; n++) {

// imposto lo spessore della traccia al valore 1.0

CGContextSetLineWidth(context, 1.0);


CGContextMoveToPoint(context, graphRect.origin.x, graphRect.origin.y - yStart * yStep + values[n] * yStep);

CGContextAddLineToPoint(context, graphRect.origin.x + graphRect.size.width, graphRect.origin.y - yStart * yStep + values[n] * yStep);

[[NSColor cyanColor] set];

CGContextStrokePath(context);

// reimposto lo spessore al valore standard

CGContextSetLineWidth(context, defaultStrokeWidth);

}

}

// DISEGNAMO I DUE ASSI

// --------------------

// imposto il colore

[[NSColor blackColor] set];

// creiamo un nuovo path

CGContextBeginPath(context);

// creiamo la linea dell'asse Y

// spostamo in punto del path dove vogliamo

CGContextMoveToPoint(context, graphRect.origin.x, graphRect.origin.y);

// aggiungiamo una linea dal punto di partenza fino al nuovo punto

CGContextAddLineToPoint(context, graphRect.origin.x, graphRect.origin.y + graphRect.size.height);

// creiamo la linea dell'asse X

CGContextMoveToPoint(context, graphRect.origin.x, graphRect.origin.y);

CGContextAddLineToPoint(context, graphRect.origin.x + graphRect.size.width, graphRect.origin.y);

// disegnamo la traccia dei path creati

CGContextStrokePath(context);

// DISEGNAMO IL GRAFICO

// --------------------

// Creo un nuovo path che rappresenterà l'andamenteo del grafico

int i;

for(i=0; i<valuesNumber; i++) {

if(i==0) { // per il primo valore imposto semplicemente il punto di origine del path

CGContextMoveToPoint(context, graphRect.origin.x + xStep * i, graphRect.origin.y - yStart * yStep + values[i] * yStep);

}

else // per gli altri valori invece aggiungo i nuovi punti al path

{

if(straight) { // con il codice sottostante disegnamo linee rette...

CGContextAddLineToPoint(context, graphRect.origin.x + xStep * i, graphRect.origin.y - yStart * yStep + values[i] * yStep);

}

else // ...mentre con questo riusciamo a fare qualcosa di più sinuoso

{

CGContextAddCurveToPoint(

context,

graphRect.origin.x + xStep * i - xStep / 2,

graphRect.origin.y - yStart * yStep + values[i-1] * yStep,

graphRect.origin.x + xStep * i - xStep / 2,

graphRect.origin.y - yStart * yStep + values[i] * yStep,

graphRect.origin.x + xStep * i,

graphRect.origin.y - yStart * yStep + values[i] * yStep

);

}

}

}

// coloro il path

// ma prima imposto lo spessore della traccia

CGContextSetLineWidth(context, pointStrokeWidth);

[graphStrokeColor set];

CGContextStrokePath(context);

// reimposto lo spessore al valore standard

CGContextSetLineWidth(context, defaultStrokeWidth);

// DISEGNAMO IN PUNTI DEI VALORI

// --------------------


if(showPoints) {

for(i=0; i<valuesNumber; i++) {

[self drawPointAtPoint:NSMakePoint(graphRect.origin.x + xStep * i, graphRect.origin.y - yStart * yStep + values[i] * yStep)];

}

}

}




/*====================================================================================*\

  Il metodo serve per ricevere un array di valori float

  dal quale copiare gli elementi nell'array interno

\*====================================================================================*/


- (void)setValues:(float *)vals length:(int)itemsNumber {

// alloco la memoria per il puntatore di tanti valori float quanti sono gli elementi dell'array

values = (float *)malloc(sizeof(float)*itemsNumber);

// faccio un ciclo per copiare tutti gli elementi dell'arrai passato del mio array

int i;

for(i=0; i<itemsNumber; i++) {

values[i] = vals[i];

}

// imposto il numero di valori dell'array

valuesNumber = itemsNumber;

}




/*====================================================================================*\

 Il metodo disegna un "punto", in base a tipo impostato correntemente

\*====================================================================================*/


- (void)drawPointAtPoint:(NSPoint)point {

// ottengo il Graphic Context

CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

// misura di metà rettangolo

float mis = 3.0 * pointScale;

// calcolo il rettangolo che occuperò il punto

NSRect pointRect = NSZeroRect;

pointRect.origin.x = point.x - mis;

pointRect.origin.y = point.y - mis;

pointRect.size.width = mis * 2.0;

pointRect.size.height = mis * 2.0;

switch(pointType) {

case dot:

[pointFillColor set];

CGContextFillEllipseInRect(context, CGRectFromNSRect(pointRect));

break;

case circle:

[pointFillColor set];

CGContextFillEllipseInRect(context, CGRectFromNSRect(pointRect));

[pointStrokeColor set];

CGContextStrokeEllipseInRect(context, CGRectFromNSRect(pointRect));

break;

case square:

[pointFillColor set];

CGContextFillRect(context, CGRectFromNSRect(pointRect));

[pointStrokeColor set];

CGContextStrokeRect(context, CGRectFromNSRect(pointRect));

break;

case rhombus:

[pointFillColor set];

fillRhombusInRect(context, CGRectFromNSRect(pointRect));

[pointStrokeColor set];

strokeRhombusInRect(context, CGRectFromNSRect(pointRect));

break;

default:

break;

}

}




/*====================================================================================*\

 Funzione per riempire un rombo disegnato all'interno di un CGRect

\*====================================================================================*/


void fillRhombusInRect(CGContextRef context, CGRect rect) {

CGContextBeginPath(context);

// spostamo in punto del path dove vogliamo

CGContextMoveToPoint(context, rect.origin.x + rect.size.width / 2, rect.origin.y + rect.size.height);

// aggiungiamo una linea dal punto di partenza fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height / 2);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x + rect.size.width / 2, rect.origin.y);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height / 2);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

//CGContextAddLineToPoint(context, point.x, point.y + mis);

CGContextClosePath(context);

//[pointFillColor set];

CGContextFillPath(context);

//[pointStrokeColor set];

//CGContextStrokePath(context);

}




/*====================================================================================*\

 Funzione per tracciare un rombo disegnato all'interno di un CGRect

\*====================================================================================*/


void strokeRhombusInRect(CGContextRef context, CGRect rect) {

CGContextBeginPath(context);

// spostamo in punto del path dove vogliamo

CGContextMoveToPoint(context, rect.origin.x + rect.size.width / 2, rect.origin.y + rect.size.height);

// aggiungiamo una linea dal punto di partenza fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height / 2);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x + rect.size.width / 2, rect.origin.y);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height / 2);

// aggiungiamo una linea dal ultimo punto fino al nuovo punto

//CGContextAddLineToPoint(context, point.x, point.y + mis);

CGContextClosePath(context);

//[pointFillColor set];

//CGContextFillPath(context);

//[pointStrokeColor set];

CGContextStrokePath(context);

}




/*====================================================================================*\

  Funzione per convertire un NSRect in CGRect

  (copiata e rovesciata dalla relativa opposta di Apple)

\*====================================================================================*/


CGRect CGRectFromNSRect(NSRect nsrect) {

return (*(CGRect *)&(nsrect));

}


@end