diff --git a/API.md b/API.md
index df0cf24..edf77b4 100644
--- a/API.md
+++ b/API.md
@@ -275,11 +275,27 @@ you can also set the color of the ticks separately with "tickColor"
(otherwise it's autogenerated as the base color with some
transparency).
-You can customize the font used to draw the labels with CSS or
-directly with "font". The default value of null means that the font is
-read from the font style on the placeholder element (80% the size of
-that to be precise). If you set it directly with "font: { ... }", the
-format is like this:
+You can customize the font used to draw the 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.
+
+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
+are given the classes 'flot-text', 'flot-[x|y]-axis', and 'flot-[x|y]#-axis',
+where '#' is the number of the axis when there are multiple axes. For example,
+the x-axis labels for a simple plot with only one x-axis might look like this:
+
+```html
+
+
January 2013
+ ...
+
+```
+
+For direct control over label styles you can also provide "font" as an object
+with this format:
```js
{
@@ -287,7 +303,8 @@ format is like this:
style: "italic",
weight: "bold",
family: "sans-serif",
- variant: "small-caps"
+ variant: "small-caps",
+ color: "#545454"
}
```
diff --git a/NEWS.md b/NEWS.md
index 48c853d..2d33836 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -17,12 +17,26 @@ standard strftime specifiers, plus one nonstandard specifier for quarters.
Additionally, if a strftime function is found in the Date object's prototype,
it will be used instead of the built-in formatter.
-Axis labels are now drawn with canvas text with some parsing to support
-newlines. This solves various issues but also means that they no longer
-support HTML markup, can be accessed as DOM elements or styled directly with
-CSS. Some older browsers lack this function of the canvas API (this doesn't
-affect IE); if this is a problem, either continue using an older version of
-Flot or try an emulation helper such as canvas-text or Flashcanvas.
+Axis tick labels now use the class 'flot-tick-label' instead of 'tickLabel'.
+The text containers for each axis now use the classes 'flot-[x|y]-axis' and
+'flot-[x|y]#-axis' instead of '[x|y]Axis' and '[x|y]#Axis'. For compatibility
+with Flot 0.7 and earlier text will continue to use the old classes as well,
+but they are considered deprecated and will be removed in a future version.
+
+A new plugin, jquery.flot.canvas.js, allows axis tick labels to be rendered
+directly to the canvas, rather than using HTML elements. This feature can be
+toggled with a simple option, making it easy to create interactive plots in the
+browser using HTML, then re-render them to canvas for export as an image.
+
+The plugin tries to remain as faithful as possible to the original HTML render,
+and goes so far as to automatically extract styles from CSS, to avoid having to
+provide a separate set of styles when rendering to canvas. Due to limitations
+of the canvas text API, the plugin cannot reproduce certain features, including
+HTML markup embedded in labels, and advanced text styles such as 'em' units.
+
+The plugin requires support for canvas text, which may not be present in some
+older browsers, even if they support the canvas tag itself. To use the plugin
+with these browsers try using a shim such as canvas-text or FlashCanvas.
The base and overlay canvas are now using the CSS classes "flot-base" and
"flot-overlay" to prevent accidental clashes (issue 540).
@@ -43,7 +57,8 @@ The base and overlay canvas are now using the CSS classes "flot-base" and
- Display time series in different time zones. (patch by Knut Forkalsrud,
issue 141)
- - Canvas text support for labels. (sponsored by YCharts.com)
+ - Added a canvas plugin to enable rendering axis tick labels to the canvas.
+ (sponsored by YCharts.com, implementation by Ole Laursen and David Schnur)
- Support for setting the interval between redraws of the overlay canvas with
redrawOverlayInterval. (suggested in issue 185)
diff --git a/examples/basic-canvas.html b/examples/basic-canvas.html
new file mode 100644
index 0000000..d9610eb
--- /dev/null
+++ b/examples/basic-canvas.html
@@ -0,0 +1,39 @@
+
+
+
+
+ Flot Examples
+
+
+
+
+
+
+
+
Flot Examples
+
+
+
+
Simple example. You don't need to specify much to get an
+ attractive look. Put in a placeholder, make sure you set its
+ dimensions (otherwise the plot library will barf) and call the
+ plot function with the data. The axes are automatically
+ scaled.
+
+
+
+
+
diff --git a/jquery.flot.canvas.js b/jquery.flot.canvas.js
new file mode 100644
index 0000000..d4cf564
--- /dev/null
+++ b/jquery.flot.canvas.js
@@ -0,0 +1,333 @@
+/* Flot plugin for drawing all elements of a plot on the canvas.
+
+Copyright (c) 2007-2012 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Flot normally produces certain elements, like axis labels and the legend, using
+HTML elements. This permits greater interactivity and customization, and often
+looks better, due to cross-browser canvas text inconsistencies and limitations.
+
+It can also be desirable to render the plot entirely in canvas, particularly
+if the goal is to save it as an image, or if Flot is being used in a context
+where the HTML DOM does not exist, as is the case within Node.js. This plugin
+switches out Flot's standard drawing operations for canvas-only replacements.
+
+Currently the plugin supports only axis labels, but it will eventually allow
+every element of the plot to be rendered directly to canvas.
+
+The plugin supports these options:
+
+{
+ canvas: boolean
+}
+
+The "canvas" option controls whether full canvas drawing is enabled, making it
+possible to toggle on and off. This is useful when a plot uses HTML text in the
+browser, but needs to redraw with canvas text when exporting as an image.
+
+*/
+
+(function($) {
+
+ var options = {
+ canvas: true
+ };
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ function init(plot, classes) {
+
+ var Canvas = classes.Canvas,
+ getTextInfo = Canvas.prototype.getTextInfo,
+ addText = Canvas.prototype.addText,
+ render = Canvas.prototype.render;
+
+ // Finishes rendering the canvas, including overlaid text
+
+ Canvas.prototype.render = function() {
+
+ if (!plot.getOptions().canvas) {
+ return render.call(this);
+ }
+
+ var context = this.context,
+ cache = this._textCache;
+
+ // For each text layer, render elements marked as active
+
+ context.save();
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+ var layerCache = cache[layerKey];
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey],
+ updateStyles = true;
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var info = styleCache[key];
+
+ if (!info.active) {
+ delete styleCache[key];
+ continue;
+ }
+
+ var x = info.x,
+ y = info.y,
+ lines = info.lines,
+ halign = info.halign;
+
+ // Since every element at this level of the cache have the
+ // same font and fill styles, we can just change them once
+ // using the values from the first element.
+
+ if (updateStyles) {
+ context.fillStyle = info.font.color;
+ context.font = info.font.definition;
+ updateStyles = false;
+ }
+
+ // TODO: Comments in Ole's implementation indicate that
+ // some browsers differ in their interpretation of 'top';
+ // so far I don't see this, but it requires more testing.
+ // We'll stick with top until this can be verified.
+
+ // Original comment was:
+ // Top alignment would be more natural, but browsers can
+ // differ a pixel or two in where they consider the top to
+ // be, so instead we middle align to minimize variation
+ // between browsers and compensate when calculating the
+ // coordinates.
+
+ context.textBaseline = "top";
+
+ for (var i = 0; i < lines.length; ++i) {
+
+ var line = lines[i],
+ linex = x;
+
+ // Apply horizontal alignment per-line
+
+ if (halign == "center") {
+ linex -= line.width / 2;
+ } else if (halign == "right") {
+ linex -= line.width;
+ }
+
+ // FIXME: LEGACY BROWSER FIX
+ // AFFECTS: Opera < 12.00
+
+ // Round the coordinates, since Opera otherwise
+ // switches to uglier (probably non-hinted) rendering.
+ // Also offset the y coordinate, since Opera is off
+ // pretty consistently compared to the other browsers.
+
+ if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
+ linex = Math.floor(linex);
+ y = Math.ceil(y - 2);
+ }
+
+ context.fillText(line.text, linex, y);
+ y += line.height;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ context.restore();
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // 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.
+ // lines: [{
+ // height: Height of this line.
+ // widths: Width of this line.
+ // text: Text on this line.
+ // }],
+ // font: {
+ // definition: Canvas font property string.
+ // color: Color of the text.
+ // },
+ // }
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle) {
+
+ if (!plot.getOptions().canvas) {
+ return getTextInfo.call(this, layer, text, font, angle);
+ }
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ if (info == null) {
+
+ var context = this.context;
+
+ // If the font was provided as CSS, create a div with those
+ // classes and examine it to generate a canvas font spec.
+
+ if (typeof font !== "object") {
+
+ var element = $("").html(text)
+ .addClass(typeof font === "string" ? font : null)
+ .css({
+ position: "absolute",
+ top: -9999
+ })
+ .appendTo(this.getTextLayer(layer));
+
+ font = {
+ style: element.css("font-style"),
+ variant: element.css("font-variant"),
+ weight: element.css("font-weight"),
+ size: parseInt(element.css("font-size"), 10),
+ family: element.css("font-family"),
+ color: element.css("color")
+ };
+
+ element.remove();
+ }
+
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+
+ // Create a new info object, initializing the dimensions to
+ // zero so we can count them up line-by-line.
+
+ info = styleCache[text] = {
+ x: null,
+ y: null,
+ width: 0,
+ height: 0,
+ active: false,
+ lines: [],
+ font: {
+ definition: textStyle,
+ color: font.color
+ }
+ };
+
+ context.save();
+ context.font = textStyle;
+
+ // Canvas can't handle multi-line strings; break on various
+ // newlines, including HTML brs, to build a list of lines.
+ // Note that we could split directly on regexps, but IE < 9 is
+ // broken; revisit when we drop IE 7/8 support.
+
+ var lines = (text + "").replace(/ |\r\n|\r/g, "\n").split("\n");
+
+ for (var i = 0; i < lines.length; ++i) {
+
+ var lineText = lines[i],
+ measured = context.measureText(lineText),
+ lineWidth, lineHeight;
+
+ lineWidth = measured.width;
+
+ // Height might not be defined; not in the standard yet
+
+ lineHeight = measured.height || font.size;
+
+ // Add a bit of margin since font rendering is not pixel
+ // perfect and cut off letters look bad. This also doubles
+ // as spacing between lines.
+
+ lineHeight += Math.round(font.size * 0.15);
+
+ info.width = Math.max(lineWidth, info.width);
+ info.height += lineHeight;
+
+ info.lines.push({
+ text: lineText,
+ width: lineWidth,
+ height: lineHeight
+ });
+ }
+
+ context.restore();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, halign, valign) {
+
+ if (!plot.getOptions().canvas) {
+ return addText.call(this, layer, x, y, text, font, angle, halign, valign);
+ }
+
+ var info = this.getTextInfo(layer, text, font, angle);
+
+ info.x = x;
+ info.y = y;
+
+ // Mark the text for inclusion in the next render pass
+
+ info.active = true;
+
+ // Save horizontal alignment for later; we'll apply it per-line
+
+ info.halign = halign;
+
+ // Tweak the initial y-position to match vertical alignment
+
+ if (valign == "middle") {
+ info.y = y - info.height / 2;
+ } else if (valign == "bottom") {
+ info.y = y - info.height;
+ }
+ };
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "canvas",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/jquery.flot.js b/jquery.flot.js
index c41b531..661007a 100644
--- a/jquery.flot.js
+++ b/jquery.flot.js
@@ -33,6 +33,388 @@ Licensed under the MIT license.
// the actual Flot code
(function($) {
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ // Add default styles for tick labels and other text
+
+ var STYLES = [
+ ".flot-tick-label {font-size:smaller;color:#545454;}"
+ ];
+
+ $(function() {
+ $("head").prepend("");
+ });
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The Canvas object is a wrapper around an HTML5