From c1a157ee9dd37879695412e568a4c8b41c2f7cc9 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 14 Mar 2023 18:16:38 +0800 Subject: [PATCH 01/10] WIP(ssr): add meta-data and test case #18334 --- src/util/innerStore.ts | 12 +++++ test/ssr.html | 102 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test/ssr.html diff --git a/src/util/innerStore.ts b/src/util/innerStore.ts index dbaaecf040..c292eb7c68 100644 --- a/src/util/innerStore.ts +++ b/src/util/innerStore.ts @@ -70,7 +70,19 @@ export const setCommonECData = (seriesIndex: number, dataType: SeriesDataType, d childECData.seriesIndex = seriesIndex; childECData.dataIndex = dataIdx; childECData.dataType = dataType; + child.__metaData = { + type: 'seriesItem', + seriesIndex, + dataIndex: dataIdx + }; }); } + else { + el.__metaData = { + type: 'seriesItem', + seriesIndex, + dataIndex: dataIdx + }; + } } }; diff --git a/test/ssr.html b/test/ssr.html new file mode 100644 index 0000000000..df4618e4df --- /dev/null +++ b/test/ssr.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + From ed1c176d56d9615030fbd489c4b1ae76795cd8c6 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 21 Mar 2023 10:17:04 +0800 Subject: [PATCH 02/10] WIP --- src/component/legend/LegendView.ts | 24 ++++++ test/ssr.html | 126 ++++++++++++++++++++++------- 2 files changed, 121 insertions(+), 29 deletions(-) diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 5b4a342599..a78b4400e0 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -225,6 +225,15 @@ class LegendView extends ComponentView { .on('mouseover', curry(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId)) .on('mouseout', curry(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId)); + itemGroup.eachChild(child => { + console.log(child.type, child.id); + child.__metaData = { + type: 'legendItem', + seriesIndex: seriesModel.seriesIndex, + dataIndex + }; + }); + legendDrawnMap.set(name, true); } else { @@ -269,6 +278,15 @@ class LegendView extends ComponentView { .on('mouseover', curry(dispatchHighlightAction, null, name, api, excludeSeriesId)) .on('mouseout', curry(dispatchDownplayAction, null, name, api, excludeSeriesId)); + itemGroup.eachChild(child => { + console.log(child.type, child.id); + child.__metaData = { + type: 'legendItem', + seriesIndex: seriesModel.seriesIndex, + dataIndex + }; + }); + legendDrawnMap.set(name, true); } @@ -430,6 +448,12 @@ class LegendView extends ComponentView { shape: itemGroup.getBoundingRect(), invisible: true }); + console.log(hitRect.id) + hitRect.__metaData = { + type: 'legendItem', + seriesIndex: seriesModel.seriesIndex, + dataIndex + }; const tooltipModel = legendItemModel.getModel('tooltip') as Model>; diff --git a/test/ssr.html b/test/ssr.html index df4618e4df..8fedced180 100644 --- a/test/ssr.html +++ b/test/ssr.html @@ -33,6 +33,38 @@ + - +

+ 1. It should have initial animation
+ 2. It should use lighter color when hovering on the bars
+ 3. It should toggle the series when clicking the legend
+ 4. It should update the series when clicking the bars
+

+

Big data render time

