From 47821c713bce10913e5871949ca0a3057dca6801 Mon Sep 17 00:00:00 2001 From: "olau@iola.dk" Date: Fri, 11 Mar 2011 20:29:03 +0000 Subject: [PATCH] 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 --- API.txt | 13 ++++- NEWS.txt | 3 ++ jquery.flot.js | 143 ++++++++++++++++++++++++------------------------- 3 files changed, 84 insertions(+), 75 deletions(-) diff --git a/API.txt b/API.txt index 850d834..392df77 100644 --- a/API.txt +++ b/API.txt @@ -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() diff --git a/NEWS.txt b/NEWS.txt index 9283590..76e46d1 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -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 diff --git a/jquery.flot.js b/jquery.flot.js index 5e645fd..51a4509 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -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 = ['
']; - 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('
') html.push('
'); for (var i = 0; i < axis.ticks.length; ++i) {