diff --git a/API.txt b/API.txt index fe297d9..b2ace0a 100644 --- a/API.txt +++ b/API.txt @@ -195,6 +195,8 @@ Customizing the axes labelHeight: null or number tickLength: null or number + + alignTicksWithAxis: null or number } All axes have the same kind of options. The following describes how to @@ -332,7 +334,15 @@ innermost axes will have ticks that extend all across the plot, while any extra axes use small ticks. A value of null means use the default, while a number means small ticks of that length - set it to 0 to hide the lines completely. - + +If you set "alignTicksWithAxis" to the number of another axis, e.g. +alignTicksWithAxis: 1, Flot will ensure that the autogenerated ticks +of this axis are aligned with the ticks of the other axis. This may +improve the looks, e.g. if you have one y axis to the left and one to +the right, because the grid lines will then match the ticks in both +ends. The trade-off is that the forced ticks won't necessarily be at +natural places. + Multiple axes ============= diff --git a/NEWS.txt b/NEWS.txt index c36775c..1da0883 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -60,6 +60,10 @@ Changes: Services, www.flightdataservices.com). - Tick color is now auto-generated as the base color with some transparency (unless you override it). +- Support for aligning ticks in the axes with "alignTicksWithAxis" to + ensure that they appear next to each other rather than in between, + at the expense of possibly awkward tick steps (sponsored by Flight + Data Services, www.flightdataservices.com). - New hooks: drawSeries diff --git a/examples/multiple-axes.html b/examples/multiple-axes.html index 35fc472..fd86cfb 100644 --- a/examples/multiple-axes.html +++ b/examples/multiple-axes.html @@ -39,7 +39,12 @@ $(function () { { xaxes: [ { mode: 'time' } ], yaxes: [ { min: 0 }, - { position: position, tickFormatter: euroFormatter } ], + { + // align if we are to the right + alignTicksWithAxis: position == "right" ? 1 : null, + position: position, + tickFormatter: euroFormatter + } ], legend: { position: 'sw' } }); } diff --git a/jquery.flot.js b/jquery.flot.js index b6eb238..088bbb7 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -62,9 +62,10 @@ autoscaleMargin: null, // margin in % to add if auto-setting min/max ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks tickFormatter: null, // fn: number -> string - tickLength: null, // size in pixels of ticks, or "full" for whole line labelWidth: null, // size of tick labels in pixels labelHeight: null, + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync // mode specific options tickDecimals: null, // no. of decimals, null means auto @@ -881,8 +882,9 @@ var sameDirection = $.grep(all, function (a) { return a && (a.labelHeight || a.labelWidth); }); - - if ($.inArray(axis, sameDirection) != 0 && tickLength == "full") + + var innermost = $.inArray(axis, sameDirection) == 0; + if (!innermost && tickLength == "full") tickLength = 5; if (!isNaN(+tickLength)) @@ -918,7 +920,7 @@ axis.position = pos; axis.tickLength = tickLength; axis.box.padding = padding; - axis.innermost = $.inArray(axis, samePosition) == 0; + axis.innermost = innermost; } function fixupAxisBox(axis) { @@ -947,11 +949,12 @@ for (k = 0; k < axes.length; ++k) { setupTickGeneration(axes[k]); setTicks(axes[k]); + snapRangeToTicks(axes[k], axes[k].ticks); } // find labelWidth/Height, do this on all, not just // used as we might need to reserve space for unused - // to if their labelWidth/Height is set + // too if their labelWidth/Height is set for (j = 0; j < xaxes.length; ++j) measureTickLabels(xaxes[j]); for (j = 0; j < yaxes.length; ++j) @@ -1042,7 +1045,7 @@ noTicks = 0.3 * Math.sqrt(canvasWidth); else noTicks = 0.3 * Math.sqrt(canvasHeight); - + var delta = (axis.max - axis.min) / noTicks, size, generator, unit, formatter, i, magn, norm; @@ -1107,10 +1110,7 @@ size *= magn; } - if (opts.tickSize) { - size = opts.tickSize[0]; - unit = opts.tickSize[1]; - } + axis.tickSize = opts.tickSize || [size, unit]; generator = function(axis) { var ticks = [], @@ -1148,7 +1148,7 @@ do { prev = v; v = d.getTime(); - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); + ticks.push(v); if (unit == "month") { if (tickSize < 1) { // a bit complicated - we'll divide the month @@ -1238,10 +1238,8 @@ if (opts.minTickSize != null && size < opts.minTickSize) size = opts.minTickSize; - if (opts.tickSize != null) - size = opts.tickSize; - - axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; generator = function (axis) { var ticks = []; @@ -1252,7 +1250,7 @@ do { prev = v; v = start + i * axis.tickSize; - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); + ticks.push(v); ++i; } while (v < axis.max && v != prev); return ticks; @@ -1263,7 +1261,44 @@ }; } - axis.tickSize = unit ? [size, unit] : size; + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = generator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + generator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (axis.mode != "time" && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), + ts = generator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + axis.tickGenerator = generator; if ($.isFunction(opts.tickFormatter)) axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; @@ -1272,47 +1307,44 @@ } function setTicks(axis) { - var opts = axis.options; - axis.ticks = []; - if (opts.ticks == null) - axis.ticks = axis.tickGenerator(axis); - else if (typeof opts.ticks == "number") { - if (opts.ticks > 0) - axis.ticks = axis.tickGenerator(axis); - } - else if (opts.ticks) { - var ticks = opts.ticks; - - if ($.isFunction(ticks)) + var oticks = axis.options.ticks, ticks = null; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) // generate the ticks - ticks = ticks({ min: axis.min, max: axis.max }); - - // clean up the user-supplied ticks, copy them over - var i, v; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = t; - if (label == null) - label = axis.tickFormatter(v, axis); - axis.ticks[i] = { v: v, label: label }; + ticks = oticks({ min: axis.min, max: axis.max }); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = t[0]; + if (t.length > 1) + label = t[1]; } + else + v = t; + if (label == null) + label = axis.tickFormatter(v, axis); + axis.ticks[i] = { v: v, label: label }; } + } - if (opts.autoscaleMargin != null && axis.ticks.length > 0) { + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin != null && ticks.length > 0) { // snap to ticks - if (opts.min == null) - axis.min = Math.min(axis.min, axis.ticks[0].v); - if (opts.max == null && axis.ticks.length > 1) - axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v); + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); } }