From cfe16e1edb35e3bfaba62e0a6a43ebe7416e8ce3 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Mon, 10 Jun 2013 13:22:49 -0400 Subject: [PATCH 1/8] Rename labelWidth/Height to tickWidth/Height. --- API.md | 7 +++---- jquery.flot.js | 39 ++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/API.md b/API.md index e0d6d55..ca10e7e 100644 --- a/API.md +++ b/API.md @@ -264,12 +264,11 @@ xaxis, yaxis: { tickFormatter: (fn: number, object -> string) or string tickDecimals: null or number - labelWidth: null or number - labelHeight: null or number - reserveSpace: null or true - tickLength: null or number + tickWidth: null or number + tickHeight: null or number + reserveSpace: null or true alignTicksWithAxis: null or number } ``` diff --git a/jquery.flot.js b/jquery.flot.js index acc535f..d8c1c12 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -495,8 +495,8 @@ Licensed under the MIT license. 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 - labelWidth: null, // size of tick labels in pixels - labelHeight: null, + tickWidth: null, // size of tick labels in pixels + tickHeight: 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 @@ -1370,9 +1370,10 @@ Licensed under the MIT license. var opts = axis.options, ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || axis.direction === "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, + // Label width & height are deprecated; remove in 1.0! + tickWidth = opts.tickWidth || opts.labelWidth || 0, + tickHeight = opts.tickHeight || opts.labelHeight || 0, + maxWidth = tickWidth || axis.direction === "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, font = opts.font || "flot-tick-label tickLabel"; @@ -1387,12 +1388,17 @@ Licensed under the MIT license. var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); + tickWidth = Math.max(tickWidth, info.width); + tickHeight = Math.max(tickHeight, info.height); } - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; + axis.tickWidth = opts.tickWidth || opts.labelWidth || tickWidth; + axis.tickHeight = opts.tickHeight || opts.labelHeight || tickHeight; + + // Label width/height properties are deprecated; remove in 1.0! + + axis.labelWidth = axis.tickWidth; + axis.labelHeight = axis.tickHeight; } function allocateAxisBoxFirstPhase(axis) { @@ -1402,8 +1408,8 @@ Licensed under the MIT license. // dimension per axis, the other dimension depends on the // other axes so will have to wait - var lw = axis.labelWidth, - lh = axis.labelHeight, + var lw = axis.tickWidth, + lh = axis.tickHeight, pos = axis.options.position, tickLength = axis.options.tickLength, axisMargin = options.grid.axisMargin, @@ -1471,11 +1477,11 @@ Licensed under the MIT license. // now that all axis boxes have been placed in one // dimension, we can set the remaining dimension coordinates if (axis.direction === "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; + axis.box.left = plotOffset.left - axis.tickWidth / 2; + axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.tickWidth; } else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; + axis.box.top = plotOffset.top - axis.tickHeight / 2; + axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.tickHeight; } } @@ -1504,7 +1510,7 @@ Licensed under the MIT license. $.each(allAxes(), function (_, axis) { var dir = axis.direction; if (axis.reserveSpace) { - margins[dir] = Math.ceil(Math.max(margins[dir], (dir === "x" ? axis.labelWidth : axis.labelHeight) / 2)); + margins[dir] = Math.ceil(Math.max(margins[dir], (dir === "x" ? axis.tickWidth : axis.tickHeight) / 2)); } }); @@ -1561,7 +1567,6 @@ Licensed under the MIT license. setupTickGeneration(axis); setTicks(axis); snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis measureTickLabels(axis); }); From 65c38d0b595d311a2bfef643f289f3c7efbf9aa0 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Mon, 10 Jun 2013 13:28:16 -0400 Subject: [PATCH 2/8] Minor reordering of axis options. --- API.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index ca10e7e..6123ca3 100644 --- a/API.md +++ b/API.md @@ -248,7 +248,6 @@ xaxis, yaxis: { timezone: null, "browser" or timezone (only makes sense for mode: "time") color: null or color spec - tickColor: null or color spec font: null or font spec object min: null or number @@ -264,12 +263,14 @@ xaxis, yaxis: { tickFormatter: (fn: number, object -> string) or string tickDecimals: null or number + tickColor: null or color spec tickLength: null or number + tickWidth: null or number tickHeight: null or number - reserveSpace: null or true alignTicksWithAxis: null or number + reserveSpace: null or true } ``` From 8d38774cac65d1fe80534cb99a6a505eb85d29d0 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Thu, 4 Jul 2013 23:32:55 -0400 Subject: [PATCH 3/8] Added axis.tickFont as an override for axis.font. --- API.md | 1 + jquery.flot.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/API.md b/API.md index 6123ca3..42c638a 100644 --- a/API.md +++ b/API.md @@ -268,6 +268,7 @@ xaxis, yaxis: { tickWidth: null or number tickHeight: null or number + tickFont: null or font spec object alignTicksWithAxis: null or number reserveSpace: null or true diff --git a/jquery.flot.js b/jquery.flot.js index d8c1c12..3764c16 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -736,11 +736,12 @@ Licensed under the MIT license. axisOptions = $.extend(true, {}, options.xaxis, axisOptions); options.xaxes[i] = axisOptions; + fontDefaults.color = axisOptions.color; if (axisOptions.font) { axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } + } + if (axisOptions.tickFont || axisOptions.font) { + axisOptions.tickFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.tickFont); } } @@ -755,11 +756,12 @@ Licensed under the MIT license. axisOptions = $.extend(true, {}, options.yaxis, axisOptions); options.yaxes[i] = axisOptions; + fontDefaults.color = axisOptions.color; if (axisOptions.font) { axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } + } + if (axisOptions.tickFont || axisOptions.font) { + axisOptions.tickFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.tickFont); } } @@ -1376,7 +1378,7 @@ Licensed under the MIT license. maxWidth = tickWidth || axis.direction === "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; + font = opts.tickFont || "flot-tick-label tickLabel"; for (var i = 0; i < ticks.length; ++i) { @@ -2167,7 +2169,7 @@ Licensed under the MIT license. var box = axis.box, legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", + font = axis.options.tickFont || "flot-tick-label tickLabel", tick, x, y, halign, valign; surface.removeText(layer); From f75a5a514fc53745b739bac675a6e0dcb24d8449 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Thu, 4 Jul 2013 23:36:04 -0400 Subject: [PATCH 4/8] Updated axis option defaults and documentation. Updated the axis option defaults and docs for tickColor, tickFont, tickWidth, and tickHeight. Also re-organized the API docs to match the master list, and revised many areas for clarity. --- API.md | 221 ++++++++++++++++++++++++------------------------- jquery.flot.js | 49 ++++++----- 2 files changed, 136 insertions(+), 134 deletions(-) diff --git a/API.md b/API.md index 42c638a..f82e3e2 100644 --- a/API.md +++ b/API.md @@ -245,61 +245,59 @@ xaxis, yaxis: { show: null or true/false position: "bottom" or "top" or "left" or "right" mode: null or "time" ("time" requires jquery.flot.time.js plugin) - timezone: null, "browser" or timezone (only makes sense for mode: "time") + timezone: null or "browser" or timezone (only used with mode: "time") color: null or color spec - font: null or font spec object + font: null or font-options object min: null or number max: null or number autoscaleMargin: null or number - + transform: null or fn: number -> number inverseTransform: null or fn: number -> number - + ticks: null or number or ticks array or (fn: axis -> ticks array) tickSize: number or array minTickSize: number or array - tickFormatter: (fn: number, object -> string) or string tickDecimals: null or number + tickFormatter: (fn: number, object -> string) or string tickColor: null or color spec tickLength: null or number tickWidth: null or number tickHeight: null or number - tickFont: null or font spec object + tickFont: null or font-options object alignTicksWithAxis: null or number reserveSpace: null or true } ``` -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. +All axes share the same set of options. The following descriptions are for a +single axis; read further down for how to create multiple x-axes or y-axes. If you don't set the "show" option (i.e. it is null), visibility is -auto-detected, i.e. the axis will show up if there's data associated -with it. You can override this by setting the "show" option 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 -numbers. Use "time" for time series data; see the time series data -section. The time plugin (jquery.flot.time.js) is required for time -series support. - -The "color" option determines the color of the line and ticks for the axis, and -defaults to the grid color with transparency. For more fine-grained control you -can also set the color of the ticks separately with "tickColor". - -You can customize the font and color used to draw the axis tick labels with CSS -or directly via the "font" option. When "font" is null - the default - each -tick label is given the 'flot-tick-label' class. For compatibility with Flot -0.7 and earlier the labels are also given the 'tickLabel' class, but this is -deprecated and scheduled to be removed with the release of version 1.0.0. +auto-detected, i.e. the axis will show up only 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, and left or right for y axes. + +The "mode" option determines how the data is interpreted, the default of null +interprets it as decimal numbers, while "time" interprets it as time series +data. See the time series data section for more information, and note that +the time plugin (jquery.flot.time.js) is required for time series support. + +The "color" option determines the color of the axis line and defaults for the +tick markers and label text. It defaults to the grid color with transparency. + +You can customize the font and color used to draw axis text with CSS or via +the "font" option. When "font" is null (the default) tick labels are assigned +the 'flot-tick-label' class. For compatibility with Flot 0.7 and earlier the +labels are also assigned the 'tickLabel' class, but this is deprecated and +scheduled to be removed with the release of version 1.0.0. To enable more granular control over styles, labels are divided between a set of text containers, with each holding the labels for one axis. These containers @@ -314,7 +312,7 @@ labels for a simple plot with only a single x-axis might look like this: ``` -For direct control over label styles you can also provide "font" as an object +For direct control over text styles you can also provide "font" as an object with this format: ```js @@ -332,28 +330,25 @@ with this format: The size and lineHeight must be expressed in pixels; CSS units such as 'em' or 'smaller' are not allowed. -The options "min"/"max" are the precise minimum/maximum value on the -scale. If you don't specify either of them, a value will automatically -be chosen based on the minimum/maximum data values. Note that Flot -always examines all the data values you feed to it, even if a -restriction on another axis may make some of them invisible (this -makes interactive use more stable). - -The "autoscaleMargin" is a bit esoteric: it's the fraction of margin -that the scaling algorithm will add to avoid that the outermost points -ends up on the grid border. Note that this margin is only applied when -a min or max value is not explicitly set. If a margin is specified, -the plot will furthermore extend the axis end-point to the nearest -whole tick. The default value is "null" for the x axes and 0.02 for y -axes which seems appropriate for most cases. - -"transform" and "inverseTransform" are callbacks you can put in to -change the way the data is drawn. You can design a function to -compress or expand certain parts of the axis non-linearly, e.g. -suppress weekends or compress far away points with a logarithm or some -other means. When Flot draws the plot, each value is first put through -the transform function. Here's an example, the x axis can be turned -into a natural logarithm axis with the following code: +The "min" and "max" options specify the precise minimum/maximum value on the +scale. If you don't specify either of them, Flot will automatically choose a +value based on the min/max values in the data. Note that Flot always examines +all the data values you feed to it, even if a restriction on another axis may +make some of them invisible (this makes interactive use more stable). + +The "autoscaleMargin" option is a bit esoteric: it's the fraction of margin +that the scaling algorithm will add to avoid having the outermost points end +up on the grid border. Note that this margin is only applied when a min or +max value is not explicitly set. If a margin is specified, the plot will also +extend the axis end-point to the nearest whole tick. The default value is null +for the x-axes and 0.02 for y-axes; this seems appropriate for most cases. + +"transform" and "inverseTransform" are callbacks that you can use to change +how the data is drawn. You can design a function to compress or expand parts +of the axis non-linearly, e.g. suppress weekends or compress far-away points +with a logarithm or some other means. When Flot draws the plot, each value is +first put through the transform function. For example, this is how one would +implement a log scale on the x-axis: ```js xaxis: { @@ -362,8 +357,7 @@ xaxis: { } ``` -Similarly, for reversing the y axis so the values appear in inverse -order: +Similarly, for reversing the y axis so the values appear in inverse order: ```js yaxis: { @@ -372,38 +366,34 @@ yaxis: { } ``` -Note that for finding extrema, Flot assumes that the transform -function does not reorder values (it should be monotone). - -The inverseTransform is simply the inverse of the transform function -(so v == inverseTransform(transform(v)) for all relevant v). It is -required for converting from canvas coordinates to data coordinates, -e.g. for a mouse interaction where a certain pixel is clicked. If you -don't use any interactive features of Flot, you may not need it. +Note that for finding extrema, Flot assumes that the transform function does +not reorder values (it should be monotone). +The inverseTransform is simply the inverse of the transform function, so +v == inverseTransform(transform(v)) for all relevant v. It is required for +converting from canvas coordinates to data coordinates, i.e. for a mouse +interaction where a certain pixel is clicked. If you don't use any +interactive features of Flot, you may not need it. -The rest of the options deal with the ticks. +If you don't specify any ticks options, a tick generator algorithm will make +some for you. The algorithm has two passes. It first estimates how many ticks +would be reasonable, uses this to compute a nice round tick interval size, +then generates the ticks. -If you don't specify any ticks, a tick generator algorithm will make -some for you. The algorithm has two passes. It first estimates how -many ticks would be reasonable and uses this number to compute a nice -round tick interval size. Then it generates the ticks. +You can specify how many ticks the algorithm aims for by setting "ticks" to a +number. The algorithm always tries to generate reasonably round tick values +so even if you ask for three ticks, you might get five if that fits better +with the rounding. If you don't want any ticks at all, set "ticks" to 0 or an +empty array. -You can specify how many ticks the algorithm aims for by setting -"ticks" to a number. The algorithm always tries to generate reasonably -round tick values so even if you ask for three ticks, you might get -five if that fits better with the rounding. If you don't want any -ticks at all, set "ticks" to 0 or an empty array. +Another option is to skip the rounding and directly set the tick interval +with "tickSize". Setting it to 2 gives you ticks at 2, 4, 6, etc. You can +also specify that you just don't want ticks at a size less than a specific +tick size with "minTickSize". Note that for time series, the format is an +array like [2, "month"]; see the time series data section for more info. -Another option is to skip the rounding part and directly set the tick -interval size with "tickSize". If you set it to 2, you'll get ticks at -2, 4, 6, etc. Alternatively, you can specify that you just don't want -ticks at a size less than a specific tick size with "minTickSize". -Note that for time series, the format is an array like [2, "month"], -see the next section. - -If you want to completely override the tick algorithm, you can specify -an array for "ticks", either like this: +If you want to completely override the tick algorithm, you can specify an +array for "ticks", either like this: ```js ticks: [0, 1.2, 2.4] @@ -417,11 +407,10 @@ ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]] You can mix the two if you like. -For extra flexibility you can specify a function as the "ticks" -parameter. The function will be called with an object with the axis -min and max and should return a ticks array. Here's a simplistic tick -generator that spits out intervals of pi, suitable for use on the x -axis for trigonometric functions: +For extra flexibility you can specify a function as the "ticks" parameter. +The function will be called with an object with the axis min and max, and +should return a ticks array. Here's a simple tick generator that produces +intervals of pi, suitable for use on the x-axis for trigonometric functions: ```js function piTickGenerator(axis) { @@ -435,13 +424,13 @@ function piTickGenerator(axis) { } ``` -You can control how the ticks look like with "tickDecimals", the +You can control how the ticks look with "tickDecimals", which specifies the number of decimals to display (default is auto-detected). Alternatively, for ultimate control over how ticks are formatted you can -provide a function to "tickFormatter". The function is passed two -parameters, the tick value and an axis object with information, and -should return a string. The default formatter looks like this: +provide a function to "tickFormatter". The function is passed two parameters, +the tick value and an axis object with information, and should return a +string. The default formatter looks like this: ```js function formatter(val, axis) { @@ -449,11 +438,10 @@ function formatter(val, axis) { } ``` -The axis object has "min" and "max" with the range of the axis, -"tickDecimals" with the number of decimals to round the value to and -"tickSize" with the size of the interval between ticks as calculated -by the automatic axis scaling algorithm (or specified by you). Here's -an example of a custom formatter: +The axis object has "min" and "max" with the axis range, "tickDecimals" with +the number of decimals to round the value to and "tickSize" with the size of +the interval between ticks as calculated by the automatic axis scaling +algorithm (or specified by you). Here's an example of a custom formatter: ```js function suffixFormatter(val, axis) { @@ -466,25 +454,30 @@ function suffixFormatter(val, axis) { } ``` -"labelWidth" and "labelHeight" specifies a fixed size of the tick -labels in pixels. They're useful in case you need to align several -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 -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. +"tickLength" is the length of the tick marks in pixels. By default the +innermost axes have ticks that extend across the plot, while any extra axes +use small ticks. A value of null means to use the default, while a number +means to use ticks of that length, where zero hides the marks completely. + +You can use "tickColor" to override the "color" option and give the tick +marks a different color from the axis line itself. + +"tickWidth" and "tickHeight" specify a fixed size for the tick labels, in +pixels. They're useful in case you need to assign several plots. + +You can use "tickFont" to give tick labels a different font style from the +axis default. It accepts exactly the same font-options object as "font" does. + +When using multiple axes, if you set "alignTicksWithAxis" to the number of +another axis, e.g. alignTicksWithAxis: 1, Flot will ensure that the generated +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 on both ends. The +trade-off is that the forced ticks won't necessarily be at natural places. + +"reserveSpace" indicates that Flot should reserve space for the axis even if +it isn't visible. This is useful in combination with labelWidth/labelHeight +for aligning multi-axis charts. ## Multiple axes ## diff --git a/jquery.flot.js b/jquery.flot.js index 3764c16..e0b7c7f 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -482,27 +482,36 @@ Licensed under the MIT license. sorted: null // default to no legend sorting }, xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis + + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + + color: null, // base color, labels, ticks + font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } + + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + + transform: null, // null or f: number -> number to transform axis inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - 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 - tickWidth: null, // size of tick labels in pixels - tickHeight: 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 - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] + + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + tickFormatter: null, // fn: number -> string + tickDecimals: null, // no. of decimals, null means auto + + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + tickLength: null, // size in pixels of ticks, or "full" for whole line + + tickWidth: null, // width of tick labels in pixels + tickHeight: null, // height of tick labels in pixels + tickFont: null, // null or font-spec object (see font, above) + + reserveSpace: null, // whether to reserve space even if axis isn't shown + alignTicksWithAxis: null // axis number or null for no sync }, yaxis: { autoscaleMargin: 0.02, From 5209b8cd7f90d8f65271c39b01fd18209a6a47a5 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Thu, 4 Jul 2013 23:36:52 -0400 Subject: [PATCH 5/8] Inline legacyStyles; it wasn't reused anyway. --- jquery.flot.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jquery.flot.js b/jquery.flot.js index e0b7c7f..ea05caf 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -1385,8 +1385,7 @@ Licensed under the MIT license. tickWidth = opts.tickWidth || opts.labelWidth || 0, tickHeight = opts.tickHeight || opts.labelHeight || 0, maxWidth = tickWidth || axis.direction === "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", font = opts.tickFont || "flot-tick-label tickLabel"; for (var i = 0; i < ticks.length; ++i) { @@ -2176,8 +2175,7 @@ Licensed under the MIT license. } var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", font = axis.options.tickFont || "flot-tick-label tickLabel", tick, x, y, halign, valign; From 97f2a127aecfa0ad384094a69a5e74d5092c9ce7 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Fri, 5 Jul 2013 10:41:52 -0400 Subject: [PATCH 6/8] Axes can now have basic horizontal text labels. Labels are provided via a 'label' option on the axis, and can be styled with the flot-axis-label class. The labelFont option works similarly to tickFont, as an override for the default font or the flot-axis-label class. The labelPadding option adds extra space between the axis and its label. Since most plots with axis labels currently use @markrcote's flot-axislabels plugin, we also support the axisLabel and axisLabelPadding options, and the axisLabels / axis[name]Label CSS classes, to make it as easy as possible to transition from that plugin. These are deprecated, and will be removed in 1.0. The implementation uses the internal text API introduced in 0.8. --- API.md | 4 ++ jquery.flot.js | 129 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 99 insertions(+), 34 deletions(-) diff --git a/API.md b/API.md index f82e3e2..f23f7cc 100644 --- a/API.md +++ b/API.md @@ -270,6 +270,10 @@ xaxis, yaxis: { tickHeight: null or number tickFont: null or font-options object + label: null or string + labelFont: null or font spec object + labelPadding: null or number + alignTicksWithAxis: null or number reserveSpace: null or true } diff --git a/jquery.flot.js b/jquery.flot.js index ea05caf..5bc01ba 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -510,12 +510,17 @@ Licensed under the MIT license. tickHeight: null, // height of tick labels in pixels tickFont: null, // null or font-spec object (see font, above) + label: null, // null or an axis label string + labelFont: null, // null or font-spec object (see font, above) + labelPadding: 2, // spacing between the axis and its label + reserveSpace: null, // whether to reserve space even if axis isn't shown alignTicksWithAxis: null // axis number or null for no sync }, yaxis: { + position: "left", // or "right" autoscaleMargin: 0.02, - position: "left" // or "right" + labelPadding: 2 }, xaxes: [], yaxes: [], @@ -742,6 +747,15 @@ Licensed under the MIT license. axisOptions.tickColor = axisOptions.color; } + // Compatibility with markrcote/flot-axislabels + + if (!axisOptions.label && axisOptions.axisLabel) { + axisOptions.label = axisOptions.axisLabel; + } + if (!axisOptions.labelPadding && axisOptions.axisLabelPadding) { + axisOptions.labelPadding = axisOptions.axisLabelPadding; + } + axisOptions = $.extend(true, {}, options.xaxis, axisOptions); options.xaxes[i] = axisOptions; @@ -752,6 +766,9 @@ Licensed under the MIT license. if (axisOptions.tickFont || axisOptions.font) { axisOptions.tickFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.tickFont); } + if (axisOptions.label && (axisOptions.labelFont || axisOptions.font)) { + axisOptions.labelFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.labelFont); + } } axisCount = options.yaxes.length || 1; @@ -762,6 +779,15 @@ Licensed under the MIT license. axisOptions.tickColor = axisOptions.color; } + // Compatibility with markrcote/flot-axislabels + + if (!axisOptions.label && axisOptions.axisLabel) { + axisOptions.label = axisOptions.axisLabel; + } + if (!axisOptions.labelPadding && axisOptions.axisLabelPadding) { + axisOptions.labelPadding = axisOptions.axisLabelPadding; + } + axisOptions = $.extend(true, {}, options.yaxis, axisOptions); options.yaxes[i] = axisOptions; @@ -772,6 +798,9 @@ Licensed under the MIT license. if (axisOptions.tickFont || axisOptions.font) { axisOptions.tickFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.tickFont); } + if (axisOptions.label && (axisOptions.labelFont || axisOptions.font)) { + axisOptions.labelFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.labelFont); + } } // backwards compatibility, to be removed in future @@ -1411,25 +1440,29 @@ Licensed under the MIT license. axis.labelHeight = axis.tickHeight; } + /////////////////////////////////////////////////////////////////////// + // Compute the axis bounding box based on the dimensions of its label + // and tick labels, then adjust the plotOffset to make room for it. + // + // This first phase only considers one dimension per axis; the other + // dimension depends on the other axes, and will be calculated later. + function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.tickWidth, - lh = axis.tickHeight, - pos = axis.options.position, - tickLength = axis.options.tickLength, + + var contentWidth = axis.tickWidth, + contentHeight = axis.tickHeight, + axisOptions = axis.options, + tickLength = axisOptions.tickLength, + axisPosition = axisOptions.position, axisMargin = options.grid.axisMargin, padding = options.grid.labelMargin, all = axis.direction === "x" ? xaxes : yaxes, innermost; - // determine axis margin - var samePosition = $.grep(all, function (a) { - return a && a.options.position === pos && a.reserveSpace; + // Determine the margin around the axis + + var samePosition = $.grep(all, function(axis) { + return axis && axis.options.position === axisPosition && axis.reserveSpace; }); if ($.inArray(axis, samePosition) === samePosition.length - 1) { axisMargin = 0; // outermost @@ -1439,7 +1472,7 @@ Licensed under the MIT license. innermost = $.inArray(axis, samePosition) === 0; - // determine tick length - if we're innermost, we can use "full" + // Determine the length of the tick marks if (tickLength == null) { if (innermost) { @@ -1453,31 +1486,39 @@ Licensed under the MIT license. padding += +tickLength; } - // compute box - if (axis.direction === "x") { - lh += padding; + // Measure the dimensions of the axis label, if it has one - if (pos === "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; + if (axisOptions.label) { + var layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", + font = axisOptions.labelFont || "flot-axis-label axisLabels " + axis.direction + axis.n + "axisLabel", + labelInfo = surface.getTextInfo(layer, axisOptions.label, font); + contentWidth += labelInfo.width + axisOptions.labelPadding; + contentHeight += labelInfo.height + axisOptions.labelPadding; + } + + // Compute the axis bounding box and update the plot bounds + + if (axis.direction === "x") { + contentHeight += padding; + if (axisPosition === "top") { + axis.box = { top: plotOffset.top + axisMargin, height: contentHeight }; + plotOffset.top += contentHeight + axisMargin; } else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; + plotOffset.bottom += contentHeight + axisMargin; + axis.box = { top: surface.height - plotOffset.bottom, height: contentHeight }; } } else { - lw += padding; - - if (pos === "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; + contentWidth += padding; + if (axisPosition === "right") { + plotOffset.right += contentWidth + axisMargin; + axis.box = { left: surface.width - plotOffset.right, width: contentWidth }; } else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; + axis.box = { left: plotOffset.left + axisMargin, width: contentWidth }; + plotOffset.left += contentWidth + axisMargin; } } - // save for future reference - axis.position = pos; + axis.position = axisPosition; axis.tickLength = tickLength; axis.box.padding = padding; axis.innermost = innermost; @@ -2175,12 +2216,32 @@ Licensed under the MIT license. } var box = axis.box, + axisOptions = axis.options, layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", - font = axis.options.tickFont || "flot-tick-label tickLabel", + labelFont = axisOptions.labelFont || "flot-axis-label axisLabels " + axis.direction + axis.n + "axisLabel", + tickFont = axisOptions.tickFont || "flot-tick-label tickLabel", tick, x, y, halign, valign; surface.removeText(layer); + if (axisOptions.label) { + if (axis.direction === "x") { + if (axisOptions.position === "top") { + surface.addText(layer, box.left + box.width / 2, box.top, axisOptions.label, labelFont, null, null, "center", "top"); + } else { + surface.addText(layer, box.left + box.width / 2, box.top + box.height, axisOptions.label, labelFont, null, null, "center", "bottom"); + } + } else { + if (axisOptions.position === "right") { + surface.addText(layer, box.left + box.width, box.top + box.height / 2, axisOptions.label, labelFont, null, null, "right", "middle"); + } else { + surface.addText(layer, box.left, box.top + box.height / 2, axisOptions.label, labelFont, null, null, "left", "middle"); + } + } + } + + // Add labels for the ticks on this axis + for (var i = 0; i < axis.ticks.length; ++i) { tick = axis.ticks[i]; @@ -2208,7 +2269,7 @@ Licensed under the MIT license. } } - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); + surface.addText(layer, x, y, tick.label, tickFont, null, null, halign, valign); } }); } From 6350cc67008bcd44bec12e8e3a2719c981efb769 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Fri, 12 Jul 2013 19:43:22 -0400 Subject: [PATCH 7/8] Added core support for rotated HTML text. --- jquery.flot.js | 204 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 43 deletions(-) diff --git a/jquery.flot.js b/jquery.flot.js index 5bc01ba..62f50e1 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -151,28 +151,33 @@ Licensed under the MIT license. for (var styleKey in layerCache) { if (Object.prototype.hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (Object.prototype.hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length === 0) { - delete styleCache[key]; - } + for (var angleKey in styleCache) { + if (Object.prototype.hasOwnProperty.call(styleCache, angleKey)) { + var angleCache = styleCache[angleKey]; + for (var key in angleCache) { + if (Object.prototype.hasOwnProperty.call(angleCache, key)) { + + var positions = angleCache[key].positions; + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + if (!position.rendered) { + layer.append(position.element); + position.rendered = true; + } + } else { + positions.splice(i--, 1); + if (position.rendered) { + position.element.detach(); + } + } + } + + if (positions.length === 0) { + delete angleCache[key]; + } + } + } } } } @@ -264,17 +269,15 @@ Licensed under the MIT license. // @param {(string|object)=} font Either a string of space-separated CSS // classes or a font-spec object, defining the text's font and style. // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. // @param {number=} width Maximum width of the text before it wraps. // @return {object} a text info object. Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such + var textStyle, layerCache, styleCache, angleCache, info; - text = "" + text; + text = "" + text; // Cast to string in case we have a number or such + angle = (360 + (angle || 0)) % 360; // Normalize the angle to 0...359 // If the font is a font-spec object, generate a CSS font definition @@ -284,21 +287,24 @@ Licensed under the MIT license. textStyle = font; } - // Retrieve (or create) the cache for the text's layer and styles + // Retrieve or create the caches for the text's layer, style, and angle layerCache = this._textCache[layer]; - if (layerCache == null) { layerCache = this._textCache[layer] = {}; } styleCache = layerCache[textStyle]; - if (styleCache == null) { styleCache = layerCache[textStyle] = {}; } - info = styleCache[text]; + angleCache = styleCache[angle]; + if (angleCache == null) { + angleCache = styleCache[angle] = {}; + } + + info = angleCache[text]; // If we can't find a matching element in our cache, create a new one @@ -321,9 +327,117 @@ Licensed under the MIT license. element.addClass(font); } - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), + // Save the original dimensions of the text; we'll modify these + // later to take into account rotation, if there is any. + + var textWidth = element.outerWidth(true), + textHeight = element.outerHeight(true); + + // Apply rotation to the text using CSS3/IE matrix transforms + + // Note how we also set the element's width, as a work-around for + // the way most browsers resize the div on rotate, which may cause + // the contents to wrap differently. The extra +1 is because IE + // rounds the width differently and needs a little extra help. + + if (angle) { + + var radians = angle * Math.PI / 180, + sin = Math.sin(radians), + cos = Math.cos(radians), + a = cos.toFixed(6), // Use fixed-point so these don't + b = (-sin).toFixed(6), // show up in scientific notation + c = sin.toFixed(6), // when we add them to the string + transformRule; + + if ($.support.leadingWhitespace) { + + // The transform origin defaults to '50% 50%', producing + // blurry text on some browsers (Chrome) when the width or + // height happens to be odd, making 50% fractional. Avoid + // this by setting the origin to rounded values. + + var cx = textWidth / 2, + cy = textHeight / 2, + transformOrigin = Math.floor(cx) + "px " + Math.floor(cy) + "px"; + + // Transforms alter the div's appearance without changing + // its origin. This will make it difficult to position it + // later, since we'll be positioning the new bounding box + // with respect to the old origin. We can work around this + // by adding a translation to align the new bounding box's + // top-left corner with the origin, using the same matrix. + + // Rather than examining all four points, we can use the + // angle to figure out in advance which two points are in + // the top-left quadrant; we can then use the x-coordinate + // of the first (left-most) point and the y-coordinate of + // the second (top-most) point as the bounding box corner. + + var x, y; + if (angle < 90) { + x = Math.floor(cx * cos + cy * sin - cx); + y = Math.floor(cx * sin + cy * cos - cy); + } else if (angle < 180) { + x = Math.floor(cy * sin - cx * cos - cx); + y = Math.floor(cx * sin - cy * cos - cy); + } else if (angle < 270) { + x = Math.floor(-cx * cos - cy * sin - cx); + y = Math.floor(-cx * sin - cy * cos - cy); + } else { + x = Math.floor(cx * cos - cy * sin - cx); + y = Math.floor(cy * cos - cx * sin - cy); + } + + transformRule = "matrix(" + a + "," + c + "," + b + "," + a + "," + x + "," + y + ")"; + + element.css({ + width: textWidth + 1, + transform: transformRule, + "-o-transform": transformRule, + "-ms-transform": transformRule, + "-moz-transform": transformRule, + "-webkit-transform": transformRule, + "transform-origin": transformOrigin, + "-o-transform-origin": transformOrigin, + "-ms-transform-origin": transformOrigin, + "-moz-transform-origin": transformOrigin, + "-webkit-transform-origin": transformOrigin + }); + + } else { + + // The IE7/8 matrix filter produces very ugly aliasing for + // text with a transparent background. Using a solid color + // greatly improves text clarity, although it does result + // in ugly boxes for plots using a non-white background. + + // TODO: Instead of white use the actual background color? + // This still wouldn't solve the problem when the plot has + // a gradient background, but it would at least help. + + transformRule = "progid:DXImageTransform.Microsoft.Matrix(M11=" + a + ", M12=" + b + ", M21=" + c + ", M22=" + a + ",sizingMethod='auto expand')"; + + element.css({ + width: textWidth + 1, + filter: transformRule, + "-ms-filter": transformRule, + "background-color": "#fff" + }); + } + + // Compute the final dimensions of the text's bounding box + + var ac = Math.abs(cos), + as = Math.abs(sin), + originalWidth = textWidth; + textWidth = Math.round(ac * textWidth + as * textHeight); + textHeight = Math.round(as * originalWidth + ac * textHeight); + } + + info = angleCache[text] = { + width: textWidth, + height: textHeight, element: element, positions: [] }; @@ -347,7 +461,6 @@ Licensed under the MIT license. // @param {(string|object)=} font Either a string of space-separated CSS // classes or a font-spec object, defining the text's font and style. // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. // @param {number=} width Maximum width of the text before it wraps. // @param {string=} halign Horizontal alignment of the text; either "left", // "center" or "right". @@ -435,14 +548,19 @@ Licensed under the MIT license. for (var styleKey in layerCache) { if (Object.prototype.hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (Object.prototype.hasOwnProperty.call(styleCache, key)) { - positions = styleCache[key].positions; - for (i = 0; position = positions[i]; i++) { - position.active = false; - } - } - } + for (var angleKey in styleCache) { + if (Object.prototype.hasOwnProperty.call(styleCache, angleKey)) { + var angleCache = styleCache[angleKey]; + for (var key in angleCache) { + if (Object.prototype.hasOwnProperty.call(angleCache, key)) { + positions = angleCache[key].positions; + for (i = 0; position = positions[i]; i++) { + position.active = false; + } + } + } + } + } } } } From 4d1042abf64b9b0acdf9edc270424197f0ab437a Mon Sep 17 00:00:00 2001 From: David Schnur Date: Fri, 12 Jul 2013 19:48:08 -0400 Subject: [PATCH 8/8] Rotate y-axis labels by 90 degrees. The labels are rotated counter-clockwise for left axes and clockwise for right axes. --- jquery.flot.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jquery.flot.js b/jquery.flot.js index 62f50e1..e5c4102 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -1609,7 +1609,8 @@ Licensed under the MIT license. if (axisOptions.label) { var layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", font = axisOptions.labelFont || "flot-axis-label axisLabels " + axis.direction + axis.n + "axisLabel", - labelInfo = surface.getTextInfo(layer, axisOptions.label, font); + angle = axis.direction === "x" ? 0 : axisOptions.position === "right" ? 90 : -90, + labelInfo = surface.getTextInfo(layer, axisOptions.label, font, angle); contentWidth += labelInfo.width + axisOptions.labelPadding; contentHeight += labelInfo.height + axisOptions.labelPadding; } @@ -2345,15 +2346,15 @@ Licensed under the MIT license. if (axisOptions.label) { if (axis.direction === "x") { if (axisOptions.position === "top") { - surface.addText(layer, box.left + box.width / 2, box.top, axisOptions.label, labelFont, null, null, "center", "top"); + surface.addText(layer, box.left + box.width / 2, box.top, axisOptions.label, labelFont, 0, null, "center", "top"); } else { - surface.addText(layer, box.left + box.width / 2, box.top + box.height, axisOptions.label, labelFont, null, null, "center", "bottom"); + surface.addText(layer, box.left + box.width / 2, box.top + box.height, axisOptions.label, labelFont, 0, null, "center", "bottom"); } } else { if (axisOptions.position === "right") { - surface.addText(layer, box.left + box.width, box.top + box.height / 2, axisOptions.label, labelFont, null, null, "right", "middle"); + surface.addText(layer, box.left + box.width, box.top + box.height / 2, axisOptions.label, labelFont, 90, null, "right", "middle"); } else { - surface.addText(layer, box.left, box.top + box.height / 2, axisOptions.label, labelFont, null, null, "left", "middle"); + surface.addText(layer, box.left, box.top + box.height / 2, axisOptions.label, labelFont, -90, null, "left", "middle"); } } }