@ -33,233 +33,280 @@ browser, but needs to redraw with canvas text when exporting as an image.
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 ,
drawText = Canvas . prototype . drawText ;
addText = Canvas . prototype . addText ,
render = Canvas . prototype . render ;
// 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.
// }
// }
// Finishes rendering the canvas, including overlaid text
Canvas . prototype . getTextInfo = function ( text , font , angle ) {
if ( plot . getOptions ( ) . canvas ) {
Canvas . prototype . render = function ( ) {
var textStyle , cacheKey , info ;
// Cast the value to a string, in case we were given a number
if ( ! plot . getOptions ( ) . canvas ) {
return render . call ( this ) ;
}
text = "" + text ;
var context = this . context ,
cache = this . _textCache ,
cacheHasText = false ,
key ;
// If the font is a font-spec object, generate a CSS definition
// Check whether the cache actually has any entries.
if ( typeof font === "object" ) {
textStyle = font . style + " " + font . variant + " " + font . weight + " " + font . size + "px " + font . family ;
} else {
textStyle = font ;
for ( key in cache ) {
if ( hasOwnProperty . call ( cache , key ) ) {
cacheHasText = true ;
break ;
}
}
// The text + style + angle uniquely identify the text's
// dimensions and content; we'll use them to build this entry's
// text cache key.
if ( ! cacheHasText ) {
return ;
}
cacheKey = text + "-" + textStyle + "-" + angle ;
// Render the contents of the cache
info = this . _textCache [ cacheKey ] || this . _activeTextCache [ cacheKey ] ;
context . save ( ) ;
if ( info == null ) {
for ( key in cache ) {
if ( hasOwnProperty . call ( cache , key ) ) {
var context = this . context ;
var info = cache [ key ] ;
// If the font was provided as CSS, create a div with those
// classes and examine it to generate a canvas font spec.
if ( ! info . active ) {
delete cache [ key ] ;
continue ;
}
if ( typeof font !== "object" ) {
var x = info . x ,
y = info . y ,
lines = info . lines ,
halign = info . halign ;
var element ;
if ( typeof font === "string" ) {
element = $ ( "<div class='" + font + "'>" + text + "</div>" )
. appendTo ( this . container ) ;
} else {
element = $ ( "<div>" + text + "</div>" )
. appendTo ( this . container ) ;
}
context . fillStyle = info . font . color ;
context . font = info . font . definition ;
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" )
} ;
// 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.
textStyle = font . style + " " + font . variant + " " + font . weight + " " + font . size + "px " + font . family ;
// 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.
element . remove ( ) ;
}
context . textBaseline = "top" ;
// 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 ;
for ( var i = 0 ; i < lines . length ; ++ i ) {
// 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 line = lines [ i ] ,
linex = x ;
var lines = ( text + "" ) . replace ( /<br ?\/?>|\r\n|\r/g , "\n" ) . split ( "\n" ) ;
// Apply horizontal alignment per-line
for ( var i = 0 ; i < lines . length ; ++ i ) {
if ( halign == "center" ) {
linex -= line . width / 2 ;
} else if ( halign == "right" ) {
linex -= line . width ;
}
var lineText = lines [ i ] ,
measured = context . measureText ( lineText ) ,
lineWidth , lineHeight ;
// FIXME: LEGACY BROWSER FIX
// AFFECTS: Opera < 12.00
lineWidth = measured . width ;
// 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.
// Height might not be defined; not in the standard yet
if ( ! ! ( window . opera && window . opera . version ( ) . split ( "." ) [ 0 ] < 12 ) ) {
linex = Math . floor ( linex ) ;
y = Math . ceil ( y - 2 ) ;
}
lineHeight = measured . height || font . size ;
context . fillText ( line . text , linex , y ) ;
y += line . height ;
}
}
}
// 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.
context . restore ( ) ;
} ;
lineHeight += Math . round ( font . size * 0.15 ) ;
// 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.
// },
// }
info . dimensions . width = Math . max ( lineWidth , info . dimensions . width ) ;
info . dimensions . height += lineHeight ;
Canvas . prototype . getTextInfo = function ( text , font , angle ) {
info . lines . push ( {
text : lineText ,
width : lineWidth ,
height : lineHeight
} ) ;
}
if ( ! plot . getOptions ( ) . canvas ) {
return getTextInfo . call ( this , text , font , angle ) ;
}
context . restore ;
}
var textStyle , cacheKey , info ;
// Save the entry to the 'hot' text cache, marking it as active
// and preserving it for the next render pass.
// Cast the value to a string, in case we were given a number
this . _activeTextCache [ cacheKey ] = info ;
text = "" + text ;
return info ;
// 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 {
return getTextInfo . call ( this , text , font , angle ) ;
textStyle = font ;
}
}
// Draws a text string onto the canvas.
//
// When the canvas option is set, this override draws directly to the
// canvas using fillText.
// The text + style + angle uniquely identify the text's dimensions
// and content; we'll use them to build the entry's text cache key.
Canvas . prototype . drawText = function ( x , y , text , font , angle , halign , valign ) {
if ( plot . getOptions ( ) . canvas ) {
cacheKey = text + "-" + textStyle + "-" + angle ;
var info = this . getTextInfo ( text , font , angle ) ,
dimensions = info . dimensions ,
context = this . context ,
lines = info . lines ;
info = this . _textCache [ cacheKey ] ;
// Apply alignment to the vertical position of the entire text
if ( info == null ) {
if ( valign == "middle" ) {
y -= dimensions . height / 2 ;
} else if ( valign == "bottom" ) {
y -= dimensions . height ;
}
var context = this . context ;
context . save ( ) ;
// 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" ) {
context . fillStyle = info . font . color ;
context . font = info . font . definition ;
var element = $ ( "<div></div>" ) . html ( text )
. addClass ( typeof font === "string" ? font : null )
. appendTo ( this . container ) ;
// 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:
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 = {
x : null ,
y : null ,
width : 0 ,
height : 0 ,
active : false ,
lines : [ ] ,
font : {
definition : textStyle ,
color : font . color
}
} ;
context . save ( ) ;
context . font = textStyle ;
// 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.
// 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
// bro ken; revisit when we drop IE 7/8 support .
context . textBaseline = "top" ;
var lines = ( text + "" ) . replace ( /<br ?\/?>|\r\n|\r/g , "\n" ) . split ( "\n" ) ;
for ( var i = 0 ; i < lines . length ; ++ i ) {
var line = lines [ i ] ,
linex = x ;
var lineText = lines [ i ] ,
measured = context . measureText ( lineText ) ,
lineWidth , lineHeight ;
// Apply alignment to the horizontal position per-line
lineWidth = measured . width ;
if ( halign == "center" ) {
linex -= line . width / 2 ;
} else if ( halign == "right" ) {
linex -= line . width ;
}
// Height might not be defined; not in the standard yet
// FIXME: LEGACY BROWSER FIX
// AFFECTS: Opera < 12.00
lineHeight = measured . height || font . size ;
// 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
// 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.
if ( ! ! ( window . opera && window . opera . version ( ) . split ( "." ) [ 0 ] < 12 ) ) {
linex = Math . floor ( linex ) ;
y = Math . ceil ( y - 2 ) ;
}
lineHeight += Math . round ( font . size * 0.15 ) ;
info . width = Math . max ( lineWidth , info . width ) ;
info . height += lineHeight ;
context . fillText ( line . text , linex , y ) ;
y += line . height ;
info . lines . push ( {
text : lineText ,
width : lineWidth ,
height : lineHeight
} ) ;
}
this . _textCache [ cacheKey ] = info ;
context . restore ( ) ;
}
} else {
drawText . call ( this , x , y , text , font , angle , halign , valign ) ;
return info ;
} ;
// Adds a text string to the canvas text overlay.
Canvas . prototype . addText = function ( x , y , text , font , angle , halign , valign ) {
if ( ! plot . getOptions ( ) . canvas ) {
return addText . call ( this , x , y , text , font , angle , halign , valign ) ;
}
var info = this . getTextInfo ( 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 ( {