diff --git a/jquery.flot.canvas.js b/jquery.flot.canvas.js
index 8a00683..36e1873 100644
--- a/jquery.flot.canvas.js
+++ b/jquery.flot.canvas.js
@@ -17,45 +17,249 @@ every element of the plot to be rendered directly to canvas.
The plugin supports these options:
- canvas: boolean,
- xaxis, yaxis: {
- font: null or font spec object
- }
+{
+ canvas: boolean
+}
-The top-level "canvas" option controls whether full canvas drawing is enabled,
-making it easy to toggle on and off.
-
-By default the plugin extracts font settings from the same CSS styles that the
-default HTML text implementation uses. If *.tickLabel* has a *font-size* of
-20px, then the canvas text will be drawn at the same size.
-
-One can also use the "font" option to control these properties directly. The
-format of the font spec object is as follows:
-
- {
- size: 11,
- style: "italic",
- weight: "bold",
- family: "sans-serif",
- variant: "small-caps"
- }
+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,
- xaxis: {
- font: null
- },
- yaxis: {
- font: null
- }
+ canvas: true
};
- function init(plot) {
+ function init(plot, classes) {
+
+ var Canvas = classes.Canvas,
+ getTextInfo = Canvas.prototype.getTextInfo,
+ drawText = Canvas.prototype.drawText;
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // When the canvas option is set, this override returns an object
+ // that looks like this:
+ //
+ // {
+ // lines: {
+ // height: Height of each line in the text.
+ // widths: List of widths for each line in the text.
+ // texts: List of lines in the text.
+ // },
+ // font: {
+ // definition: Canvas font property string.
+ // color: Color of the text.
+ // },
+ // dimensions: {
+ // width: Width of the text's bounding box.
+ // height: Height of the text's bounding box.
+ // }
+ // }
+
+ Canvas.prototype.getTextInfo = function(text, font, angle) {
+ if (plot.getOptions().canvas) {
+
+ var textStyle, cacheKey, 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;
+ }
+
+ // The text + style + angle uniquely identify the text's
+ // dimensions and content; we'll use them to build this entry's
+ // text cache key.
+
+ cacheKey = text + "-" + textStyle + "-" + angle;
+
+ info = this._textCache[cacheKey] || this._activeTextCache[cacheKey];
+
+ 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;
+ if (typeof font === "string") {
+ element = $("
" + text + "
")
+ .appendTo(this.container);
+ } else {
+ element = $("" + text + "
")
+ .appendTo(this.container);
+ }
+
+ font = {
+ style: element.css("font-style"),
+ variant: element.css("font-variant"),
+ weight: element.css("font-weight"),
+ size: parseInt(element.css("font-size")),
+ family: element.css("font-family"),
+ color: element.css("color")
+ };
+
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+
+ element.remove();
+ }
+
+ // Create a new info object, initializing the dimensions to
+ // zero so we can count them up line-by-line.
+
+ info = {
+ lines: [],
+ font: {
+ definition: textStyle,
+ color: font.color
+ },
+ dimensions: {
+ width: 0,
+ height: 0
+ }
+ };
+
+ 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.dimensions.width = Math.max(lineWidth, info.dimensions.width);
+ info.dimensions.height += lineHeight;
+
+ info.lines.push({
+ text: lineText,
+ width: lineWidth,
+ height: lineHeight
+ });
+ }
+
+ context.restore;
+ }
+
+ // Save the entry to the 'hot' text cache, marking it as active
+ // and preserving it for the next render pass.
+
+ this._activeTextCache[cacheKey] = info;
+
+ return info;
+
+ } else {
+ return getTextInfo.call(this, text, font, angle);
+ }
+ }
+
+ // Draws a text string onto the canvas.
+ //
+ // When the canvas option is set, this override draws directly to the
+ // canvas using fillText.
+
+ Canvas.prototype.drawText = function(x, y, text, font, angle, halign, valign) {
+ if (plot.getOptions().canvas) {
+
+ var info = this.getTextInfo(text, font, angle),
+ dimensions = info.dimensions,
+ context = this.context,
+ lines = info.lines;
+
+ // Apply alignment to the vertical position of the entire text
+
+ if (valign == "middle") {
+ y -= dimensions.height / 2;
+ } else if (valign == "bottom") {
+ y -= dimensions.height;
+ }
+
+ context.save();
+
+ context.fillStyle = info.font.color;
+ context.font = info.font.definition;
+
+ // 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 alignment to the horizontal position 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 more ugly rendering (probably
+ // non-hinted) and offset the y coordinates since
+ // it seems to be 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();
+
+ } else {
+ drawText.call(this, x, y, text, font, angle, halign, valign);
+ }
+ }
}
$.plot.plugins.push({
diff --git a/jquery.flot.js b/jquery.flot.js
index d6f3306..6571598 100644
--- a/jquery.flot.js
+++ b/jquery.flot.js
@@ -34,6 +34,12 @@ Licensed under the MIT license.
// the actual Flot code
(function($) {
+ // Add default styles for tick labels and other text
+
+ $(function() {
+ $("head").prepend("");
+ });
+
///////////////////////////////////////////////////////////////////////////
// The Canvas object is a wrapper around an HTML5