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
====================
xaxis, yaxis: {
show: null or true/false
position: "bottom" or "top" or "left" or "right"
mode: null or "time"
......@@ -193,6 +194,7 @@ Customizing the axes
labelWidth: null or number
labelHeight: null or number
reserveSpace: null or true
tickLength: null or number
......@@ -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
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
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
......@@ -330,7 +336,9 @@ an example of a custom formatter:
"labelWidth" and "labelHeight" specifies a fixed size of the tick
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
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.
With multiple axes, the extra axes are returned as x2axis, x3axis,
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()
......
......@@ -84,6 +84,9 @@ Changes:
on patch by kkaefer).
- Support for providing the drag cursor for the navigate plugin as an
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
......
......@@ -51,6 +51,7 @@
backgroundOpacity: 0.85 // set to 0 to avoid background
},
xaxis: {
show: null, // null = auto-detect, true = always, false = never
position: "bottom", // or "top"
mode: null, // null or "time"
color: null, // base color, labels, ticks
......@@ -64,6 +65,7 @@
tickFormatter: null, // fn: number -> string
labelWidth: null, // size of tick labels in pixels
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
alignTicksWithAxis: null, // axis number or null for no sync
......@@ -256,6 +258,7 @@
options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
// backwards compatibility, to be removed in future
if (options.xaxis.noTicks && options.xaxis.ticks == null)
options.xaxis.ticks = options.xaxis.noTicks;
......@@ -282,6 +285,7 @@
if (options.shadowSize != null)
options.series.shadowSize = options.shadowSize;
// save options on axes for future reference
for (i = 0; i < options.xaxes.length; ++i)
getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
for (i = 0; i < options.yaxes.length; ++i)
......@@ -331,6 +335,11 @@
return a;
}
function allAxes() {
// return flat array without annoying null entries
return $.grep(xaxes.concat(yaxes), function (a) { return a; });
}
function canvasToAxisCoords(pos) {
// return an object with x/y corresponding to all used axes
var res = {}, i, axis;
......@@ -502,15 +511,6 @@
i, j, k, m, length,
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) {
if (min < axis.datamin && min != -fakeInfinity)
axis.datamin = min;
......@@ -518,10 +518,12 @@
axis.datamax = max;
}
for (i = 0; i < xaxes.length; ++i)
initAxis(xaxes[i]);
for (i = 0; i < yaxes.length; ++i)
initAxis(yaxes[i]);
$.each(allAxes(), function (_, axis) {
// init axis
axis.datamin = topSentry;
axis.datamax = bottomSentry;
axis.used = false;
});
for (i = 0; i < series.length; ++i) {
s = series[i];
......@@ -691,7 +693,7 @@
updateAxis(s.yaxis, ymin, ymax);
}
$.each(getUsedAxes(), function (i, axis) {
$.each(allAxes(), function (_, axis) {
if (axis.datamin == topSentry)
axis.datamin = null;
if (axis.datamax == bottomSentry)
......@@ -802,9 +804,6 @@
}
function measureTickLabels(axis) {
if (!axis)
return;
var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
......@@ -869,10 +868,7 @@
axis.labelHeight = h;
}
function computeAxisBox(axis) {
if (!axis || !axis.labelWidth || !axis.labelHeight)
return;
function computeAxisBoxFirstPhase(axis) {
// find the bounding box of the axis by looking at label
// widths/heights and ticks, make room by diminishing the
// plotOffset
......@@ -888,7 +884,7 @@
// determine axis margin
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)
axismargin = 0; // outermost
......@@ -898,7 +894,7 @@
tickLength = "full";
var sameDirection = $.grep(all, function (a) {
return a && (a.labelHeight || a.labelWidth);
return a && a.reserveSpace;
});
var innermost = $.inArray(axis, sameDirection) == 0;
......@@ -941,10 +937,7 @@
axis.innermost = innermost;
}
function fixupAxisBox(axis) {
if (!axis || !axis.labelWidth || !axis.labelHeight)
return;
function computeAxisBoxSecondPhase(axis) {
// set remaining bounding box coordinates
if (axis.direction == "x") {
axis.box.left = plotOffset.left;
......@@ -957,40 +950,43 @@
}
function setupGrid() {
var axes = getUsedAxes(), j, k;
var i, axes = allAxes();
// compute axis intervals
for (k = 0; k < axes.length; ++k)
setRange(axes[k]);
// first calculate the plot and axis box dimensions
$.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;
if (options.grid.show) {
// make the ticks
for (k = 0; k < axes.length; ++k) {
setupTickGeneration(axes[k]);
setTicks(axes[k]);
snapRangeToTicks(axes[k], axes[k].ticks);
}
$.each(allocatedAxes, function (_, axis) {
// make the ticks
setupTickGeneration(axis);
setTicks(axis);
snapRangeToTicks(axis, axis.ticks);
// find labelWidth/Height for axis
measureTickLabels(axis);
});
// find labelWidth/Height, do this on all, not just
// used as we might need to reserve space for unused
// too if their labelWidth/Height is set
for (j = 0; j < xaxes.length; ++j)
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]);
// with all dimensions in house, we can compute the
// axis boxes, start from the outside (reverse order)
for (i = allocatedAxes.length - 1; i >= 0; --i)
computeAxisBoxFirstPhase(allocatedAxes[i]);
// make sure we've got enough space for things that
// might stick out
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));
for (var a in plotOffset) {
......@@ -1003,13 +999,15 @@
plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
// now we got the proper plotWidth/Height, we can compute the scaling
for (k = 0; k < axes.length; ++k)
setTransformationHelpers(axes[k]);
$.each(axes, function (_, axis) {
setTransformationHelpers(axis);
});
if (options.grid.show) {
for (k = 0; k < axes.length; ++k)
fixupAxisBox(axes[k]);
$.each(allocatedAxes, function (_, axis) {
computeAxisBoxSecondPhase(axis);
});
insertAxisLabels();
}
......@@ -1028,7 +1026,7 @@
if (opts.min == null)
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
if (opts.max == null || opts.min != null)
max += widen;
......@@ -1062,12 +1060,10 @@
var noTicks;
if (typeof opts.ticks == "number" && opts.ticks > 0)
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
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,
size, generator, unit, formatter, i, magn, norm;
......@@ -1330,8 +1326,6 @@
}
function setTicks(axis) {
axis.ticks = [];
var oticks = axis.options.ticks, ticks = [];
if (oticks == null || (typeof oticks == "number" && oticks > 0))
ticks = axis.tickGenerator(axis);
......@@ -1345,19 +1339,21 @@
// clean up/labelify the supplied ticks, copy them over
var i, v;
axis.ticks = [];
for (i = 0; i < ticks.length; ++i) {
var label = null;
var t = ticks[i];
if (typeof t == "object") {
v = t[0];
v = +t[0];
if (t.length > 1)
label = t[1];
}
else
v = t;
v = +t;
if (label == null)
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 @@
}
// 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) {
var axis = axes[j], box = axis.box,
t = axis.tickLength, x, y, xoff, yoff;
if (axis.ticks.length == 0)
continue;
if (!axis.show || axis.ticks.length == 0)
continue
ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
ctx.lineWidth = 1;
......@@ -1618,9 +1613,11 @@
var html = ['<div class="tickLabels" style="font-size:smaller">'];
var axes = getUsedAxes();
var axes = allAxes();
for (var j = 0; j < axes.length; ++j) {
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>')
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) {
......
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