diff --git a/API.md b/API.md index e0d6d55..f23f7cc 100644 --- a/API.md +++ b/API.md @@ -245,60 +245,63 @@ 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 - tickColor: 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 - labelWidth: null or number - labelHeight: null or number - reserveSpace: null or true - + tickColor: null or color spec tickLength: null or number + tickWidth: null or number + 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 } ``` -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 @@ -313,7 +316,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 @@ -331,28 +334,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: { @@ -361,8 +361,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: { @@ -371,38 +370,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] @@ -416,11 +411,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) { @@ -434,13 +428,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) { @@ -448,11 +442,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) { @@ -465,25 +458,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 acc535f..e5c4102 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; + var textStyle, layerCache, styleCache, angleCache, info; - // Cast the value to a string, in case we were given a number or such - - 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; + } + } + } + } + } } } } @@ -482,31 +600,45 @@ 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 - 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 - 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) + + 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: [], @@ -733,14 +865,27 @@ 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; + 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); + } + if (axisOptions.label && (axisOptions.labelFont || axisOptions.font)) { + axisOptions.labelFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.labelFont); } } @@ -752,14 +897,27 @@ 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; + 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); + } + if (axisOptions.label && (axisOptions.labelFont || axisOptions.font)) { + axisOptions.labelFont = $.extend({}, axisOptions.font || fontDefaults, axisOptions.labelFont); } } @@ -1370,12 +1528,12 @@ 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, - 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"; + // 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, + 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) { @@ -1387,33 +1545,42 @@ 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; } + /////////////////////////////////////////////////////////////////////// + // 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.labelWidth, - lh = axis.labelHeight, - 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 @@ -1423,7 +1590,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) { @@ -1437,31 +1604,40 @@ 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 (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", + 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; + } - if (pos === "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; + // 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; @@ -1471,11 +1647,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 +1680,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 +1737,6 @@ Licensed under the MIT license. setupTickGeneration(axis); setTicks(axis); snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis measureTickLabels(axis); }); @@ -2160,13 +2335,32 @@ 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", + axisOptions = axis.options, + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + axis.direction + "Axis " + axis.direction + axis.n + "Axis", + 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, 0, null, "center", "top"); + } else { + 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, 90, null, "right", "middle"); + } else { + surface.addText(layer, box.left, box.top + box.height / 2, axisOptions.label, labelFont, -90, null, "left", "middle"); + } + } + } + + // Add labels for the ticks on this axis + for (var i = 0; i < axis.ticks.length; ++i) { tick = axis.ticks[i]; @@ -2194,7 +2388,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); } }); }