A case in point
Introduce the Echart line chart
const echartsInstance = echarts.init(document.getElementById("main"))
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}]
};
echartsInstance.setOption(option)
Copy the code
In this way, a simple line chart is drawn.
But how does Echart produce a chart? This is the question addressed in this article.
Initialize the
First you need to initialize an instance of Echart
// ./charts.ts import { init } from './core/echarts'; export default { init() { return init.apply(null, arguments); }}; // ./core/echarts.ts export function init( dom: HTMLElement, theme? : string | object, opts? : { renderer? : RendererType, devicePixelRatio? : number, width? : number, height? : number, locale? : string | LocaleOption }): EChartsType { const chart = new ECharts(dom, theme, opts); // instantiate Echarts chart.id = 'ec_' + idBase++; instances[chart.id] = chart; . return chart; }Copy the code
It seems that the core of initialization is concentrated in the Echarts object,
class ECharts extends Eventful<ECEventDefinition> { id: string; group: string; private _zr: zrender.ZRenderType; private _dom: HTMLElement; private _model: GlobalModel; private _throttledZrFlush: zrender.ZRenderType extends {flush: infer R} ? R : never; . constructor( dom: HTMLElement, theme? : string | ThemeOption, opts? : { locale? : string | LocaleOption, renderer? : RendererType, devicePixelRatio? : number, useDirtyRect? : boolean, width? : number, height? : number } ) { super(new ECEventProcessor()); . Const zr = this._zr = zrender. Init (dom, {renderer: opts.renderer || defaultRenderer, devicePixelRatio: opts.devicePixelRatio, width: opts.width, height: opts.height, useDirtyRect: opts.useDirtyRect == null ? defaultUseDirtyRect : opts.useDirtyRect }); . On ('frame', this._onframe, this); // Register the callback function for the interaction event to ensure that zr.animation.on('frame', this._onframe, this) is redrawn; . }... }Copy the code
The above only preserves the key logic of the constructor,
First, canvas initialization based on ZRender (zrender. Init), the details of zRender initialization are arranged in the ZRender section.
We then register callback functions for interactive events, such as zr.animation/ resize. The core function of the callback function is to redraw the chart, which is not covered in detail here.
setOption
With the current method, the user can bind the set property object to the Echarts instance for the build and first rendering of the diagram. The logic is as follows
setOption<Opt extends ECBasicOption>(option: Opt, notMerge? : boolean | SetOptionOpts, lazyUpdate? : boolean): void { ... // Prepare (this); / / update the view updateMethods. Update. The call (this); . }Copy the code
The above snippet preserves only the core logic.
1. Prepare data
The Prepare method groups the child components of the chart together, storing the assembled chart object in a ZR object
// Prepare (this); // Prepare = function (ecIns: ECharts): void {... prepareView(ecIns, true); . }; prepareView = function (ecIns: ECharts, isComponent: boolean): void { ... isComponent ? ecModel.eachComponent(function (componentType, model) { componentType ! == 'series' && doPrepare(model); }) : ecModel.eachSeries(doPrepare); function doPrepare(model: ComponentModel): void { const viewId = '_ec_' + model.id + '_' + model.type; let view = ! requireNewView && viewMap[viewId]; if (! view) { const classType = parseClassType(model.type); const Clazz = isComponent ? (ComponentView as ComponentViewConstructor).getClass(classType.main, classType.sub) : (ChartView as ChartViewConstructor).getClass(classType.sub)); View = new Clazz(); view.init(ecModel, api); viewMap[viewId] = view; viewList.push(view as any); zr.add(view.group); }}... };Copy the code
2. Update the view
The first update of the chart view is achieved by calling the UPDATE method
updateMethods.update.call(this)
Copy the code
In the update method, the render method is called, which is the core method of rendering
updateMethods = { update(this: ECharts, payload: Payload, updateParams: UpdateLifecycleParams): void { ... render(this, ecModel, api, payload, updateParams); . }} render = ( ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, updateParams: UpdateLifecycleParams) => { renderComponents(ecIns, ecModel, api, payload, updateParams); renderSeries(ecIns, ecModel, api, payload, updateParams); };Copy the code
Render method is divided into renderComponents and renderSeries
RenderComponents calls the Render method under Component with each traversal
renderComponents = ( ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, updateParams: UpdateLifecycleParams, dirtyList? : ComponentView []) = > {/ / render all components each (dirtyList | | ecIns. _componentsViews, function (ComponentView: ComponentView) { componentView.render(componentModel, ecModel, api, payload); }); };Copy the code
RenderSeries also calls the render method under series via each iteration in echarts3.0. After version 4.0, the logic controlling rendering is handed over to the scheduler for processing. By calling the reset method in Charts and then calling the render method under Series, the specific logic will not be described again.
In Echart, what are commonly called chart components are called Series, such as line chart/bar chart/pie chart **; ** And call related components outside of this component component, such as title icon title/Legend legend component/AxisPointer axis indicator/coordinate system/axis, etc.
Next, series and Component, respectively.
Part 2 Component
For various chart-related components, Echart adopts MVC structure to split the code structure, where Model is used to manage component data and View is used to render the View.
Take the Title component of a chart,
Add configuration data in the corresponding field of Option
Title: {left: 'center', text: 'sample title'}Copy the code
The Model part of the title file extends from the ComponentModel with the extendComponentModel method, overriding the defaultOption property to set the defaultOption for title
View extends ComponentView through extendComponentView method, rewriting the render method to render the title
render(titleModel: TitleModel, ecModel: GlobalModel, api: ExtensionAPI) {// Add text element const textStyleModel = titleModel.getModel('textStyle'); const textEl = new graphic.Text({ style: createTextStyle(textStyleModel, { text: titleModel.get('text'), fill: textStyleModel.getTextColor() }, {disableBox: true}), z2: 10 }); group.add(textEl); Const alignStyle = {align: textAlign, verticalAlign: textVerticalAlign}; textEl.setStyle(alignStyle); // Add background groupRect = group.getboundingRect (); const rect = new graphic.Rect({ shape: {... }, style: style, subPixelOptimize: true, silent: true }); group.add(rect); }Copy the code
Title Text rendering is primarily rendered by Text in the Zrender Graphic, and elements are styled using the setStyle method defined in Zrender Style.
Part III Series
Similar to the component structure of Component, the Series component is split into model and View parts
Take the Line graph component as an example
LineSeries
LineSeries extends the Series Model with the extend method, overrides the defaultOption property, and the getInitialData method.
Lineview
LineView extends from Chart View with the extend method, overriding init, Render, highlight, and downplay methods
render(seriesModel: LineSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { ... Polyline = this._newPolyline(points, coordSys, hasAnimation); Polyline = this._newPolyline(points, coordSys, hasAnimation); if (isAreaChart && ! Polygon = this._newPolygon(points, stackedOnPoints); // Zrender polygon = this._newPolygon(points, stackedOnPoints); } else if (polygon && ! isAreaChart) { // If areaStyle is removed lineGroup.remove(polygon); polygon = this._polygon = null; }... if (polygon) { ... Polygon. UseStyle (zrUtil. Defaults (areaStyleModel getAreaStyle (), {the fill: visualColor, opacity: 0.7, lineJoin: 'bevel' as CanvasLineJoin, decal: data.getVisual('style').decal } )); polygon.setShape({ smooth, stackedOnSmooth, smoothMonotone, connectNulls }); . }... }Copy the code
Among them, the basic line, line area drawing, rely on zRender’s basic drawing API support.
This is the core of Echart’s plotting logic. (Event mechanism and other important but non-core modules are not introduced for the moment)
As you can see, both the initialization of diagrams (Zrender. Init) and the rendering of specific components (new graphic.text) rely on an underlying drawing library called Zrender, where all the drawing details are encapsulated. Next, let’s analyze the structure in ZRender.
Ps: Zrender base drawing library
Zrender is a drawing library based on Canvas, and the overall architecture adopts MVC
– Storage(M) : CRUD of graphics data
– Painter(V) : Manages the rendering and updating of canvas elements
– Handler(C) : Realizes interactive event processing and simulated encapsulation of canvas element dom
In the context of infrastructure, focus first on the series of basic shape components provided. Directory at. / SRC/graphic/shape
Take Circle.ts, for example.
class Circle extends Path<CircleProps> { shape: CircleShape constructor(opts? : CircleProps) { super(opts); } buildPath(ctx: CanvasRenderingContext2D, shape: CircleShape, inBundle: boolean) { if (inBundle) { ctx.moveTo(shape.cx + shape.r, shape.cy); } ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2); }};Copy the code
The Circle class inherits from the basic graphics class Path and overwrites the buildPath method to complete the drawing of canvas concrete graphics.
In addition
The Path class defines interface methods for basic graphics, such as buildPath; Contain () and implement some common methods, such as contain(). The Path class inherits from the Displayable class.
Technical features
1. Clear module splitting
Renderer is an interface that can be extended to canvas, SVG, etc. The. / SRC/renderer / {the renderer} the default canvas renderer will introduce zrender/SRC/canvas/Painter
The basic graphics rendering of canvas and the capabilities of the basic drawing API are separately separated and packaged into zRender library.
2. Strong expansibility
Tooltip supports canvas rendering and DOM rendering
3. Strong rendering performance
Canvas drawing supports synchronous rendering and asynchronous batch rendering.