Commit 47821c71 authored by olau@iola.dk's avatar olau@iola.dk

Refactor the axis dimension calculations slightly to avoid the whole

implicit assumptions madness and support turning axes on and off
(suggested by Time Tuominen in issue 466), this adds axis.show and
axis.reserveSpace - still need to figure out what to do about the
public axis-snarfing API and get it documented.


git-svn-id: https://flot.googlecode.com/svn/trunk@302 1e0a6537-2640-0410-bfb7-f154510ff394
parent 4715e826
...@@ -172,6 +172,7 @@ Customizing the axes ...@@ -172,6 +172,7 @@ Customizing the axes
==================== ====================
xaxis, yaxis: { xaxis, yaxis: {
show: null or true/false
position: "bottom" or "top" or "left" or "right" position: "bottom" or "top" or "left" or "right"
mode: null or "time" mode: null or "time"
...@@ -193,6 +194,7 @@ Customizing the axes ...@@ -193,6 +194,7 @@ Customizing the axes
labelWidth: null or number labelWidth: null or number
labelHeight: null or number labelHeight: null or number
reserveSpace: null or true
tickLength: null or number tickLength: null or number
...@@ -203,6 +205,10 @@ All axes have the same kind of options. The following describes how to ...@@ -203,6 +205,10 @@ All axes have the same kind of options. The following describes how to
configure one axis, see below for what to do if you've got more than configure one axis, see below for what to do if you've got more than
one x axis or y axis. one x axis or y axis.
The "show" option defaults to null which means the axis will show up
if there's data associated with it. You can override this by setting
"show" to true or false.
The "position" option specifies where the axis is placed, bottom or The "position" option specifies where the axis is placed, bottom or
top for x axes, left or right for y axes. The "mode" option determines top for x axes, left or right for y axes. The "mode" option determines
how the data is interpreted, the default of null means as decimal how the data is interpreted, the default of null means as decimal
...@@ -330,7 +336,9 @@ an example of a custom formatter: ...@@ -330,7 +336,9 @@ an example of a custom formatter:
"labelWidth" and "labelHeight" specifies a fixed size of the tick "labelWidth" and "labelHeight" specifies a fixed size of the tick
labels in pixels. They're useful in case you need to align several labels in pixels. They're useful in case you need to align several
plots. plots. "reserveSpace" means that even if an axis isn't shown, Flot
should reserve space for it - it is useful in combination with
labelWidth and labelHeight for aligning multi-axis charts.
"tickLength" is the length of the tick lines in pixels. By default, the "tickLength" is the length of the tick lines in pixels. By default, the
innermost axes will have ticks that extend all across the plot, while innermost axes will have ticks that extend all across the plot, while
...@@ -918,7 +926,8 @@ Flot to keep track of its state, so be careful. ...@@ -918,7 +926,8 @@ Flot to keep track of its state, so be careful.
With multiple axes, the extra axes are returned as x2axis, x3axis, With multiple axes, the extra axes are returned as x2axis, x3axis,
etc., e.g. getAxes().y2axis is the second y axis. You can check etc., e.g. getAxes().y2axis is the second y axis. You can check
y2axis.used to see whether the axis is actually in use or not. y2axis.used to see whether the axis is associated with any data
points and y2axis.show to see if it is currently shown.
- getPlaceholder() - getPlaceholder()
......
...@@ -84,6 +84,9 @@ Changes: ...@@ -84,6 +84,9 @@ Changes:
on patch by kkaefer). on patch by kkaefer).
- Support for providing the drag cursor for the navigate plugin as an - Support for providing the drag cursor for the navigate plugin as an
option (based on patch by Kelly T. Moore). option (based on patch by Kelly T. Moore).
- Options for controlling whether an axis is shown or not (suggestion
by Timo Tuominen) and whether to reserve space for it even if it
isn't shown.
- New hooks: drawSeries - New hooks: drawSeries
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
backgroundOpacity: 0.85 // set to 0 to avoid background backgroundOpacity: 0.85 // set to 0 to avoid background
}, },
xaxis: { xaxis: {
show: null, // null = auto-detect, true = always, false = never
position: "bottom", // or "top" position: "bottom", // or "top"
mode: null, // null or "time" mode: null, // null or "time"
color: null, // base color, labels, ticks color: null, // base color, labels, ticks
...@@ -64,6 +65,7 @@ ...@@ -64,6 +65,7 @@
tickFormatter: null, // fn: number -> string tickFormatter: null, // fn: number -> string
labelWidth: null, // size of tick labels in pixels labelWidth: null, // size of tick labels in pixels
labelHeight: null, labelHeight: null,
reserveSpace: null, // whether to reserve space even if axis isn't shown
tickLength: null, // size in pixels of ticks, or "full" for whole line tickLength: null, // size in pixels of ticks, or "full" for whole line
alignTicksWithAxis: null, // axis number or null for no sync alignTicksWithAxis: null, // axis number or null for no sync
...@@ -256,6 +258,7 @@ ...@@ -256,6 +258,7 @@
options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
for (i = 0; i < Math.max(1, options.yaxes.length); ++i) for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
// backwards compatibility, to be removed in future // backwards compatibility, to be removed in future
if (options.xaxis.noTicks && options.xaxis.ticks == null) if (options.xaxis.noTicks && options.xaxis.ticks == null)
options.xaxis.ticks = options.xaxis.noTicks; options.xaxis.ticks = options.xaxis.noTicks;
...@@ -282,6 +285,7 @@ ...@@ -282,6 +285,7 @@
if (options.shadowSize != null) if (options.shadowSize != null)
options.series.shadowSize = options.shadowSize; options.series.shadowSize = options.shadowSize;
// save options on axes for future reference
for (i = 0; i < options.xaxes.length; ++i) for (i = 0; i < options.xaxes.length; ++i)
getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
for (i = 0; i < options.yaxes.length; ++i) for (i = 0; i < options.yaxes.length; ++i)
...@@ -331,6 +335,11 @@ ...@@ -331,6 +335,11 @@
return a; return a;
} }
function allAxes() {
// return flat array without annoying null entries
return $.grep(xaxes.concat(yaxes), function (a) { return a; });
}
function canvasToAxisCoords(pos) { function canvasToAxisCoords(pos) {
// return an object with x/y corresponding to all used axes // return an object with x/y corresponding to all used axes
var res = {}, i, axis; var res = {}, i, axis;
...@@ -502,15 +511,6 @@ ...@@ -502,15 +511,6 @@
i, j, k, m, length, i, j, k, m, length,
s, points, ps, x, y, axis, val, f, p; s, points, ps, x, y, axis, val, f, p;
function initAxis(axis, number) {
if (!axis)
return;
axis.datamin = topSentry;
axis.datamax = bottomSentry;
axis.used = false;
}
function updateAxis(axis, min, max) { function updateAxis(axis, min, max) {
if (min < axis.datamin && min != -fakeInfinity) if (min < axis.datamin && min != -fakeInfinity)
axis.datamin = min; axis.datamin = min;
...@@ -518,10 +518,12 @@ ...@@ -518,10 +518,12 @@
axis.datamax = max; axis.datamax = max;
} }
for (i = 0; i < xaxes.length; ++i) $.each(allAxes(), function (_, axis) {
initAxis(xaxes[i]); // init axis
for (i = 0; i < yaxes.length; ++i) axis.datamin = topSentry;
initAxis(yaxes[i]); axis.datamax = bottomSentry;
axis.used = false;
});
for (i = 0; i < series.length; ++i) { for (i = 0; i < series.length; ++i) {
s = series[i]; s = series[i];
...@@ -691,7 +693,7 @@ ...@@ -691,7 +693,7 @@
updateAxis(s.yaxis, ymin, ymax); updateAxis(s.yaxis, ymin, ymax);
} }
$.each(getUsedAxes(), function (i, axis) { $.each(allAxes(), function (_, axis) {
if (axis.datamin == topSentry) if (axis.datamin == topSentry)
axis.datamin = null; axis.datamin = null;
if (axis.datamax == bottomSentry) if (axis.datamax == bottomSentry)
...@@ -802,9 +804,6 @@ ...@@ -802,9 +804,6 @@
} }
function measureTickLabels(axis) { function measureTickLabels(axis) {
if (!axis)
return;
var opts = axis.options, i, ticks = axis.ticks || [], labels = [], var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
...@@ -869,10 +868,7 @@ ...@@ -869,10 +868,7 @@
axis.labelHeight = h; axis.labelHeight = h;
} }
function computeAxisBox(axis) { function computeAxisBoxFirstPhase(axis) {
if (!axis || !axis.labelWidth || !axis.labelHeight)
return;
// find the bounding box of the axis by looking at label // find the bounding box of the axis by looking at label
// widths/heights and ticks, make room by diminishing the // widths/heights and ticks, make room by diminishing the
// plotOffset // plotOffset
...@@ -888,7 +884,7 @@ ...@@ -888,7 +884,7 @@
// determine axis margin // determine axis margin
var samePosition = $.grep(all, function (a) { var samePosition = $.grep(all, function (a) {
return a && a.options.position == pos && (a.labelHeight || a.labelWidth); return a && a.options.position == pos && a.reserveSpace;
}); });
if ($.inArray(axis, samePosition) == samePosition.length - 1) if ($.inArray(axis, samePosition) == samePosition.length - 1)
axismargin = 0; // outermost axismargin = 0; // outermost
...@@ -898,7 +894,7 @@ ...@@ -898,7 +894,7 @@
tickLength = "full"; tickLength = "full";
var sameDirection = $.grep(all, function (a) { var sameDirection = $.grep(all, function (a) {
return a && (a.labelHeight || a.labelWidth); return a && a.reserveSpace;
}); });
var innermost = $.inArray(axis, sameDirection) == 0; var innermost = $.inArray(axis, sameDirection) == 0;
...@@ -941,10 +937,7 @@ ...@@ -941,10 +937,7 @@
axis.innermost = innermost; axis.innermost = innermost;
} }
function fixupAxisBox(axis) { function computeAxisBoxSecondPhase(axis) {
if (!axis || !axis.labelWidth || !axis.labelHeight)
return;
// set remaining bounding box coordinates // set remaining bounding box coordinates
if (axis.direction == "x") { if (axis.direction == "x") {
axis.box.left = plotOffset.left; axis.box.left = plotOffset.left;
...@@ -957,40 +950,43 @@ ...@@ -957,40 +950,43 @@
} }
function setupGrid() { function setupGrid() {
var axes = getUsedAxes(), j, k; var i, axes = allAxes();
// compute axis intervals // first calculate the plot and axis box dimensions
for (k = 0; k < axes.length; ++k)
setRange(axes[k]); $.each(axes, function (_, axis) {
axis.show = axis.options.show;
if (axis.show == null)
axis.show = axis.used; // by default an axis is visible if it's got data
axis.reserveSpace = axis.show || axis.options.reserveSpace;
setRange(axis);
});
allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
if (options.grid.show) { if (options.grid.show) {
// make the ticks $.each(allocatedAxes, function (_, axis) {
for (k = 0; k < axes.length; ++k) { // make the ticks
setupTickGeneration(axes[k]); setupTickGeneration(axis);
setTicks(axes[k]); setTicks(axis);
snapRangeToTicks(axes[k], axes[k].ticks); snapRangeToTicks(axis, axis.ticks);
}
// find labelWidth/Height for axis
measureTickLabels(axis);
});
// find labelWidth/Height, do this on all, not just // with all dimensions in house, we can compute the
// used as we might need to reserve space for unused // axis boxes, start from the outside (reverse order)
// too if their labelWidth/Height is set for (i = allocatedAxes.length - 1; i >= 0; --i)
for (j = 0; j < xaxes.length; ++j) computeAxisBoxFirstPhase(allocatedAxes[i]);
measureTickLabels(xaxes[j]);
for (j = 0; j < yaxes.length; ++j)
measureTickLabels(yaxes[j]);
// compute the axis boxes, start from the outside (reverse order)
for (j = xaxes.length - 1; j >= 0; --j)
computeAxisBox(xaxes[j]);
for (j = yaxes.length - 1; j >= 0; --j)
computeAxisBox(yaxes[j]);
// make sure we've got enough space for things that // make sure we've got enough space for things that
// might stick out // might stick out
var maxOutset = 0; var maxOutset = 0;
for (var i = 0; i < series.length; ++i) for (i = 0; i < series.length; ++i)
maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
for (var a in plotOffset) { for (var a in plotOffset) {
...@@ -1003,13 +999,15 @@ ...@@ -1003,13 +999,15 @@
plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
// now we got the proper plotWidth/Height, we can compute the scaling // now we got the proper plotWidth/Height, we can compute the scaling
for (k = 0; k < axes.length; ++k) $.each(axes, function (_, axis) {
setTransformationHelpers(axes[k]); setTransformationHelpers(axis);
});
if (options.grid.show) { if (options.grid.show) {
for (k = 0; k < axes.length; ++k) $.each(allocatedAxes, function (_, axis) {
fixupAxisBox(axes[k]); computeAxisBoxSecondPhase(axis);
});
insertAxisLabels(); insertAxisLabels();
} }
...@@ -1028,7 +1026,7 @@ ...@@ -1028,7 +1026,7 @@
if (opts.min == null) if (opts.min == null)
min -= widen; min -= widen;
// alway widen max if we couldn't widen min to ensure we // always widen max if we couldn't widen min to ensure we
// don't fall into min == max which doesn't work // don't fall into min == max which doesn't work
if (opts.max == null || opts.min != null) if (opts.max == null || opts.min != null)
max += widen; max += widen;
...@@ -1062,12 +1060,10 @@ ...@@ -1062,12 +1060,10 @@
var noTicks; var noTicks;
if (typeof opts.ticks == "number" && opts.ticks > 0) if (typeof opts.ticks == "number" && opts.ticks > 0)
noTicks = opts.ticks; noTicks = opts.ticks;
else if (axis.direction == "x")
// heuristic based on the model a*sqrt(x) fitted to
// some reasonable data points
noTicks = 0.3 * Math.sqrt(canvasWidth);
else else
noTicks = 0.3 * Math.sqrt(canvasHeight); // heuristic based on the model a*sqrt(x) fitted to
// some data points that seemed reasonable
noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
var delta = (axis.max - axis.min) / noTicks, var delta = (axis.max - axis.min) / noTicks,
size, generator, unit, formatter, i, magn, norm; size, generator, unit, formatter, i, magn, norm;
...@@ -1330,8 +1326,6 @@ ...@@ -1330,8 +1326,6 @@
} }
function setTicks(axis) { function setTicks(axis) {
axis.ticks = [];
var oticks = axis.options.ticks, ticks = []; var oticks = axis.options.ticks, ticks = [];
if (oticks == null || (typeof oticks == "number" && oticks > 0)) if (oticks == null || (typeof oticks == "number" && oticks > 0))
ticks = axis.tickGenerator(axis); ticks = axis.tickGenerator(axis);
...@@ -1345,19 +1339,21 @@ ...@@ -1345,19 +1339,21 @@
// clean up/labelify the supplied ticks, copy them over // clean up/labelify the supplied ticks, copy them over
var i, v; var i, v;
axis.ticks = [];
for (i = 0; i < ticks.length; ++i) { for (i = 0; i < ticks.length; ++i) {
var label = null; var label = null;
var t = ticks[i]; var t = ticks[i];
if (typeof t == "object") { if (typeof t == "object") {
v = t[0]; v = +t[0];
if (t.length > 1) if (t.length > 1)
label = t[1]; label = t[1];
} }
else else
v = t; v = +t;
if (label == null) if (label == null)
label = axis.tickFormatter(v, axis); label = axis.tickFormatter(v, axis);
axis.ticks[i] = { v: v, label: label }; if (!isNaN(v))
axis.ticks.push({ v: v, label: label });
} }
} }
...@@ -1513,14 +1509,13 @@ ...@@ -1513,14 +1509,13 @@
} }
// draw the ticks // draw the ticks
var axes = getUsedAxes(), bw = options.grid.borderWidth; var axes = allAxes(), bw = options.grid.borderWidth;
for (var j = 0; j < axes.length; ++j) { for (var j = 0; j < axes.length; ++j) {
var axis = axes[j], box = axis.box, var axis = axes[j], box = axis.box,
t = axis.tickLength, x, y, xoff, yoff; t = axis.tickLength, x, y, xoff, yoff;
if (!axis.show || axis.ticks.length == 0)
if (axis.ticks.length == 0) continue
continue;
ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
ctx.lineWidth = 1; ctx.lineWidth = 1;
...@@ -1618,9 +1613,11 @@ ...@@ -1618,9 +1613,11 @@
var html = ['<div class="tickLabels" style="font-size:smaller">']; var html = ['<div class="tickLabels" style="font-size:smaller">'];
var axes = getUsedAxes(); var axes = allAxes();
for (var j = 0; j < axes.length; ++j) { for (var j = 0; j < axes.length; ++j) {
var axis = axes[j], box = axis.box; var axis = axes[j], box = axis.box;
if (!axis.show)
continue;
//debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>') //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">'); html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
for (var i = 0; i < axis.ticks.length; ++i) { for (var i = 0; i < axis.ticks.length; ++i) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment