diff --git a/API.md b/API.md index 342b49e..3d13c1a 100644 --- a/API.md +++ b/API.md @@ -8,7 +8,7 @@ var plot = $.plot(placeholder, data, options) The placeholder is a jQuery object or DOM element or jQuery expression that the plot will be put into. This placeholder needs to have its -width and height set as explained in the README (go read that now if +width and height set as explained in the [README](README.md) (go read that now if you haven't, it's short). The plot will modify some properties of the placeholder so it's recommended you simply pass in a div that you don't use for anything else. Make sure you check any fancy styling @@ -636,6 +636,10 @@ standard strftime specifiers are supported (plus the nonstandard %q): %w: weekday as number (0-6, 0 being Sunday) ``` +Flot 0.8 switched from %h to the standard %H hours specifier. The %h specifier +is still available, for backwards-compatibility, but is deprecated and +scheduled to be removed permanently with the release of version 1.0. + You can customize the month names with the "monthNames" option. For instance, for Danish you might specify: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4eafe1..eef971b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,11 @@ To make merging as easy as possible, please keep these rules in mind: 2. Format your code according to the style guidelines below. - 3. Rebase against master, if necessary, before submitting your pull request. + 3. Submit new features or architectural changes to the -work branch + for the next major release. Submit bug fixes to the master branch. + + 4. Rebase, if necessary, before submitting your pull request, to reduce the + work we need to do to merge it. ### Flot Style Guidelines ### diff --git a/NEWS.md b/NEWS.md index 37a8d14..5697078 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,52 @@ - Added a strokeColor option to control the outline around point symbols. (patch by djamshed, pull request #1003) +## Flot 0.8.1 ## + +### Bug fixes ### + + - Fixed a regression in the time plugin, introduced in 0.8, that caused dates + to align to the minute rather than to the highest appropriate unit. This + caused many x-axes in 0.8 to have different ticks than they did in 0.7. + (reported by Tom Sheppard, patch by Daniel Shapiro, issue #1017, pull + request #1023) + + - Fixed a regression in text rendering, introduced in 0.8, that caused axis + labels with the same text as another label on the same axis to disappear. + More generally, it's again possible to have the same text in two locations. + (issue #1032) + + - Fixed a regression in text rendering, introduced in 0.8, where axis labels + were no longer assigned an explicit width, and their text could not wrap. + (reported by sabregreen, issue #1019) + + - Fixed a regression in the pie plugin, introduced in 0.8, that prevented it + from accepting data in the format '[[x, y]]'. + (patch by Nicolas Morel, pull request #1024) + + - The 'zero' series option and 'autoscale' format option are no longer + ignored when the series contains a null value. + (reported by Daniel Shapiro, issue #1033) + + - Avoid triggering the time-mode plugin exception when there are zero series. + (reported by Daniel Rothig, patch by Mark Raymond, issue #1016) + + - When a custom color palette has fewer colors than the default palette, Flot + no longer fills out the colors with the remainder of the default. + (patch by goorpy, issue #1031, pull request #1034) + + - Fixed missing update for bar highlights after a zoom or other redraw. + (reported by Paolo Valleri, issue #1030) + + - Fixed compatibility with jQuery versions earlier than 1.7. + (patch by Lee Willis, issue #1027, pull request #1027) + + - The mouse wheel no longer scrolls the page when using the navigate plugin. + (patch by vird, pull request #1020) + + - Fixed missing semicolons in the core library. + (reported by Michal Zglinski) + ## Flot 0.8.0 ## diff --git a/README.md b/README.md index e5b922a..4de7bde 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ $.plot($("#placeholder"), data, options); Here, data is an array of data series and options is an object with settings if you want to customize the plot. Take a look at the -examples for some ideas of what to put in or look at the reference -in the file `API.txt`. Here's a quick example that'll draw a line from -(0, 0) to (1, 1): +examples for some ideas of what to put in or look at the +[API reference](API.md). Here's a quick example that'll draw a line +from (0, 0) to (1, 1): ```js $.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); diff --git a/jquery.flot.canvas.js b/jquery.flot.canvas.js index 392c554..d94b961 100644 --- a/jquery.flot.canvas.js +++ b/jquery.flot.canvas.js @@ -79,12 +79,9 @@ browser, but needs to redraw with canvas text when exporting as an image. for (var key in styleCache) { if (hasOwnProperty.call(styleCache, key)) { - var info = styleCache[key]; - - if (!info.active) { - delete styleCache[key]; - continue; - } + var info = styleCache[key], + positions = info.positions, + lines = info.lines; // Since every element at this level of the cache have the // same font and fill styles, we can just change them once @@ -96,10 +93,18 @@ browser, but needs to redraw with canvas text when exporting as an image. updateStyles = false; } - var lines = info.lines; - for (var i = 0; i < lines.length; ++i) { - var line = lines[i]; - context.fillText(line.text, line.x, line.y); + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + for (var j = 0, line; line = position.lines[j]; j++) { + context.fillText(lines[j].text, line[0], line[1]); + } + } else { + positions.splice(i--, 1); + } + } + + if (positions.length == 0) { + delete styleCache[key]; } } } @@ -116,11 +121,9 @@ browser, but needs to redraw with canvas text when exporting as an image. // When the canvas option is set, the object looks like this: // // { - // x: X coordinate at which the text is located. - // x: Y coordinate at which the text is located. // width: Width of the text's bounding box. // height: Height of the text's bounding box. - // active: Flag indicating whether the text should be visible. + // positions: Array of positions at which this text is drawn. // lines: [{ // height: Height of this line. // widths: Width of this line. @@ -131,11 +134,20 @@ browser, but needs to redraw with canvas text when exporting as an image. // color: Color of the text. // }, // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // lines: Array of [x, y] coordinates at which to draw the line. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } - Canvas.prototype.getTextInfo = function(layer, text, font, angle) { + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { if (!plot.getOptions().canvas) { - return getTextInfo.call(this, layer, text, font, angle); + return getTextInfo.call(this, layer, text, font, angle, width); } var textStyle, layerCache, styleCache, info; @@ -210,7 +222,7 @@ browser, but needs to redraw with canvas text when exporting as an image. info = styleCache[text] = { width: 0, height: 0, - active: false, + positions: [], lines: [], font: { definition: textStyle, @@ -251,19 +263,16 @@ browser, but needs to redraw with canvas text when exporting as an image. // Adds a text string to the canvas text overlay. - Canvas.prototype.addText = function(layer, x, y, text, font, angle, halign, valign) { + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { if (!plot.getOptions().canvas) { - return addText.call(this, layer, x, y, text, font, angle, halign, valign); + return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); } - var info = this.getTextInfo(layer, text, font, angle), + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions, lines = info.lines; - // Mark the text for inclusion in the next render pass - - info.active = true; - // Text is drawn with baseline 'middle', which we need to account // for by adding half a line's height to the y position. @@ -289,20 +298,39 @@ browser, but needs to redraw with canvas text when exporting as an image. y -= 2; } + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + position = { + active: true, + lines: [], + x: x, + y: y + }; + + positions.push(position); + // Fill in the x & y positions of each line, adjusting them // individually for horizontal alignment. - for (var i = 0; i < lines.length; ++i) { - var line = lines[i]; - line.y = y; - y += line.height; + for (var i = 0, line; line = lines[i]; i++) { if (halign == "center") { - line.x = Math.round(x - line.width / 2); + position.lines.push([Math.round(x - line.width / 2), y]); } else if (halign == "right") { - line.x = Math.round(x - line.width); + position.lines.push([Math.round(x - line.width), y]); } else { - line.x = Math.round(x); + position.lines.push([Math.round(x), y]); } + y += line.height; } }; } diff --git a/jquery.flot.js b/jquery.flot.js index 79d4c1d..ad2ef5c 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -184,17 +184,25 @@ Licensed under the MIT license. var styleCache = layerCache[styleKey]; for (var key in styleCache) { if (hasOwnProperty.call(styleCache, key)) { - var info = styleCache[key]; - if (info.active) { - if (!info.rendered) { - layer.append(info.element); - info.rendered = true; + + 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(); + } } - } else { + } + + if (positions.length == 0) { delete styleCache[key]; - if (info.rendered) { - info.element.detach(); - } } } } @@ -258,11 +266,26 @@ Licensed under the MIT license. // { // width: Width of the text's wrapper div. // height: Height of the text's wrapper div. + // element: The jQuery-wrapped HTML div containing the text. + // positions: Array of positions at which this text is drawn. + // } + // + // The positions array contains objects that look like this: + // + // { // active: Flag indicating whether the text should be visible. // rendered: Flag indicating whether the text is currently visible. // element: The jQuery-wrapped HTML div containing the text. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. // } // + // Each position after the first receives a clone of the original element. + // + // The idea is that that the width, height, and general 'identity' of the + // text is constant no matter where it is placed; the placements are a + // secondary property. + // // Canvas maintains a cache of recently-used text info objects; getTextInfo // either returns the cached element or creates a new entry. // @@ -273,9 +296,10 @@ Licensed under the MIT license. // 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) { + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { var textStyle, layerCache, styleCache, info; @@ -314,6 +338,7 @@ Licensed under the MIT license. var element = $("
").html(text) .css({ position: "absolute", + 'max-width': width, top: -9999 }) .appendTo(this.getTextLayer(layer)); @@ -328,11 +353,10 @@ Licensed under the MIT license. } info = styleCache[text] = { - active: false, - rendered: false, - element: element, width: element.outerWidth(true), - height: element.outerHeight(true) + height: element.outerHeight(true), + element: element, + positions: [] }; element.detach(); @@ -355,18 +379,16 @@ Licensed under the MIT license. // 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". // @param {string=} valign Vertical alignment of the text; either "top", // "middle" or "bottom". - Canvas.prototype.addText = function(layer, x, y, text, font, angle, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle); - - // Mark the div for inclusion in the next render pass + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - info.active = true; + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions; // Tweak the div's position to match the text's alignment @@ -382,45 +404,85 @@ Licensed under the MIT license. y -= info.height; } + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + // For the very first position we'll re-use the original element, + // while for subsequent ones we'll clone it. + + position = { + active: true, + rendered: false, + element: positions.length ? info.element.clone() : info.element, + x: x, + y: y + } + + positions.push(position); + // Move the element to its final position within the container - info.element.css({ + position.element.css({ top: Math.round(y), - left: Math.round(x) + left: Math.round(x), + 'text-align': halign // In case the text wraps }); }; // Removes one or more text strings from the canvas text overlay. // // If no parameters are given, all text within the layer is removed. - // The text is not actually removed; it is simply marked as inactive, which - // will result in its removal on the next render pass. + // + // Note that the text is not immediately removed; it is simply marked as + // inactive, which will result in its removal on the next render pass. + // This avoids the performance penalty for 'clear and redraw' behavior, + // where we potentially get rid of all text on a layer, but will likely + // add back most or all of it later, as when redrawing axes, for example. // // @param {string} layer A string of space-separated CSS classes uniquely // identifying the layer containing this text. - // @param {string} text Text string to remove. + // @param {number=} x X coordinate of the text. + // @param {number=} y Y coordinate of the text. + // @param {string=} text Text string to remove. // @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 the text is rotated, in degrees. // Angle is currently unused, it will be implemented in the future. - Canvas.prototype.removeText = function(layer, text, font, angle) { + Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { if (text == null) { var layerCache = this._textCache[layer]; if (layerCache != null) { for (var styleKey in layerCache) { if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey] + var styleCache = layerCache[styleKey]; for (var key in styleCache) { if (hasOwnProperty.call(styleCache, key)) { - styleCache[key].active = false; + var positions = styleCache[key].positions; + for (var i = 0, position; position = positions[i]; i++) { + position.active = false; + } } } } } } } else { - this.getTextInfo(layer, text, font, angle).active = false; + var positions = this.getTextInfo(layer, text, font, angle).positions; + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = false; + } + } } }; @@ -643,6 +705,15 @@ Licensed under the MIT license. $.extend(true, options, opts); + // $.extend merges arrays, rather than replacing them. When less + // colors are provided than the size of the default palette, we + // end up with those colors plus the remaining defaults, which is + // not expected behavior; avoid it by replacing them here. + + if (opts && opts.colors) { + options.colors = opts.colors; + } + if (options.xaxis.color == null) options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); if (options.yaxis.color == null) @@ -1061,10 +1132,14 @@ Licensed under the MIT license. if (val != null) { f = format[m]; // extract min/max info - if (f.x) - updateAxis(s.xaxis, val, val); - if (f.y) - updateAxis(s.yaxis, val, val); + if (f.autoscale) { + if (f.x) { + updateAxis(s.xaxis, val, val); + } + if (f.y) { + updateAxis(s.yaxis, val, val); + } + } } points[k + m] = null; } @@ -1101,7 +1176,7 @@ Licensed under the MIT license. // second pass: find datamax/datamin for auto-scaling for (i = 0; i < series.length; ++i) { s = series[i]; - points = s.datapoints.points, + points = s.datapoints.points; ps = s.datapoints.pointsize; format = s.datapoints.format; @@ -1272,8 +1347,11 @@ Licensed under the MIT license. function measureTickLabels(axis) { - var opts = axis.options, ticks = axis.ticks || [], - axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0, + 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"; @@ -1285,16 +1363,14 @@ Licensed under the MIT license. if (!t.label) continue; - var info = surface.getTextInfo(layer, t.label, font); + var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - if (opts.labelWidth == null) - axisw = Math.max(axisw, info.width); - if (opts.labelHeight == null) - axish = Math.max(axish, info.height); + labelWidth = Math.max(labelWidth, info.width); + labelHeight = Math.max(labelHeight, info.height); } - axis.labelWidth = Math.ceil(axisw); - axis.labelHeight = Math.ceil(axish); + axis.labelWidth = opts.labelWidth || labelWidth; + axis.labelHeight = opts.labelHeight || labelHeight; } function allocateAxisBoxFirstPhase(axis) { @@ -1741,6 +1817,11 @@ Licensed under the MIT license. } surface.render(); + + // A draw implies that either the axes or data have changed, so we + // should probably update the overlay highlights as well. + + triggerRedrawOverlay(); } function extractRange(ranges, coord) { @@ -2057,7 +2138,7 @@ Licensed under the MIT license. } } - surface.addText(layer, x, y, tick.label, font, null, halign, valign); + surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); } }); } @@ -2971,7 +3052,7 @@ Licensed under the MIT license. return this.each(function() { $.plot(this, data, options); }); - } + }; // round to nearby lower multiple of base function floorInBase(n, base) { diff --git a/jquery.flot.navigate.js b/jquery.flot.navigate.js index b5949b4..10256b8 100644 --- a/jquery.flot.navigate.js +++ b/jquery.flot.navigate.js @@ -135,6 +135,7 @@ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-L } function onMouseWheel(e, delta) { + e.preventDefault(); onZoomClick(e, delta < 0); return false; } diff --git a/jquery.flot.pie.js b/jquery.flot.pie.js index ebff1a3..6553c8e 100644 --- a/jquery.flot.pie.js +++ b/jquery.flot.pie.js @@ -180,13 +180,18 @@ More detail and specific examples can be found in the included HTML file. // new one; this is more efficient and preserves any extra data // that the user may have stored in higher indexes. + if ($.isArray(value) && value.length == 1) { + value = value[0]; + } + if ($.isArray(value)) { - if ($.isNumeric(value[1])) { + // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 + if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { value[1] = +value[1]; } else { value[1] = 0; } - } else if ($.isNumeric(value)) { + } else if (!isNaN(parseFloat(value)) && isFinite(value)) { value = [1, +value]; } else { value = [1, 0]; diff --git a/jquery.flot.time.js b/jquery.flot.time.js index 3d3360b..15f5281 100644 --- a/jquery.flot.time.js +++ b/jquery.flot.time.js @@ -73,6 +73,7 @@ API.txt for details. case 'b': c = "" + monthNames[d.getMonth()]; break; case 'd': c = leftPad(d.getDate()); break; case 'e': c = leftPad(d.getDate(), " "); break; + case 'h': // For back-compat with 0.7; remove in 1.0 case 'H': c = leftPad(hours); break; case 'I': c = leftPad(hours12); break; case 'l': c = leftPad(hours12, " "); break; @@ -194,7 +195,7 @@ API.txt for details. [1, "year"]]); function init(plot) { - plot.hooks.processDatapoints.push(function (plot, series, datapoints) { + plot.hooks.processOptions.push(function (plot, options) { $.each(plot.getAxes(), function(axisName, axis) { var opts = axis.options; @@ -294,17 +295,23 @@ API.txt for details. if (step >= timeUnitSize.minute) { d.setSeconds(0); - } else if (step >= timeUnitSize.hour) { + } + if (step >= timeUnitSize.hour) { d.setMinutes(0); - } else if (step >= timeUnitSize.day) { + } + if (step >= timeUnitSize.day) { d.setHours(0); - } else if (step >= timeUnitSize.day * 4) { + } + if (step >= timeUnitSize.day * 4) { d.setDate(1); - } else if (step >= timeUnitSize.month * 2) { + } + if (step >= timeUnitSize.month * 2) { d.setMonth(floorInBase(d.getMonth(), 3)); - } else if (step >= timeUnitSize.quarter * 2) { + } + if (step >= timeUnitSize.quarter * 2) { d.setMonth(floorInBase(d.getMonth(), 6)); - } else if (step >= timeUnitSize.year) { + } + if (step >= timeUnitSize.year) { d.setMonth(0); }