| /* |
| * 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 } 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 List from '../data/List'; |
| |
| 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]: Becuase 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 {} |
| |
| /** |
| * Highlight series or specified data item. |
| */ |
| highlight(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { |
| toggleHighlight(seriesModel.getData(), payload, 'emphasis'); |
| } |
| |
| /** |
| * Downplay series or specified data item. |
| */ |
| downplay(seriesModel: SeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { |
| toggleHighlight(seriesModel.getData(), 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); |
| } |
| |
| 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) { |
| (state === 'emphasis' ? enterEmphasis : leaveEmphasis)(el, highlightDigit); |
| } |
| } |
| |
| function toggleHighlight(data: List, 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; |