+
+ + + From 0e7eac71768363c7648c4680fa4088ded96642b3 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 31 May 2023 17:15:08 +0800 Subject: [PATCH 05/10] feat(ssr): improve code --- src/component/legend/LegendView.ts | 24 ++++++++++++------------ test/ssr.html | 23 ++++++++++++++++++++--- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 50241b625d..d570c6e044 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -226,10 +226,11 @@ class LegendView extends ComponentView { .on('mouseout', curry(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId)); itemGroup.eachChild(child => { - child.__metaData = { + child.__metaData = child.__metaData || { type: 'legendItem', - seriesIndex: seriesModel.seriesIndex, - dataIndex + series_index: seriesModel.seriesIndex, + data_index: dataIndex, + name }; }); @@ -278,10 +279,11 @@ class LegendView extends ComponentView { .on('mouseout', curry(dispatchDownplayAction, null, name, api, excludeSeriesId)); itemGroup.eachChild(child => { - child.__metaData = { + child.__metaData = child.__metaData || { type: 'legendItem', - seriesIndex: seriesModel.seriesIndex, - dataIndex + series_index: seriesModel.seriesIndex, + data_index: dataIndex, + name }; }); @@ -444,13 +446,11 @@ class LegendView extends ComponentView { // Add a invisible rect to increase the area of mouse hover const hitRect = new graphic.Rect({ shape: itemGroup.getBoundingRect(), - invisible: true + style: { + // Cannot use 'invisible' because SVG SSR will miss the node + fill: 'transparent' + } }); - hitRect.__metaData = { - type: 'legendItem', - seriesIndex: seriesModel.seriesIndex, - dataIndex - }; const tooltipModel = legendItemModel.getModel('tooltip') as Model>; diff --git a/test/ssr.html b/test/ssr.html index 94926660d3..d5eec1c797 100644 --- a/test/ssr.html +++ b/test/ssr.html @@ -38,6 +38,16 @@ const svgRoot = dom.querySelector('svg'); const children = svgRoot.children; + function getIndex(child, attr) { + const index = child.getAttribute(attr); + if (index) { + return parseInt(index, 10); + } + else { + return null; + } + } + const events = options.on; if (events) { for (let eventName in events) { @@ -50,8 +60,9 @@ events[eventName]({ type: eventName, nodeType: type, - seriesIndex: child.getAttribute('ecmeta_seriesindex'), - dataIndex: child.getAttribute('ecmeta_dataindex'), + seriesIndex: getIndex(child, 'ecmeta_series_index'), + dataIndex: getIndex(child, 'ecmeta_data_index'), + name: child.getAttribute('ecmeta_name'), event: e, }); }); @@ -84,6 +95,7 @@

2. It should use lighter color when hovering on the bars
3. It should toggle the series when clicking the legend
4. It should update the series when clicking the bars
+ 5. The third series is silent so it cannot be lighter when hovering

@@ -111,6 +123,11 @@

Big data render time

type: 'bar', name: 'b', data: [63, 53, 73, 23, 43, 23, 43] + }, { + type: 'bar', + name: 'silent', + data: [32, 55, 31, 65, 54, 43, 65], + silent: true }]; let aShow = true; let bShow = true; @@ -155,7 +172,7 @@

Big data render time

if (params.nodeType === 'legendItem') { serverChart.dispatchAction({ type: 'legendToggleSelect', - name: params.seriesIndex === '0' ? 'a' : 'b' + name: params.name }); update(); } From b2ff198acbdd33dba9e4729b0dd0fd34f35d383e Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 13 Jun 2023 17:12:34 +0800 Subject: [PATCH 06/10] feat(ssr): call registerSSRDataGetter instead of metaData --- src/component/legend/LegendView.ts | 21 +++++++++------------ src/core/echarts.ts | 13 +++++++++++++ src/util/innerStore.ts | 18 ++++++------------ test/ssr.html | 18 +++++++----------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index d570c6e044..14392d33f2 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -50,6 +50,7 @@ import {LineStyleProps} from '../../model/mixin/lineStyle'; import {createSymbol, ECSymbol} from '../../util/symbol'; import SeriesModel from '../../model/Series'; import { createOrUpdatePatternFromDecal } from '../../util/decal'; +import { getECData } from '../../util/innerStore'; const curry = zrUtil.curry; const each = zrUtil.each; @@ -226,12 +227,10 @@ class LegendView extends ComponentView { .on('mouseout', curry(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId)); itemGroup.eachChild(child => { - child.__metaData = child.__metaData || { - type: 'legendItem', - series_index: seriesModel.seriesIndex, - data_index: dataIndex, - name - }; + const ecData = getECData(child); + ecData.seriesIndex = seriesModel.seriesIndex; + ecData.dataIndex = dataIndex; + ecData.ssrType = 'legend'; }); legendDrawnMap.set(name, true); @@ -279,12 +278,10 @@ class LegendView extends ComponentView { .on('mouseout', curry(dispatchDownplayAction, null, name, api, excludeSeriesId)); itemGroup.eachChild(child => { - child.__metaData = child.__metaData || { - type: 'legendItem', - series_index: seriesModel.seriesIndex, - data_index: dataIndex, - name - }; + const ecData = getECData(child); + ecData.seriesIndex = seriesModel.seriesIndex; + ecData.dataIndex = dataIndex; + ecData.ssrType = 'legend'; }); legendDrawnMap.set(name, true); diff --git a/src/core/echarts.ts b/src/core/echarts.ts index a332312f3a..66df6cfcfc 100644 --- a/src/core/echarts.ts +++ b/src/core/echarts.ts @@ -428,6 +428,15 @@ class ECharts extends Eventful { : devUseDirtyRect; } + zrender.registerSSRDataGetter(el => { + const ecData = getECData(el); + const hashMap = createHashMap(); + hashMap.set('series_index', ecData.seriesIndex); + hashMap.set('data_index', ecData.dataIndex); + hashMap.set('ssr_type', ecData.ssrType); + return hashMap; + }); + const zr = this._zr = zrender.init(dom, { renderer: opts.renderer || defaultRenderer, devicePixelRatio: opts.devicePixelRatio, @@ -2760,6 +2769,10 @@ export function getInstanceById(key: string): EChartsType | undefined { return instances[key]; } +export function registerSSRDataGetter() { + +} + /** * Register theme */ diff --git a/src/util/innerStore.ts b/src/util/innerStore.ts index c292eb7c68..a1ecb2a78c 100644 --- a/src/util/innerStore.ts +++ b/src/util/innerStore.ts @@ -23,6 +23,9 @@ import { ComponentMainType, ComponentItemTooltipOption } from './types'; import { makeInner } from './model'; + +export type SSRItemType = 'chart' | 'legend'; + /** * ECData stored on graphic element */ @@ -34,6 +37,7 @@ export interface ECData { dataType?: SeriesDataType; focus?: InnerFocus; blurScope?: BlurScope; + ssrType?: SSRItemType; // Required by `tooltipConfig` and `focus`. componentMainType?: ComponentMainType; @@ -62,6 +66,7 @@ export const setCommonECData = (seriesIndex: number, dataType: SeriesDataType, d ecData.dataIndex = dataIdx; ecData.dataType = dataType; ecData.seriesIndex = seriesIndex; + ecData.ssrType = 'chart'; // TODO: not store dataIndex on children. if (el.type === 'group') { @@ -70,19 +75,8 @@ export const setCommonECData = (seriesIndex: number, dataType: SeriesDataType, d childECData.seriesIndex = seriesIndex; childECData.dataIndex = dataIdx; childECData.dataType = dataType; - child.__metaData = { - type: 'seriesItem', - seriesIndex, - dataIndex: dataIdx - }; + childECData.ssrType === 'chart'; }); } - else { - el.__metaData = { - type: 'seriesItem', - seriesIndex, - dataIndex: dataIdx - }; - } } }; diff --git a/test/ssr.html b/test/ssr.html index d5eec1c797..02fe3e139d 100644 --- a/test/ssr.html +++ b/test/ssr.html @@ -54,15 +54,15 @@ if (typeof events[eventName] === 'function') { for (let i = 0; i < children.length; i++) { const child = children[i]; - const type = child.getAttribute('ecmeta_type'); - if (type) { + const type = child.getAttribute('ecmeta_ssr_type'); + const silent = child.getAttribute('ecmeta_silent') === 'true'; + if (type && !silent) { child.addEventListener(eventName, e => { events[eventName]({ type: eventName, - nodeType: type, + ssrType: type, seriesIndex: getIndex(child, 'ecmeta_series_index'), dataIndex: getIndex(child, 'ecmeta_data_index'), - name: child.getAttribute('ecmeta_name'), event: e, }); }); @@ -92,7 +92,7 @@

1. It should have initial animation
- 2. It should use lighter color when hovering on the bars
+ 2. It should use lighter color when hovering on the bars of a and b series
3. It should toggle the series when clicking the legend
4. It should update the series when clicking the bars
5. The third series is silent so it cannot be lighter when hovering
@@ -129,8 +129,6 @@

Big data render time

data: [32, 55, 31, 65, 54, 43, 65], silent: true }]; - let aShow = true; - let bShow = true; const option = { legend: { @@ -169,10 +167,10 @@

Big data render time

// client should call API to get the new chart // and `serverChart.dispatchAction` should be // done on the server. - if (params.nodeType === 'legendItem') { + if (params.ssrType === 'legend') { serverChart.dispatchAction({ type: 'legendToggleSelect', - name: params.name + name: ['a', 'b'][params.seriesIndex] }); update(); } @@ -220,8 +218,6 @@

Big data render time

name: 'a', data }]; - let aShow = true; - let bShow = true; const option = { xAxis: { From a63297085484ad822991d94c4544592f48d49829 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 13 Jun 2023 18:57:10 +0800 Subject: [PATCH 07/10] chore(ssr): update building ssr client --- .gitignore | 1 + build/build-ssr-client.js | 36 +++++++++++++++++++ build/build.js | 6 ++++ build/config.js | 16 +++++++++ build/pre-publish.js | 30 +++++++++++++++- package.json | 3 +- ssr/client/src/index.ts | 74 +++++++++++++++++++++++++++++++++++++++ test/lib/config.js | 1 + test/ssr.html | 46 ++---------------------- 9 files changed, 168 insertions(+), 45 deletions(-) create mode 100644 build/build-ssr-client.js create mode 100644 ssr/client/src/index.ts diff --git a/.gitignore b/.gitignore index ba75419260..34c814925f 100644 --- a/.gitignore +++ b/.gitignore @@ -195,6 +195,7 @@ todo /index.blank.js /extension-esm /extension +/ssr/client/lib /core.js /core.d.ts /charts.js diff --git a/build/build-ssr-client.js b/build/build-ssr-client.js new file mode 100644 index 0000000000..6f89a44322 --- /dev/null +++ b/build/build-ssr-client.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +/* +* 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. +*/ + +const rollup = require('rollup'); +const chalk = require('chalk'); +const createSSRClient = require('./config').createSSRClient; + +async function run() { + const config = createSSRClient({ min: true }); + console.log(chalk.cyan.dim(`Bundling ${config.input}`)) + + console.time('rollup build ssr-client'); + const bundle = await rollup.rollup(config); + await bundle.write(config.output); + console.timeEnd('rollup build ssr-client'); +} + +run(); diff --git a/build/build.js b/build/build.js index d6e9cc7a44..fbf6e8820d 100755 --- a/build/build.js +++ b/build/build.js @@ -97,6 +97,12 @@ async function run() { ]; await build(cfgs); } + else if (buildType === 'ssr') { + const cfgs = [ + config.createSSRClient(opt) + ]; + await build(cfgs); + } else if (buildType === 'myTransform') { const cfgs = [ config.createMyTransform(opt) diff --git a/build/config.js b/build/config.js index f07f008058..092f7b23a5 100644 --- a/build/config.js +++ b/build/config.js @@ -174,3 +174,19 @@ exports.createMyTransform = function (opt) { ) }; }; + +exports.createSSRClient = function (opt) { + const input = nodePath.resolve(ecDir, `ssr/client/lib/index.js`); + + return { + plugins: [nodeResolvePlugin()], + input: input, + output: createOutputs( + nodePath.resolve(ecDir, `ssr/client/dist/index`), + opt, + { + name: 'echarts-ssr-client' + } + ) + }; +}; diff --git a/build/pre-publish.js b/build/pre-publish.js index 75d7c771a9..91914e5768 100644 --- a/build/pre-publish.js +++ b/build/pre-publish.js @@ -67,6 +67,14 @@ const extensionSrcGlobby = { }; const extensionSrcDir = nodePath.resolve(ecDir, 'extension-src'); const extensionESMDir = nodePath.resolve(ecDir, 'extension'); +const ssrClientGlobby = { + patterns: [ + 'ssr/client/src/**/*.ts' + ], + cwd: ecDir +}; +const ssrClientSrcDir = nodePath.resolve(ecDir, 'ssr/client/src'); +const ssrClientESMDir = nodePath.resolve(ecDir, 'ssr/client/lib'); const typesDir = nodePath.resolve(ecDir, 'types'); const esmDir = 'lib'; @@ -135,6 +143,26 @@ const compileWorkList = [ after: async function () { await transformLibFiles(extensionESMDir, 'lib'); } + }, + { + logLabel: 'ssr client ts -> js-esm', + compilerOptionsOverride: { + module: 'ES2015', + declaration: false, + rootDir: ssrClientSrcDir, + outDir: ssrClientESMDir + }, + srcGlobby: ssrClientGlobby, + transformOptions: { + filesGlobby: {patterns: ['**/*.js'], cwd: ssrClientESMDir}, + transformDEV: true + }, + before: async function () { + fsExtra.removeSync(ssrClientESMDir); + }, + after: async function () { + await transformLibFiles(ssrClientESMDir, 'lib'); + } } ]; @@ -221,7 +249,7 @@ async function tsCompile(compilerOptionsOverride, srcPathList) { && compilerOptionsOverride.rootDir && compilerOptionsOverride.outDir ); - + console.log(compilerOptionsOverride, srcPathList) let compilerOptions = { ...tsConfig.compilerOptions, ...compilerOptionsOverride, diff --git a/package.json b/package.json index 5cdb572da7..ca4a24eebd 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,11 @@ "build:i18n": "node build/build-i18n.js", "build:lib": "node build/build.js --prepublish", "build:extension": "node build/build.js --type extension", + "build:ssr": "node build/build.js --type ssr", "dev:fast": "node build/build-i18n.js && node build/dev-fast.js", "dev": "npx -y concurrently -n build,server \"npm run dev:fast\" \"npx -y http-server -c-1 -s -o test\"", "prepare": "npm run build:lib", - "release": "npm run build:lib && npm run build:i18n && npm run build && npm run build:esm && npm run build:extension", + "release": "npm run build:lib && npm run build:i18n && npm run build && npm run build:esm && npm run build:extension && npm run build:ssr", "help": "node build/build.js --help", "test:visual": "node test/runTest/server.js", "test": "npx jest --config test/ut/jest.config.js", diff --git a/ssr/client/src/index.ts b/ssr/client/src/index.ts new file mode 100644 index 0000000000..dabda9a96b --- /dev/null +++ b/ssr/client/src/index.ts @@ -0,0 +1,74 @@ +/* +* 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. +*/ + +export interface ECSSRClientEventParams {} + +export interface ECSSRClientOptions { + on?: { + mouseover?: (params: ECSSRClientEventParams) => void, + mouseout?: (params: ECSSRClientEventParams) => void, + click?: (params: ECSSRClientEventParams) => void + } +} + +export type ECSSREvent = 'mouseover' | 'mouseout' | 'click'; + +export function initEChartsSSRClient(dom: HTMLElement, options: ECSSRClientOptions) { + const svgRoot = dom.querySelector('svg'); + if (!svgRoot) { + console.error('No SVG element found in the DOM.'); + return; + } + + const children = svgRoot.children; + + function getIndex(child: Element, attr: string) { + const index = child.getAttribute(attr); + if (index) { + return parseInt(index, 10); + } + else { + return null; + } + } + + const events = options.on; + if (events) { + for (let eventName in events) { + if (typeof events[eventName as ECSSREvent] === 'function') { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const type = child.getAttribute('ecmeta_ssr_type'); + const silent = child.getAttribute('ecmeta_silent') === 'true'; + if (type && !silent) { + child.addEventListener(eventName, e => { + events[eventName as ECSSREvent]({ + type: eventName, + ssrType: type, + seriesIndex: getIndex(child, 'ecmeta_series_index'), + dataIndex: getIndex(child, 'ecmeta_data_index'), + event: e, + }); + }); + } + } + } + } + } +} diff --git a/test/lib/config.js b/test/lib/config.js index bc36037e79..69bda4ae89 100644 --- a/test/lib/config.js +++ b/test/lib/config.js @@ -82,6 +82,7 @@ 'map': 'data/map', 'i18n': '../i18n', 'extension': '../dist/extension', + 'ssrClient': '../ssr-client/index' } }); } diff --git a/test/ssr.html b/test/ssr.html index 02fe3e139d..e582f93850 100644 --- a/test/ssr.html +++ b/test/ssr.html @@ -33,46 +33,6 @@ -