You might want to post a code snippet where that sort of code is failing.
Again, feel free to toss some specific code up and I'm sure someone can point you in the right direction.
-Lee
Sure, here is some of my 1000+ lines of spaghetti code (of version 6 or so). I am not sure I am happy with this version, too much cruft because of these floating point errors.
The idea is to have a NSView that can:
plot any number of arbitrary 2D xy-data,
plot any number of arbitrary 2D functions,
plot linear or logaritmic scales
scale the data (e.i. different units: Hz, kHz, mHz)
plot not only lines between two points, but also have a symbol (circle, square, x,+,...) at that point
Plot the legend with the correct label, symbol, line thickness, color
track the mouse position, and display it on screen
with as many user adjustable options as reasonable possible
zoom in
These are things that in some way or another are working.
Is not possible:
arbitrary positioning of title, labels, legend
editing title, labels, legend directly on the PlotView
The next steps is to include:
converting mouse clicks to marked points
A subclass could be
2D colorplots or barplots
I am not going to do 3D plots. Such plots are also usually pretty horrendous.
There will also be an inspector, while most responsibility of the PlotView is offloaded to a delegate and datasource in such a way that the user doesn't have to see the PlotView code. Like an NSTableView works.
Yes, I know a few other frameworks are out there, but these frameworks don't seem to be orientated towards scientific plots, and besides it's fun to do and I am learning a lot about software design.
When I am either happy or bored with it, maybe I put it in a repository.
Yes, I know there is also exell, numbers, origin, gnuplot, octave, (R, mathematica,) which I use for my real data.
Code:
-(void) drawYAxis {
if ([delegate showYAxis]){
//get/set (path) attributes
NSBezierPath *path = [NSBezierPath bezierPath];
[path setLineWidth:[delegate borderThickness]];
[[delegate borderColor] set];
float ticLength = [delegate axisLength];
//minimum font & rect size (ensure enough spacing between labels)
NSSize minimumsize = [self minimumLabelsize];
NSRect rect = NSMakeRect(0,
plotarea.origin.y-3*minimumsize.height,
0,
minimumsize.height);
if (yRange.type == 1){
//linear
//starting point
float minCoordStart = fmaxf(nearbyintf(yRange.min),ceilf(yRange.min));
//draw
float y;
float yCoord = minCoordStart;
while (roundf(100*yCoord) <= roundf(100*yRange.max)){
//convert
y = [self transformY:yCoord
to:ATRPlotcoordinates];
//path
[path moveToPoint:NSMakePoint(plotarea.origin.x,y)];
[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength/2,y)];
//text
NSAttributedString *str = [self allocLabel:yCoord
forAxis:yaxis];//DO NOT FORGET TO RELEASE str
if (roundf(100*y) > roundf(100*(rect.origin.y+rect.size.height+minimumsize.height))) {
//enough clearning in relation to previous string
//update rect
NSRect newRect;
newRect.origin.x = plotarea.origin.x-padding-[str size].width;
newRect.origin.y = y-[str size].height/2;
newRect.size.width = [str size].width;
newRect.size.height = [str size].height+minimumsize.height;
if ([self rectFallsWithinCanvasarea:newRect]) {
//rect falls within canvasarea, will be displayed completely
newRect.size.height -= minimumsize.height;
[str drawInRect:newRect];
rect = newRect;
[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength,y)]; //full tic length for labeled tics
}
}
[str release];
yCoord+=yRange.interval;
}
} else if (yRange.type == 2){
//log
//starting point
float minCoordStart = yRange.min;
//magnitude -> interval
float y;
int magnitude = [self orderOfMagnitude:minCoordStart];
float interval=powf(10,magnitude);
//startvalue
float yCoord = interval;
int i = 1;
while (yCoord < minCoordStart){
yCoord +=interval;
i++;
}
while (roundf(100*yCoord) <= roundf(100*yRange.max)){
//convert
y = [self transformY:yCoord
to:ATRPlotcoordinates];
//path
[path moveToPoint:NSMakePoint(plotarea.origin.x,y)];
if (i == 1)
[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength,y)];
else
[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength/2,y)];
//text
if (i == 1 | i == 5){
//text
NSAttributedString *str = [self allocLabel:yCoord
forAxis:yaxis];
if (roundf(100*y) > roundf(100*(rect.origin.y+rect.size.height+minimumsize.height))) {
//enough clearning in relation to previous string
//update rect
NSRect newRect;
newRect.origin.x = plotarea.origin.x-padding-[str size].width;
newRect.origin.y = y - [str size].height/2;
newRect.size.width = [str size].width;
newRect.size.height = [str size].height + minimumsize.height;
if ([self rectFallsWithinCanvasarea:newRect]) {
//rect falls within canvasarea
newRect.size.height -= minimumsize.height;
[str drawInRect:newRect];
rect = newRect;
}
}
[str release];
}
//adjust interval as appropiate
i++;
yCoord += interval;
if (i > 9) {
i=1;
interval = 10*interval;
}
}
}
[path stroke];
}
}
-(BOOL) rectFallsWithinCanvasarea:(NSRect) rect {
//is rect located in the total available area?
//top and right side fall within available area (origin + size)
if (roundf(100*(rect.origin.x+rect.size.width)) < roundf(100*canvasarea.origin.x))
return NO; //todo: is view origin not always (0,0)? -> just check for sign?
if (roundf(100*(rect.origin.y+rect.size.height)) < roundf(100*canvasarea.origin.y))
return NO;
if (roundf(100*(rect.origin.x+rect.size.width)) > roundf(100*(canvasarea.origin.x+canvasarea.size.width)))
return NO;
if (roundf(100*(rect.origin.y+rect.size.height)) > roundf(100*(canvasarea.origin.y+canvasarea.size.height)))
return NO;
//does origin fall within canvasarea
return [self pointFallsWithinCanvasarea:rect.origin];
}
-(int) orderOfMagnitude:(float)value {
int magnitude = 0;
if (value < 1) {
while (value < 1){
magnitude--;
value = value*10;
}
} else {
float integralPart;
modff(value,&integralPart);
div_t breuk = div(integralPart,10);
while (breuk.quot > 0){
magnitude++;
breuk = div(breuk.quot,10);
}
}
return magnitude;
}
-(void) prepareScalefactors {
//this should not take scaling into account. Scaling is only for datapoints, this fct is for all points
//x scale
if (xRange.type == ATRLinear)
ratio.x = plotarea.size.width/(xRange.max-xRange.min); //linear
else if (xRange.type == ATRLogaritmic)
ratio.x = plotarea.size.width/(log10f(xRange.max)-log10f(xRange.min)); //log
//y scale
if (yRange.type == ATRLinear)
ratio.y = plotarea.size.height/(yRange.max-yRange.min); //linear
else if (yRange.type == ATRLogaritmic)
ratio.y = plotarea.size.height/(log10f(yRange.max)-log10f(yRange.min)); //log
}