From 6350cc67008bcd44bec12e8e3a2719c981efb769 Mon Sep 17 00:00:00 2001 From: David Schnur Date: Fri, 12 Jul 2013 19:43:22 -0400 Subject: [PATCH] Added core support for rotated HTML text. --- jquery.flot.js | 204 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 43 deletions(-) diff --git a/jquery.flot.js b/jquery.flot.js index 5bc01ba..62f50e1 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -151,28 +151,33 @@ Licensed under the MIT license. for (var styleKey in layerCache) { if (Object.prototype.hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (Object.prototype.hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length === 0) { - delete styleCache[key]; - } + for (var angleKey in styleCache) { + if (Object.prototype.hasOwnProperty.call(styleCache, angleKey)) { + var angleCache = styleCache[angleKey]; + for (var key in angleCache) { + if (Object.prototype.hasOwnProperty.call(angleCache, key)) { + + var positions = angleCache[key].positions; + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + if (!position.rendered) { + layer.append(position.element); + position.rendered = true; + } + } else { + positions.splice(i--, 1); + if (position.rendered) { + position.element.detach(); + } + } + } + + if (positions.length === 0) { + delete angleCache[key]; + } + } + } } } } @@ -264,17 +269,15 @@ Licensed under the MIT license. // @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. // @param {number=} width Maximum width of the text before it wraps. // @return {object} a text info object. Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such + var textStyle, layerCache, styleCache, angleCache, info; - text = "" + text; + text = "" + text; // Cast to string in case we have a number or such + angle = (360 + (angle || 0)) % 360; // Normalize the angle to 0...359 // If the font is a font-spec object, generate a CSS font definition @@ -284,21 +287,24 @@ Licensed under the MIT license. textStyle = font; } - // Retrieve (or create) the cache for the text's layer and styles + // Retrieve or create the caches for the text's layer, style, and angle layerCache = this._textCache[layer]; - if (layerCache == null) { layerCache = this._textCache[layer] = {}; } styleCache = layerCache[textStyle]; - if (styleCache == null) { styleCache = layerCache[textStyle] = {}; } - info = styleCache[text]; + angleCache = styleCache[angle]; + if (angleCache == null) { + angleCache = styleCache[angle] = {}; + } + + info = angleCache[text]; // If we can't find a matching element in our cache, create a new one @@ -321,9 +327,117 @@ Licensed under the MIT license. element.addClass(font); } - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), + // Save the original dimensions of the text; we'll modify these + // later to take into account rotation, if there is any. + + var textWidth = element.outerWidth(true), + textHeight = element.outerHeight(true); + + // Apply rotation to the text using CSS3/IE matrix transforms + + // Note how we also set the element's width, as a work-around for + // the way most browsers resize the div on rotate, which may cause + // the contents to wrap differently. The extra +1 is because IE + // rounds the width differently and needs a little extra help. + + if (angle) { + + var radians = angle * Math.PI / 180, + sin = Math.sin(radians), + cos = Math.cos(radians), + a = cos.toFixed(6), // Use fixed-point so these don't + b = (-sin).toFixed(6), // show up in scientific notation + c = sin.toFixed(6), // when we add them to the string + transformRule; + + if ($.support.leadingWhitespace) { + + // The transform origin defaults to '50% 50%', producing + // blurry text on some browsers (Chrome) when the width or + // height happens to be odd, making 50% fractional. Avoid + // this by setting the origin to rounded values. + + var cx = textWidth / 2, + cy = textHeight / 2, + transformOrigin = Math.floor(cx) + "px " + Math.floor(cy) + "px"; + + // Transforms alter the div's appearance without changing + // its origin. This will make it difficult to position it + // later, since we'll be positioning the new bounding box + // with respect to the old origin. We can work around this + // by adding a translation to align the new bounding box's + // top-left corner with the origin, using the same matrix. + + // Rather than examining all four points, we can use the + // angle to figure out in advance which two points are in + // the top-left quadrant; we can then use the x-coordinate + // of the first (left-most) point and the y-coordinate of + // the second (top-most) point as the bounding box corner. + + var x, y; + if (angle < 90) { + x = Math.floor(cx * cos + cy * sin - cx); + y = Math.floor(cx * sin + cy * cos - cy); + } else if (angle < 180) { + x = Math.floor(cy * sin - cx * cos - cx); + y = Math.floor(cx * sin - cy * cos - cy); + } else if (angle < 270) { + x = Math.floor(-cx * cos - cy * sin - cx); + y = Math.floor(-cx * sin - cy * cos - cy); + } else { + x = Math.floor(cx * cos - cy * sin - cx); + y = Math.floor(cy * cos - cx * sin - cy); + } + + transformRule = "matrix(" + a + "," + c + "," + b + "," + a + "," + x + "," + y + ")"; + + element.css({ + width: textWidth + 1, + transform: transformRule, + "-o-transform": transformRule, + "-ms-transform": transformRule, + "-moz-transform": transformRule, + "-webkit-transform": transformRule, + "transform-origin": transformOrigin, + "-o-transform-origin": transformOrigin, + "-ms-transform-origin": transformOrigin, + "-moz-transform-origin": transformOrigin, + "-webkit-transform-origin": transformOrigin + }); + + } else { + + // The IE7/8 matrix filter produces very ugly aliasing for + // text with a transparent background. Using a solid color + // greatly improves text clarity, although it does result + // in ugly boxes for plots using a non-white background. + + // TODO: Instead of white use the actual background color? + // This still wouldn't solve the problem when the plot has + // a gradient background, but it would at least help. + + transformRule = "progid:DXImageTransform.Microsoft.Matrix(M11=" + a + ", M12=" + b + ", M21=" + c + ", M22=" + a + ",sizingMethod='auto expand')"; + + element.css({ + width: textWidth + 1, + filter: transformRule, + "-ms-filter": transformRule, + "background-color": "#fff" + }); + } + + // Compute the final dimensions of the text's bounding box + + var ac = Math.abs(cos), + as = Math.abs(sin), + originalWidth = textWidth; + textWidth = Math.round(ac * textWidth + as * textHeight); + textHeight = Math.round(as * originalWidth + ac * textHeight); + } + + info = angleCache[text] = { + width: textWidth, + height: textHeight, element: element, positions: [] }; @@ -347,7 +461,6 @@ Licensed under the MIT license. // @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. // @param {number=} width Maximum width of the text before it wraps. // @param {string=} halign Horizontal alignment of the text; either "left", // "center" or "right". @@ -435,14 +548,19 @@ Licensed under the MIT license. for (var styleKey in layerCache) { if (Object.prototype.hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (Object.prototype.hasOwnProperty.call(styleCache, key)) { - positions = styleCache[key].positions; - for (i = 0; position = positions[i]; i++) { - position.active = false; - } - } - } + for (var angleKey in styleCache) { + if (Object.prototype.hasOwnProperty.call(styleCache, angleKey)) { + var angleCache = styleCache[angleKey]; + for (var key in angleCache) { + if (Object.prototype.hasOwnProperty.call(angleCache, key)) { + positions = angleCache[key].positions; + for (i = 0; position = positions[i]; i++) { + position.active = false; + } + } + } + } + } } } }