Commit 60ed6b29 authored by David Schnur's avatar David Schnur

Merge pull request #935 from dnschnur/canvas-text

Moved canvas text support into a plugin.
parents 82ab0c46 39698d38
...@@ -275,11 +275,27 @@ you can also set the color of the ticks separately with "tickColor" ...@@ -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 (otherwise it's autogenerated as the base color with some
transparency). transparency).
You can customize the font used to draw the labels with CSS or You can customize the font used to draw the labels with CSS or directly via the
directly with "font". The default value of null means that the font is "font" option. When "font" is null - the default - each tick label is given the
read from the font style on the placeholder element (80% the size of 'flot-tick-label' class. For compatibility with Flot 0.7 and earlier the labels
that to be precise). If you set it directly with "font: { ... }", the are also given the 'tickLabel' class, but this is deprecated and scheduled to
format is like this: 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
<div class='flot-text flot-x-axis flot-x1-axis'>
<div class='flot-tick-label'>January 2013</div>
...
</div>
```
For direct control over label styles you can also provide "font" as an object
with this format:
```js ```js
{ {
...@@ -287,7 +303,8 @@ format is like this: ...@@ -287,7 +303,8 @@ format is like this:
style: "italic", style: "italic",
weight: "bold", weight: "bold",
family: "sans-serif", family: "sans-serif",
variant: "small-caps" variant: "small-caps",
color: "#545454"
} }
``` ```
......
...@@ -17,12 +17,26 @@ standard strftime specifiers, plus one nonstandard specifier for quarters. ...@@ -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, Additionally, if a strftime function is found in the Date object's prototype,
it will be used instead of the built-in formatter. it will be used instead of the built-in formatter.
Axis labels are now drawn with canvas text with some parsing to support Axis tick labels now use the class 'flot-tick-label' instead of 'tickLabel'.
newlines. This solves various issues but also means that they no longer The text containers for each axis now use the classes 'flot-[x|y]-axis' and
support HTML markup, can be accessed as DOM elements or styled directly with 'flot-[x|y]#-axis' instead of '[x|y]Axis' and '[x|y]#Axis'. For compatibility
CSS. Some older browsers lack this function of the canvas API (this doesn't with Flot 0.7 and earlier text will continue to use the old classes as well,
affect IE); if this is a problem, either continue using an older version of but they are considered deprecated and will be removed in a future version.
Flot or try an emulation helper such as canvas-text or Flashcanvas.
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 The base and overlay canvas are now using the CSS classes "flot-base" and
"flot-overlay" to prevent accidental clashes (issue 540). "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 ...@@ -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, - Display time series in different time zones. (patch by Knut Forkalsrud,
issue 141) 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 - Support for setting the interval between redraws of the overlay canvas with
redrawOverlayInterval. (suggested in issue 185) redrawOverlayInterval. (suggested in issue 185)
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css">
<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.canvas.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px;"></div>
<p>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.</p>
<script type="text/javascript">
$(function () {
var d1 = [];
for (var i = 0; i < 14; i += 0.5)
d1.push([i, Math.sin(i)]);
var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
// a null signifies separate line segments
var d3 = [[0, 12], [7, 12], null, [7, 2.5], [12, 2.5]];
$.plot($("#placeholder"), [ d1, d2, d3 ], {canvas: true});
});
</script>
</body>
</html>
/* 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 = $("<div></div>").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(/<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.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);
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment