diff --git a/jquery.flot.js b/jquery.flot.js index 0e15407..f9c5b7d 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -46,8 +46,11 @@ Licensed under the MIT license. function Canvas(cls, container) { + this.container = container; + var element = document.createElement("canvas"); element.className = cls; + this.element = element; $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) .data("canvas", this) @@ -64,8 +67,6 @@ Licensed under the MIT license. } var context = element.getContext("2d"); - - this.element = element; this.context = context; // Determine the screen's ratio of physical to device-independent @@ -89,6 +90,31 @@ Licensed under the MIT license. // Size the canvas to match the internal dimensions of its container this.resize(container.width(), container.height()); + + // Container for HTML text overlaid onto the canvas; created on demand + + this.text = null; + + // Buffer for HTML text fragments, so we can add them all at once + + this._textBuffer = ""; + + // Cache of text fragments and metrics, so we can avoid expensively + // re-calculating them when the plot is re-rendered in a loop. + + this._textCache = {}; + + // A 'hot' copy of the text cache; it holds only info that has been + // accessed in the past render cycle. With each render it is saved as + // the new text cache, as an alternative to more complicated ways of + // expiring items that are no longer needed. + + // NOTE: It's unclear how this compares performance-wise to keeping a + // single cache and looping over it to delete expired items. This way + // is certainly less operations, but seems like it might result in more + // garbage collection and possibly increased cache-insert times. + + this._activeTextCache = {}; }; // Resizes the canvas to the given dimensions. @@ -139,10 +165,175 @@ Licensed under the MIT license. context.scale(pixelRatio, pixelRatio); } - // Clears the entire canvas area. + // Clears the entire canvas area, including overlaid text. Canvas.prototype.clear = function() { this.context.clearRect(0, 0, this.width, this.height); + if (this.text) { + this.text.empty(); + } + } + + // Finishes rendering the canvas, including populating the text overlay. + + Canvas.prototype.render = function() { + + if (this._textBuffer.length) { + + // Add the HTML text layer, if it doesn't already exist + + if (!this.text) { + this.text = $("
").css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0 + }).insertAfter(this.element); + } + + this.text.append(this._textBuffer); + this._textBuffer = ""; + } + + // Swap out the text cache for the 'hot cache' that we've been filling + // out since the last call to render. + + this._textCache = this._activeTextCache; + this._activeTextCache = {}; + } + + // Creates (if necessary) and returns a text info object. + // + // The object looks like this: + // + // { + // prefix: First half of the HTML for the text's div wrapper. + // suffix: Second half of the HTML for the text's div wrapper. + // dimensions: { + // width: Width of the text's wrapper div. + // height: Height of the text's wrapper div. + // } + // } + // + // The prefix and suffix are divided at the 'top' inline style definition, + // so the top and left positions can be added when creating the real div. + // + // Canvas maintains a cache of recently-used text info objects; getTextInfo + // either returns the cached element or creates a new entry. + // + // @param {string} text Text string to retrieve info for. + // @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 to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @return {object} a text info object. + + Canvas.prototype.getTextInfo = function(text, font, angle) { + + var textStyle, cacheKey, info; + + // Cast the value to a string, in case we were given a number or such + + text = "" + text; + + // If the font is a font-spec object, generate a CSS font 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 prefix, + suffix = "px;'>" + text + "", + element; + + // If the font is a font-spec object, generate an inline-style string + + if (typeof font === "object") { + prefix = "