//
// 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