You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
7.5 KiB
JavaScript
273 lines
7.5 KiB
JavaScript
/* 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
|
|
};
|
|
|
|
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 = $("<div class='" + font + "'>" + text + "</div>")
|
|
.appendTo(this.container);
|
|
} else {
|
|
element = $("<div>" + text + "</div>")
|
|
.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(/<br ?\/?>|\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({
|
|
init: init,
|
|
options: options,
|
|
name: "canvas",
|
|
version: "1.0"
|
|
});
|
|
|
|
})(jQuery);
|