| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | import * as zrUtil from 'zrender/src/core/util'; | 
 | import ChartView from '../../view/Chart'; | 
 | import * as graphic from '../../util/graphic'; | 
 | import { setStatesStylesFromModel } from '../../util/states'; | 
 | import Path, { PathProps } from 'zrender/src/graphic/Path'; | 
 | import {createClipPath} from '../helper/createClipPathFromCoordSys'; | 
 | import CandlestickSeriesModel, { CandlestickDataItemOption } from './CandlestickSeries'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import { StageHandlerProgressParams } from '../../util/types'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import {CandlestickItemLayout} from './candlestickLayout'; | 
 | import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem'; | 
 | import Model from '../../model/Model'; | 
 | import { saveOldStyle } from '../../animation/basicTransition'; | 
 | import Element from 'zrender/src/Element'; | 
 |  | 
 | const SKIP_PROPS = ['color', 'borderColor'] as const; | 
 |  | 
 | class CandlestickView extends ChartView { | 
 |  | 
 |     static readonly type = 'candlestick'; | 
 |     readonly type = CandlestickView.type; | 
 |  | 
 |     private _isLargeDraw: boolean; | 
 |  | 
 |     private _data: SeriesData; | 
 |  | 
 |     private _progressiveEls: Element[]; | 
 |  | 
 |     render(seriesModel: CandlestickSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         // If there is clipPath created in large mode. Remove it. | 
 |         this.group.removeClipPath(); | 
 |         // Clear previously rendered progressive elements. | 
 |         this._progressiveEls = null; | 
 |  | 
 |         this._updateDrawMode(seriesModel); | 
 |  | 
 |         this._isLargeDraw | 
 |             ? this._renderLarge(seriesModel) | 
 |             : this._renderNormal(seriesModel); | 
 |     } | 
 |  | 
 |     incrementalPrepareRender(seriesModel: CandlestickSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         this._clear(); | 
 |         this._updateDrawMode(seriesModel); | 
 |     } | 
 |  | 
 |     incrementalRender( | 
 |         params: StageHandlerProgressParams, | 
 |         seriesModel: CandlestickSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI | 
 |     ) { | 
 |         this._progressiveEls = []; | 
 |         this._isLargeDraw | 
 |              ? this._incrementalRenderLarge(params, seriesModel) | 
 |              : this._incrementalRenderNormal(params, seriesModel); | 
 |     } | 
 |  | 
 |     eachRendered(cb: (el: Element) => boolean | void) { | 
 |         graphic.traverseElements(this._progressiveEls || this.group, cb); | 
 |     } | 
 |  | 
 |     _updateDrawMode(seriesModel: CandlestickSeriesModel) { | 
 |         const isLargeDraw = seriesModel.pipelineContext.large; | 
 |         if (this._isLargeDraw == null || isLargeDraw !== this._isLargeDraw) { | 
 |             this._isLargeDraw = isLargeDraw; | 
 |             this._clear(); | 
 |         } | 
 |     } | 
 |  | 
 |     _renderNormal(seriesModel: CandlestickSeriesModel) { | 
 |         const data = seriesModel.getData(); | 
 |         const oldData = this._data; | 
 |         const group = this.group; | 
 |         const isSimpleBox = data.getLayout('isSimpleBox'); | 
 |  | 
 |         const needsClip = seriesModel.get('clip', true); | 
 |         const coord = seriesModel.coordinateSystem; | 
 |         const clipArea = coord.getArea && coord.getArea(); | 
 |  | 
 |         // There is no old data only when first rendering or switching from | 
 |         // stream mode to normal mode, where previous elements should be removed. | 
 |         if (!this._data) { | 
 |             group.removeAll(); | 
 |         } | 
 |  | 
 |         data.diff(oldData) | 
 |             .add(function (newIdx) { | 
 |                 if (data.hasValue(newIdx)) { | 
 |                     const itemLayout = data.getItemLayout(newIdx) as CandlestickItemLayout; | 
 |  | 
 |                     if (needsClip && isNormalBoxClipped(clipArea, itemLayout)) { | 
 |                         return; | 
 |                     } | 
 |  | 
 |                     const el = createNormalBox(itemLayout, newIdx, true); | 
 |                     graphic.initProps(el, {shape: {points: itemLayout.ends}}, seriesModel, newIdx); | 
 |  | 
 |                     setBoxCommon(el, data, newIdx, isSimpleBox); | 
 |  | 
 |                     group.add(el); | 
 |  | 
 |                     data.setItemGraphicEl(newIdx, el); | 
 |                 } | 
 |             }) | 
 |             .update(function (newIdx, oldIdx) { | 
 |                 let el = oldData.getItemGraphicEl(oldIdx) as NormalBoxPath; | 
 |  | 
 |                 // Empty data | 
 |                 if (!data.hasValue(newIdx)) { | 
 |                     group.remove(el); | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 const itemLayout = data.getItemLayout(newIdx) as CandlestickItemLayout; | 
 |                 if (needsClip && isNormalBoxClipped(clipArea, itemLayout)) { | 
 |                     group.remove(el); | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 if (!el) { | 
 |                     el = createNormalBox(itemLayout, newIdx); | 
 |                 } | 
 |                 else { | 
 |                     graphic.updateProps(el, { | 
 |                         shape: { | 
 |                             points: itemLayout.ends | 
 |                         } | 
 |                     }, seriesModel, newIdx); | 
 |  | 
 |                     saveOldStyle(el); | 
 |                 } | 
 |  | 
 |                 setBoxCommon(el, data, newIdx, isSimpleBox); | 
 |  | 
 |                 group.add(el); | 
 |                 data.setItemGraphicEl(newIdx, el); | 
 |             }) | 
 |             .remove(function (oldIdx) { | 
 |                 const el = oldData.getItemGraphicEl(oldIdx); | 
 |                 el && group.remove(el); | 
 |             }) | 
 |             .execute(); | 
 |  | 
 |         this._data = data; | 
 |     } | 
 |  | 
 |     _renderLarge(seriesModel: CandlestickSeriesModel) { | 
 |         this._clear(); | 
 |  | 
 |         createLarge(seriesModel, this.group); | 
 |  | 
 |         const clipPath = seriesModel.get('clip', true) | 
 |             ? createClipPath(seriesModel.coordinateSystem, false, seriesModel) | 
 |             : null; | 
 |         if (clipPath) { | 
 |             this.group.setClipPath(clipPath); | 
 |         } | 
 |         else { | 
 |             this.group.removeClipPath(); | 
 |         } | 
 |  | 
 |     } | 
 |  | 
 |     _incrementalRenderNormal(params: StageHandlerProgressParams, seriesModel: CandlestickSeriesModel) { | 
 |         const data = seriesModel.getData(); | 
 |         const isSimpleBox = data.getLayout('isSimpleBox'); | 
 |  | 
 |         let dataIndex; | 
 |         while ((dataIndex = params.next()) != null) { | 
 |             const itemLayout = data.getItemLayout(dataIndex) as CandlestickItemLayout; | 
 |             const el = createNormalBox(itemLayout, dataIndex); | 
 |             setBoxCommon(el, data, dataIndex, isSimpleBox); | 
 |  | 
 |             el.incremental = true; | 
 |             this.group.add(el); | 
 |  | 
 |             this._progressiveEls.push(el); | 
 |         } | 
 |     } | 
 |  | 
 |     _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: CandlestickSeriesModel) { | 
 |         createLarge(seriesModel, this.group, this._progressiveEls, true); | 
 |     } | 
 |  | 
 |     remove(ecModel: GlobalModel) { | 
 |         this._clear(); | 
 |     } | 
 |  | 
 |     _clear() { | 
 |         this.group.removeAll(); | 
 |         this._data = null; | 
 |     } | 
 | } | 
 |  | 
 | class NormalBoxPathShape { | 
 |     points: number[][]; | 
 | } | 
 |  | 
 | interface NormalBoxPathProps extends PathProps { | 
 |     shape?: Partial<NormalBoxPathShape> | 
 | } | 
 |  | 
 | class NormalBoxPath extends Path<NormalBoxPathProps> { | 
 |  | 
 |     readonly type = 'normalCandlestickBox'; | 
 |  | 
 |     shape: NormalBoxPathShape; | 
 |  | 
 |     __simpleBox: boolean; | 
 |  | 
 |     constructor(opts?: NormalBoxPathProps) { | 
 |         super(opts); | 
 |     } | 
 |  | 
 |     getDefaultShape() { | 
 |         return new NormalBoxPathShape(); | 
 |     } | 
 |  | 
 |     buildPath(ctx: CanvasRenderingContext2D, shape: NormalBoxPathShape) { | 
 |         const ends = shape.points; | 
 |  | 
 |         if (this.__simpleBox) { | 
 |             ctx.moveTo(ends[4][0], ends[4][1]); | 
 |             ctx.lineTo(ends[6][0], ends[6][1]); | 
 |         } | 
 |         else { | 
 |             ctx.moveTo(ends[0][0], ends[0][1]); | 
 |             ctx.lineTo(ends[1][0], ends[1][1]); | 
 |             ctx.lineTo(ends[2][0], ends[2][1]); | 
 |             ctx.lineTo(ends[3][0], ends[3][1]); | 
 |             ctx.closePath(); | 
 |  | 
 |             ctx.moveTo(ends[4][0], ends[4][1]); | 
 |             ctx.lineTo(ends[5][0], ends[5][1]); | 
 |             ctx.moveTo(ends[6][0], ends[6][1]); | 
 |             ctx.lineTo(ends[7][0], ends[7][1]); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | function createNormalBox(itemLayout: CandlestickItemLayout, dataIndex: number, isInit?: boolean) { | 
 |     const ends = itemLayout.ends; | 
 |     return new NormalBoxPath({ | 
 |         shape: { | 
 |             points: isInit | 
 |                 ? transInit(ends, itemLayout) | 
 |                 : ends | 
 |         }, | 
 |         z2: 100 | 
 |     }); | 
 | } | 
 |  | 
 | function isNormalBoxClipped(clipArea: CoordinateSystemClipArea, itemLayout: CandlestickItemLayout) { | 
 |     let clipped = true; | 
 |     for (let i = 0; i < itemLayout.ends.length; i++) { | 
 |         // If any point are in the region. | 
 |         if (clipArea.contain(itemLayout.ends[i][0], itemLayout.ends[i][1])) { | 
 |             clipped = false; | 
 |             break; | 
 |         } | 
 |     } | 
 |     return clipped; | 
 | } | 
 |  | 
 | function setBoxCommon(el: NormalBoxPath, data: SeriesData, dataIndex: number, isSimpleBox?: boolean) { | 
 |     const itemModel = data.getItemModel(dataIndex) as Model<CandlestickDataItemOption>; | 
 |  | 
 |     el.useStyle(data.getItemVisual(dataIndex, 'style')); | 
 |     el.style.strokeNoScale = true; | 
 |  | 
 |     el.__simpleBox = isSimpleBox; | 
 |  | 
 |     setStatesStylesFromModel(el, itemModel); | 
 | } | 
 |  | 
 | function transInit(points: number[][], itemLayout: CandlestickItemLayout) { | 
 |     return zrUtil.map(points, function (point) { | 
 |         point = point.slice(); | 
 |         point[1] = itemLayout.initBaseline; | 
 |         return point; | 
 |     }); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | class LargeBoxPathShape { | 
 |     points: ArrayLike<number>; | 
 | } | 
 |  | 
 | interface LargeBoxPathProps extends PathProps { | 
 |     shape?: Partial<LargeBoxPathShape> | 
 |     __sign?: number | 
 | } | 
 |  | 
 | class LargeBoxPath extends Path { | 
 |     readonly type = 'largeCandlestickBox'; | 
 |  | 
 |     shape: LargeBoxPathShape; | 
 |  | 
 |     __sign: number; | 
 |  | 
 |     constructor(opts?: LargeBoxPathProps) { | 
 |         super(opts); | 
 |     } | 
 |  | 
 |     getDefaultShape() { | 
 |         return new LargeBoxPathShape(); | 
 |     } | 
 |  | 
 |     buildPath(ctx: CanvasRenderingContext2D, shape: LargeBoxPathShape) { | 
 |         // Drawing lines is more efficient than drawing | 
 |         // a whole line or drawing rects. | 
 |         const points = shape.points; | 
 |         for (let i = 0; i < points.length;) { | 
 |             if (this.__sign === points[i++]) { | 
 |                 const x = points[i++]; | 
 |                 ctx.moveTo(x, points[i++]); | 
 |                 ctx.lineTo(x, points[i++]); | 
 |             } | 
 |             else { | 
 |                 i += 3; | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function createLarge( | 
 |     seriesModel: CandlestickSeriesModel, | 
 |     group: graphic.Group, | 
 |     progressiveEls?: Element[], | 
 |     incremental?: boolean | 
 | ) { | 
 |     const data = seriesModel.getData(); | 
 |     const largePoints = data.getLayout('largePoints'); | 
 |  | 
 |     const elP = new LargeBoxPath({ | 
 |         shape: {points: largePoints}, | 
 |         __sign: 1, | 
 |         ignoreCoarsePointer: true | 
 |     }); | 
 |     group.add(elP); | 
 |     const elN = new LargeBoxPath({ | 
 |         shape: {points: largePoints}, | 
 |         __sign: -1, | 
 |         ignoreCoarsePointer: true | 
 |     }); | 
 |     group.add(elN); | 
 |     const elDoji = new LargeBoxPath({ | 
 |         shape: {points: largePoints}, | 
 |         __sign: 0, | 
 |         ignoreCoarsePointer: true | 
 |     }); | 
 |     group.add(elDoji); | 
 |  | 
 |     setLargeStyle(1, elP, seriesModel, data); | 
 |     setLargeStyle(-1, elN, seriesModel, data); | 
 |     setLargeStyle(0, elDoji, seriesModel, data); | 
 |  | 
 |     if (incremental) { | 
 |         elP.incremental = true; | 
 |         elN.incremental = true; | 
 |     } | 
 |  | 
 |     if (progressiveEls) { | 
 |         progressiveEls.push(elP, elN); | 
 |     } | 
 | } | 
 |  | 
 | function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickSeriesModel, data: SeriesData) { | 
 |     // TODO put in visual? | 
 |     let borderColor = seriesModel.get(['itemStyle', sign > 0 ? 'borderColor' : 'borderColor0']) | 
 |         // Use color for border color by default. | 
 |         || seriesModel.get(['itemStyle', sign > 0 ? 'color' : 'color0']); | 
 |     if (sign === 0) { | 
 |         borderColor = seriesModel.get(['itemStyle', 'borderColorDoji']); | 
 |     } | 
 |  | 
 |     // Color must be excluded. | 
 |     // Because symbol provide setColor individually to set fill and stroke | 
 |     const itemStyle = seriesModel.getModel('itemStyle').getItemStyle(SKIP_PROPS); | 
 |  | 
 |     el.useStyle(itemStyle); | 
 |     el.style.fill = null; | 
 |     el.style.stroke = borderColor; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | export default CandlestickView; |