Review
The previous article said: zrender Source Code Analysis 3: painter (view layer)-above, next, start shape object
Overall understanding
First, return to the render method of the last painter.
/*** Create various Dom and context * core methods for the first plotting, Zr. render () --> painter. difference between render ** render and refersh: render is clear, and refresh is the callback function after the changed layer ** @ Param {function =} callback painting ends */painter. prototype. render = function (callback) {// omitting // traversing in ascending order. zlevel on the Shape specifies the Z axis of the painting layer. This. storage. itershape (this. _ brush ({ALL: true}), {normal: 'up'}); // return this is omitted ;}; /*** click the image ** @ private * @ Param {object} changedzlevel The zlevel index to be updated */painter. prototype. _ brush = function (changedzlevel) {var ctxlist = This. _ ctxlist; var me = This; function updatepainter (shapelist, callback) {me. update (shapelist, callback);} return function (SHAPE) {If (changedzlevel. all | changedzlevel [shape. zlevel]) &! Shape. Invisible) {var CTX = ctxlist [shape. zlevel]; If (CTX) {If (! Shape. onbrush // There is no onbrush // There is an onbrush and the call returns false or undefined to continue painting | (shape. onbrush &&! Shape. onbrush (CTX, false) {If (config. catchbrushexception) {try {shape. brush (CTX, false, updatepainter);} catch (error) {log (error, 'brush error of '+ shape. type, shape) ;}} else {shape. brush (CTX, false, updatepainter) ;}} else {log ('can not find the specific zlevel canvas! ');}}};};
We can see that at the core, the method of traversing the shape object of storage is called, and the input callback is painter. _ brush method: the logic is transferred to the _ brush method. Here, a callback is returned. In the callback, the brush method of the Shape object is directly called. It can be seen that it is still in the shape object.
Shape object
Open the shape folder of zrender. We can see that there are many JS files. The base class is a base class, and other files are a graphics class, all inherited from the base class. It is clear that the template method is used here. Next, use the simplest circle class to analyze the source code. First look at the structure of the circle.
Function circle (options) {base. call (this, options);} circle. prototype = {type: 'circle',/*** create a circular path * @ Param {context2d} CTX canvas 2D context * @ Param {object} style */buildpath: function (CTX, style) {// omitted Implementation},/*** returns the rectangular area for local refresh and Text Location * @ Param {object} style */getrect: function (style) {// omitted Implementation}; require ('.. /tool/util '). inherits (circle, base );
The last line is important and inherits the base class, while the base class implements the brush method. We can see the buildpath and getrect methods and type attributes implemented by circle, it should be a method that overwrites the Same Name of the base class. The base class is still function base () {} base. prototype. baba = funciton () {}. Some default values are set in the constructor, and then overwritten with the custom option.
function Base( options ) { this.id = options.id || guid(); this.zlevel = 0; this.draggable = false; this.clickable = false; this.hoverable = true; this.position = [0, 0]; this.rotation = [0, 0, 0]; this.scale = [1, 1, 0, 0]; for ( var key in options ) { this[ key ] = options[ key ]; } this.style = this.style || {}; }
Next let's look at the core method: Brush
/***** Click ** @ Param CTX canvas handle * @ Param ishighlight whether it is highlighted * @ Param updatecallback you can use this callback (e) to asynchronously load the shape of the resource) * Let painter update the view, base. the brush is useless. If necessary, reload the brush */base. prototype. brush = function (CTX, ishighlight) {var style = This. style; // such as lineshape, Which is configured with brushtypeonly if (this. brushtypeonly) {style. brushtype = This. brushtypeonly;} If (ishighlight) {// default highlighted Style = This according to style extension. gethighlightstyle (Style, this. highlightstyle | |{}, this. brushtypeonly);} If (this. brushtypeonly = 'stroke') {style. strokecolor = style. strokecolor | style. color;} CTX. save (); // set the content object this. setcontext (CTX, style); // sets transform this. updatetransform (CTX); CTX. beginpath (); this. buildpath (CTX, style); If (this. brushtypeonly! = 'Stroke') {CTX. closepath ();} switch (style. brushtype) {Case 'both ': CTX. fill (); Case 'stroke': style. linewidth> 0 & CTX. stroke (); break; default: CTX. fill ();} If (style. text) {This. drawtext (CTX, style, this. style);} CTX. restore ();};
- 1. Set brushtypeonly and brushtype in three forms: Both, stroke, and fill. For example, in a lineshape object, the dashes cannot be fill, but they can only be stroke.
- 2. obtain the appropriate highlightstyle Based on the style of the current shape and transfer it to gethighlightstyle.
/*** Extend the highlighted style according to the default style ** @ Param CTX canvas 2D context * @ Param {object} style default style * @ Param {object} highlightstyle highlight style */base. prototype. gethighlightstyle = function (style, highlightstyle, brushtypeonly) {var newstyle ={}; for (var k in style) {newstyle [k] = style [k];} vaR color = require ('.. /tool/color '); var highlightcolor = color. gethighlightcolor (); // rgba (255,255.0 .0.5) translucent yellow // according to highlightsty Le extension if (style. brushtype! = 'Stroke') {// fill in with a highlighted edge newstyle. strokecolor = highlightcolor; newstyle. linewidth = (style. linewidth | 1) + this. gethighlightzoom (); // if it is text, it is 6. If it is not text, it is 2 newstyle. brushtype = 'both '; // If the highlight layer and brushtype is both or fill, force it to both} else {If (brushtypeonly! = 'Stroke') {// The stroke type uses the primary color to process the highlighted newstyle. strokecolor = highlightcolor; newstyle. linewidth = (style. linewidth | 1) + this. gethighlightzoom ();} For the else {// line type, use the primary color to process the highlighted newstyle. strokecolor = highlightstyle. strokecolor | color. mix (style. strokecolor, color. torgb (highlightcolor) ;}}// you can customize the overwrite default value for (var k in highlightstyle) {If (typeof highlightstyle [k]! = 'Undefined') {newstyle [k] = highlightstyle [k] ;}} return newstyle ;};
- First, copy the default style to the newstyle variable. At the end of the method, newstyle is returned.
- Calculate the highlighted style based on the default style. If brushtype is both or fill, change strokecolor to translucent yellow. Calculate linewidth Based on the graphic type and assign brushtype to both.
- If brushtype is stroke and brushonly is not set to stroke, set strokecolor to translucent yellow and linewidth
- If brushtype is stroke and brushonly is not set to stroke, a color value is calculated using color. Mix.
- Overwrite the custom highlightstyle to newstyle, and return newstyle.
- If brushtypeonly is stroke, multiple sources of color are processed, and the real plot between CTX. Save () and CTX. Restore () is completed.
- Go to the setcontext Method
VaR style_ctx_map = [['color', 'fillstyle'], ['strokecolor', 'strokestyle'], ['opacity ', 'globalalpha'], ['linecap'], ['linejoin'], ['miterlimit'], ['linewidth'], ['shadowblur'], ['shadowcolor'], ['shadowoffsetx'], ['shadowoffsety'];/*** canvas general settings ** @ Param CTX canvas handle * @ Param style general style */base. prototype. setcontext = function (CTX, style) {for (VAR I = 0, Len = style_ctx_map.length; I <Len; I ++ ){ VaR styleprop = style_ctx_map [I] [0]; var stylevalue = style [styleprop]; var ctxprop = style_ctx_map [I] [1] | styleprop; If (typeof stylevalue! = 'Undefined') {CTX [ctxprop] = stylevalue ;}}};
In the native context assignment style, both are context. fillstyle = '# aaa'; however, the zrender abstraction makes it easier to use. setcontext is responsible for native canvasapi and zrender. shape. in fact, only fillstyle, strokestyle, and globalalpha are changed. Replace them with style. Color, style. strokecolor, and opacity. However, the attribute names of these native APIs are not so approachable.
- About deformation, skip temporarily
- Start beginpath, call base. buildpath, and find that there is no buildpath implementation in the base. As mentioned above, the template method is implemented in the subclass. The following is an example of buildpath analysis.
// Shape/circle. JS/*** create a circular path * @ Param {context2d} CTX canvas 2D context * @ Param {object} style */buildpath: function (CTX, style) {CTX. ARC (style. x, style. y, style. r, 0, math. pI * 2, true); return ;}, // shape/rectangle/*** create a rectangular path * @ Param {context2d} CTX canvas 2D context * @ Param {object} style */buildpath: function (CTX, style) {If (! Style. radius) {CTX. moveTo (style. x, style. y); CTX. lineto (style. X + style. width, style. y); CTX. lineto (style. X + style. width, style. Y + style. height); CTX. lineto (style. x, style. Y + style. height); CTX. lineto (style. x, style. y); // CTX. rect (style. x, style. y, style. width, style. height);} else {This. _ buildradiuspath (CTX, style) ;}return ;},
We can see that in the buildpath of the circle class, there is only one sentence, that is, the real canvas drawing API call, and in the rectangle, a path is drawn using moveTo and lineto.
- If it is a shape that can only be underlined, there is no need for closepath; otherwise, colsepath is used to avoid the appearance of a broken line of the image, and then fill and stroke are performed according to the brushtype type. Note, the first case does not have a break, so fill and stroke can be performed simultaneously.
- Finally, process the text appended to the image.
Base. prototype. drawtext = function (CTX, style, normalstyle) {// font color policy var textcolor = style. textcolor | style. color | style. strokecolor; CTX. fillstyle = textcolor;/* If (style. textposition = 'inside ') {CTX. shadowcolor = 'rgba (,) '; // internal text without shadowcolor} * // blank gap between text and graphics var dd = 10; var al; // text horizontal alignment var BL; // text vertical alignment var TX; // text abscissa var ty; // text ordinate var textposition = style. textposi Tion // user defined | this. textposition // shape default | 'top'; // global default switch (textposition) {Case 'inside ': Case 'top': Case 'bottom': Case 'left ': case 'right': If (this. getrect) {var rect = (normalstyle | style ). _ rect | this. getrect (normalstyle | style); Switch (textposition) {Case 'inside ': Tx = rect. X + rect. width/2; ty = rect. Y + rect. height/2; Al = 'center'; BL = 'middle'; // If brushtype If it is both or fill, there will be a fill action. In this case, if the text color is the same as the fill color, the text will be invisible, so it will become white. // However, if the color of the text is white, ah, no, it's too bad if (style. brushtype! = 'Stroke' & textcolor = style. color) {CTX. fillstyle = '# fff';} break; Case 'left': Tx = rect. x-dd; // gap ty = rect. Y + rect. height/2; Al = 'end'; BL = 'middle'; break; Case 'right': Tx = rect. X + rect. width + dd; ty = rect. Y + rect. height/2; Al = 'start'; BL = 'middle'; break; Case 'top': Tx = rect. X + rect. width/2; ty = rect. y-dd; Al = 'center'; BL = 'bottom '; break; Case 'bottom ': Tx = rect. X + rect. width/2; ty = rect. Y + rect. height + dd; Al = 'center'; BL = 'top'; break ;}} break; Case 'start': Case 'end': var xstart; var xend; vaR ystart; var yend; If (typeof style. pointlist! = 'Undefined') {var pointlist = style. pointlist; If (pointlist. Length <2) {// no more than 2 points ~ Return;} var length = pointlist. length; Switch (textposition) {Case 'start': xstart = pointlist [0] [0]; xend = pointlist [1] [0]; ystart = pointlist [0] [1]; yend = pointlist [1] [1]; break; Case 'end': xstart = pointlist [length-2] [0]; xend = pointlist [length-1] [0]; ystart = pointlist [length-2] [1]; yend = pointlist [length-1] [1]; break ;}} else {xstart = style. xstart | 0; xend = style. xend | 0; ystart = style. ystart | 0; yend = style. yend | 0;} switch (textposition) {Case 'start': Al = xstart <xend? 'End': 'start'; BL = ystart <yend? 'Bottom ': 'top'; Tx = xstart; ty = ystart; break; Case 'end': Al = xstart <xend? 'Start': 'end'; BL = ystart <yend? 'Top': 'bottom '; Tx = xend; ty = yend; break;} DD-= 4; If (xstart! = Xend) {TX-= (Al = 'end '? DD:-dd);} else {Al = 'center';} If (ystart! = Yend) {ty-= (BL = 'bottom '? DD:-dd);} else {BL = 'middle';} break; Case 'specpacific ': Tx = style. textx | 0; ty = style. texty | 0; Al = 'start'; BL = 'middle'; break;} If (TX! = NULL & Ty! = NULL) {_ filltext (CTX, style. text, TX, Ty, style. textfont, style. textalign | Al, style. textbaseline | BL) ;}}; // circle. JS getrect/*** returns the rectangular area for local refresh and text positioning * @ Param {object} style */getrect: function (style) {If (style. _ rect) {return style. _ rect;} var linewidth; If (style. brushtype = 'stroke' | style. brushtype = 'fill') {linewidth = style. linewidth | 1;} else {linewidth = 0;} style. _ rect = {X: math. round (style. x-style. r-linewidth/2), Y: math. round (style. y-style. r-linewidth/2), width: style. R * 2 + linewidth, height: style. R * 2 + linewidth}; return style. _ rect ;}};
- For more information about textposition settings, see the API
- Getrect is also a template method used to obtain the rectangular area of the image. It is illustrated with circle that, through a series of calculations, the XY coordinates in the upper left corner of the circle are obtained, the width and height of the original rectangle are obtained, and return. Where, __rect is the cache Function
- Here, Al represents context. textalign in canvasapi, BL refers to textbaseline, TX, ty is the reference coordinate of the text, please see http://www.w3school.com.cn/tags/canvas_textalign.asp and http://www.w3school.com.cn/tags/canvas_textbaseline.asp
- If textposition is inside, left, right, top, and bottom ), assign values to TX/TY/Al/BL Based on rect Information
- If it is start or end, only the line and line are configured. Similarly, set Tx/TY/Al/BL Based on the rect information.
- Finally, get the Tx/TY/Al/BL/font/text and call the real drawing method _ filltext
Function _ filltext (CTX, text, X, Y, textfont, textalign, textbaseline) {If (textfont) {CTX. font = textfont;} CTX. textalign = textalign; CTX. textbaseline = textbaseline; var rect = _ gettextrect (text, X, Y, textfont, textalign, textbaseline); text = (Text + ''). split ('\ n'); var lineheight = require ('.. /tool/area '). gettextheight ('state', textfont); Switch (textbaseline) {Case 'top': Y = rect. y; break; Case 'bottom ': Y = rect. Y + lineheight; break; default: Y = rect. Y + lineheight/2;} For (VAR I = 0, L = text. length; I <L; I ++) {CTX. filltext (Text [I], x, y); y + = lineheight ;}}/*** returns the rectangular area, used for local refresh and text positioning ** @ inner * @ Param {object} style */function _ gettextrect (text, X, Y, textfont, textalign, textbaseline) {var area = require ('.. /tool/area '); var width = area. gettextwidth (text, textfont); var lineheight = area. gettextheight ('status', textfont); text = (Text + ''). split ('\ n'); Switch (textalign) {Case 'end': Case 'right': X-= width; break; Case 'center ': x-= (width/2); break;} switch (textbaseline) {Case 'top': break; Case 'bottom ': Y-= lineheight * text. length; break; default: Y-= lineheight * text. length/2;} return {X: X, Y: Y, width: width, height: lineheight * text. length };}// The following is tool/area. JS method/*** calculate the height of multi-line text * @ Param {object} textfont */function gettextheight (text, textfont) {var key = text + ':' + textfont; If (_ textheightcache [Key]) {return _ textheightcache [Key];} _ CTX = _ CTX | util. getcontext (); _ CTX. save (); If (textfont) {_ CTX. font = textfont;} text = (Text + ''). split ('\ n'); // relatively rough var Height = (_ CTX. measuretext ('status '). width + 2) * text. length; _ CTX. restore (); _ textheightcache [Key] = height; If (++ _ textheightcachecounter> text_cache_max) {// memory release _ textheightcachecounter = 0; _ textheightcache = {};} return height;}/*** calculate the text width of multiple lines * @ Param {object} text * @ Param {object} textfont */function gettextwidth (text, textfont) {var key = text + ':' + textfont; If (_ textwidthcache [Key]) {return _ textwidthcache [Key];} _ CTX = _ CTX | util. getcontext (); _ CTX. save (); If (textfont) {_ CTX. font = textfont;} text = (Text + ''). split ('\ n'); var width = 0; For (VAR I = 0, L = text. length; I <L; I ++) {width = math. max (_ CTX. measuretext (Text [I]). width, width);} _ CTX. restore (); _ textwidthcache [Key] = width; If (++ _ textwidthcachecounter> text_cache_max) {// memory release _ textwidthcachecounter = 0; _ textwidthcache = {};} return width ;}
- Set textalign and textbaseline of context first
- For area. gettextheight and area. gettextwidth, the native measuretext method of canvas is mainly used. There is also a caching technique. For measuretext, see http://www.w3school.com.cn/tags/canvas_measuretext.asp
- _ Gettextrect: obtains the hotspot area of the problem to be drawn, and returns x/y/width/height.
- After _ filltext gets the hotspot area and performs some special processing on the row height, it calls filltext to make a real plot.
- Now, the brush method has been analyzed.
Summary
It takes a lot of time to write these things. The detailed implementation of deformation settings and other graphics will come soon. Let's continue. The next article will continue with the painter analysis.
Zrender source code analysis 4: painter (view layer)-Medium