|  | /* | 
|  | * 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 {each} from 'zrender/src/core/util'; | 
|  | import Group from 'zrender/src/graphic/Group'; | 
|  | import * as componentUtil from '../util/component'; | 
|  | import * as clazzUtil from '../util/clazz'; | 
|  | import * as modelUtil from '../util/model'; | 
|  | import { enterEmphasis, leaveEmphasis, getHighlightDigit, isHighDownDispatcher } from '../util/states'; | 
|  | import {createTask, TaskResetCallbackReturn} from '../core/task'; | 
|  | import createRenderPlanner from '../chart/helper/createRenderPlanner'; | 
|  | import SeriesModel from '../model/Series'; | 
|  | import GlobalModel from '../model/Global'; | 
|  | import ExtensionAPI from '../core/ExtensionAPI'; | 
|  | import Element from 'zrender/src/Element'; | 
|  | import { | 
|  | Payload, ViewRootGroup, ECActionEvent, EventQueryItem, | 
|  | StageHandlerPlanReturn, DisplayState, StageHandlerProgressParams, ECElementEvent | 
|  | } from '../util/types'; | 
|  | import { SeriesTaskContext, SeriesTask } from '../core/Scheduler'; | 
|  | import SeriesData from '../data/SeriesData'; | 
|  | import { traverseElements } from '../util/graphic'; | 
|  | import { error } from '../util/log'; | 
|  |  | 
|  | const inner = modelUtil.makeInner<{ | 
|  | updateMethod: keyof ChartView | 
|  | }, Payload>(); | 
|  | const renderPlanner = createRenderPlanner(); | 
|  |  | 
|  | interface ChartView { | 
|  | /** | 
|  | * Rendering preparation in progressive mode. | 
|  | * Implement it if needed. | 
|  | */ | 
|  | incrementalPrepareRender( | 
|  | seriesModel: SeriesModel, | 
|  | ecModel: GlobalModel, | 
|  | api: ExtensionAPI, | 
|  | payload: Payload | 
|  | ): void; | 
|  |  | 
|  | /** | 
|  | * Render in progressive mode. | 
|  | * Implement it if needed. | 
|  | * @param params See taskParams in `stream/task.js` | 
|  | */ | 
|  | incrementalRender( | 
|  | params: StageHandlerProgressParams, | 
|  | seriesModel: SeriesModel, | 
|  | ecModel: GlobalModel, | 
|  | api: ExtensionAPI, | 
|  | payload: Payload | 
|  | ): void; | 
|  |  | 
|  | /** | 
|  | * Update transform directly. | 
|  | * Implement it if needed. | 
|  | */ | 
|  | updateTransform( | 
|  | seriesModel: SeriesModel, | 
|  | ecModel: GlobalModel, | 
|  | api: ExtensionAPI, | 
|  | payload: Payload | 
|  | ): void | {update: true}; | 
|  |  | 
|  | /** | 
|  | * The view contains the given point. | 
|  | * Implement it if needed. | 
|  | */ | 
|  | containPoint( | 
|  | point: number[], seriesModel: SeriesModel | 
|  | ): boolean; | 
|  |  | 
|  | /** | 
|  | * Pass only when return `true`. | 
|  | * Implement it if needed. | 
|  | */ | 
|  | filterForExposedEvent( | 
|  | eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECActionEvent | ECElementEvent | 
|  | ): boolean; | 
|  | } | 
|  | class ChartView { | 
|  |  | 
|  | // [Caution]: Because this class or desecendants can be used as `XXX.extend(subProto)`, | 
|  | // the class members must not be initialized in constructor or declaration place. | 
|  | // Otherwise there is bad case: | 
|  | //   class A {xxx = 1;} | 
|  | //   enableClassExtend(A); | 
|  | //   class B extends A {} | 
|  | //   var C = B.extend({xxx: 5}); | 
|  | //   var c = new C(); | 
|  | //   console.log(c.xxx); // expect 5 but always 1. | 
|  |  | 
|  | // @readonly | 
|  | type: string; | 
|  |  | 
|  | readonly group: ViewRootGroup; | 
|  |  | 
|  | readonly uid: string; | 
|  |  | 
|  | readonly renderTask: SeriesTask; | 
|  |  | 
|  | /** | 
|  | * Ignore label line update in global stage. Will handle it in chart itself. | 
|  | * Used in pie / funnel | 
|  | */ | 
|  | ignoreLabelLineUpdate: boolean; | 
|  |  | 
|  | // ---------------------- | 
|  | // Injectable properties | 
|  | // ---------------------- | 
|  | __alive: boolean; | 
|  | __model: SeriesModel; | 
|  | __id: string; | 
|  |  | 
|  | static protoInitialize = (function () { | 
|  | const proto = ChartView.prototype; | 
|  | proto.type = 'chart'; | 
|  | })(); | 
|  |  | 
|  |  | 
|  | constructor() { | 
|  | this.group = new Group(); | 
|  | this.uid = componentUtil.getUID('viewChart'); | 
|  |  | 
|  | this.renderTask = createTask<SeriesTaskContext>({ | 
|  | plan: renderTaskPlan, | 
|  | reset: renderTaskReset | 
|  | }); | 
|  | this.renderTask.context = {view: this} as SeriesTaskContext; | 
|  | } | 
|  |  | 
|  | init(ecModel: GlobalModel, api: ExtensionAPI): void {} | 
|  |  | 
|  | render(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | if (__DEV__) { | 
|  | throw new Error('render method must been implemented'); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Highlight series or specified data item. | 
|  | */ | 
|  | highlight(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | const data = seriesModel.getData(payload && payload.dataType); | 
|  | if (!data) { | 
|  | if (__DEV__) { | 
|  | error(`Unknown dataType ${payload.dataType}`); | 
|  | } | 
|  | return; | 
|  | } | 
|  | toggleHighlight(data, payload, 'emphasis'); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Downplay series or specified data item. | 
|  | */ | 
|  | downplay(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | const data = seriesModel.getData(payload && payload.dataType); | 
|  | if (!data) { | 
|  | if (__DEV__) { | 
|  | error(`Unknown dataType ${payload.dataType}`); | 
|  | } | 
|  | return; | 
|  | } | 
|  | toggleHighlight(data, payload, 'normal'); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Remove self. | 
|  | */ | 
|  | remove(ecModel: GlobalModel, api: ExtensionAPI): void { | 
|  | this.group.removeAll(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dispose self. | 
|  | */ | 
|  | dispose(ecModel: GlobalModel, api: ExtensionAPI): void {} | 
|  |  | 
|  |  | 
|  | updateView(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | this.render(seriesModel, ecModel, api, payload); | 
|  | } | 
|  |  | 
|  | // FIXME never used? | 
|  | updateLayout(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | this.render(seriesModel, ecModel, api, payload); | 
|  | } | 
|  |  | 
|  | // FIXME never used? | 
|  | updateVisual(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | 
|  | this.render(seriesModel, ecModel, api, payload); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Traverse the new rendered elements. | 
|  | * | 
|  | * It will traverse the new added element in progressive rendering. | 
|  | * And traverse all in normal rendering. | 
|  | */ | 
|  | eachRendered(cb: (el: Element) => boolean | void) { | 
|  | traverseElements(this.group, cb); | 
|  | } | 
|  |  | 
|  | static markUpdateMethod(payload: Payload, methodName: keyof ChartView): void { | 
|  | inner(payload).updateMethod = methodName; | 
|  | } | 
|  |  | 
|  | static registerClass: clazzUtil.ClassManager['registerClass']; | 
|  | }; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Set state of single element | 
|  | */ | 
|  | function elSetState(el: Element, state: DisplayState, highlightDigit: number) { | 
|  | if (el && isHighDownDispatcher(el)) { | 
|  | (state === 'emphasis' ? enterEmphasis : leaveEmphasis)(el, highlightDigit); | 
|  | } | 
|  | } | 
|  |  | 
|  | function toggleHighlight(data: SeriesData, payload: Payload, state: DisplayState) { | 
|  | const dataIndex = modelUtil.queryDataIndex(data, payload); | 
|  |  | 
|  | const highlightDigit = (payload && payload.highlightKey != null) | 
|  | ? getHighlightDigit(payload.highlightKey) | 
|  | : null; | 
|  |  | 
|  | if (dataIndex != null) { | 
|  | each(modelUtil.normalizeToArray(dataIndex), function (dataIdx) { | 
|  | elSetState(data.getItemGraphicEl(dataIdx), state, highlightDigit); | 
|  | }); | 
|  | } | 
|  | else { | 
|  | data.eachItemGraphicEl(function (el) { | 
|  | elSetState(el, state, highlightDigit); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | export type ChartViewConstructor = typeof ChartView | 
|  | & clazzUtil.ExtendableConstructor | 
|  | & clazzUtil.ClassManager; | 
|  |  | 
|  | clazzUtil.enableClassExtend(ChartView as ChartViewConstructor, ['dispose']); | 
|  | clazzUtil.enableClassManagement(ChartView as ChartViewConstructor); | 
|  |  | 
|  |  | 
|  | function renderTaskPlan(context: SeriesTaskContext): StageHandlerPlanReturn { | 
|  | return renderPlanner(context.model); | 
|  | } | 
|  |  | 
|  | function renderTaskReset(context: SeriesTaskContext): TaskResetCallbackReturn<SeriesTaskContext> { | 
|  | const seriesModel = context.model; | 
|  | const ecModel = context.ecModel; | 
|  | const api = context.api; | 
|  | const payload = context.payload; | 
|  | // FIXME: remove updateView updateVisual | 
|  | const progressiveRender = seriesModel.pipelineContext.progressiveRender; | 
|  | const view = context.view; | 
|  |  | 
|  | const updateMethod = payload && inner(payload).updateMethod; | 
|  | const methodName: keyof ChartView = progressiveRender | 
|  | ? 'incrementalPrepareRender' | 
|  | : (updateMethod && view[updateMethod]) | 
|  | ? updateMethod | 
|  | // `appendData` is also supported when data amount | 
|  | // is less than progressive threshold. | 
|  | : 'render'; | 
|  |  | 
|  | if (methodName !== 'render') { | 
|  | (view[methodName] as any)(seriesModel, ecModel, api, payload); | 
|  | } | 
|  |  | 
|  | return progressMethodMap[methodName]; | 
|  | } | 
|  |  | 
|  | const progressMethodMap: {[method: string]: TaskResetCallbackReturn<SeriesTaskContext>} = { | 
|  | incrementalPrepareRender: { | 
|  | progress: function (params: StageHandlerProgressParams, context: SeriesTaskContext): void { | 
|  | context.view.incrementalRender( | 
|  | params, context.model, context.ecModel, context.api, context.payload | 
|  | ); | 
|  | } | 
|  | }, | 
|  | render: { | 
|  | // Put view.render in `progress` to support appendData. But in this case | 
|  | // view.render should not be called in reset, otherwise it will be called | 
|  | // twise. Use `forceFirstProgress` to make sure that view.render is called | 
|  | // in any cases. | 
|  | forceFirstProgress: true, | 
|  | progress: function (params: StageHandlerProgressParams, context: SeriesTaskContext): void { | 
|  | context.view.render( | 
|  | context.model, context.ecModel, context.api, context.payload | 
|  | ); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | export default ChartView; |