From ad1d4878dc1e4efec4ce102a7ca9ca0c9ba1d251 Mon Sep 17 00:00:00 2001 From: semla Date: Fri, 14 Jun 2024 11:58:07 +0200 Subject: [PATCH 01/25] feat(aria): omit columns --- src/util/types.ts | 3 ++- src/visual/aria.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/types.ts b/src/util/types.ts index 8fd9435013..81e58553c9 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1727,7 +1727,8 @@ export interface AriaLabelOption { separator?: { middle?: string; end?: string; - } + }, + columnsToExclude?: number[] } } diff --git a/src/visual/aria.ts b/src/visual/aria.ts index fae8999877..e87994e204 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -216,9 +216,10 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { const middleSeparator = labelModel.get(['data', 'separator', 'middle']); const endSeparator = labelModel.get(['data', 'separator', 'end']); + const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); const dataLabels = []; for (let i = 0; i < data.count(); i++) { - if (i < maxDataCnt) { + if (i < maxDataCnt && !columnsToExclude?.includes(i)) { const name = data.getName(i); const value = data.getValues(i); const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); From 64b51e91d07e86a13f1e5d09729b6baa1b8b36fe Mon Sep 17 00:00:00 2001 From: semla Date: Sat, 15 Jun 2024 20:20:36 +0200 Subject: [PATCH 02/25] add test for excluding data from aria --- test/ut/spec/series/aria.test.ts | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 test/ut/spec/series/aria.test.ts diff --git a/test/ut/spec/series/aria.test.ts b/test/ut/spec/series/aria.test.ts new file mode 100644 index 0000000000..97b297f386 --- /dev/null +++ b/test/ut/spec/series/aria.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { EChartsType } from '@/src/echarts'; +import { createChart } from '../../core/utHelper'; + +describe('omit some aria data', function() { + + let chart: EChartsType; + beforeEach(function() { + chart = createChart(); + }); + + afterEach(function() { + chart.dispose(); + }); + + it('data for column index in columnsToExclude (Tuesday, second column) should be omitted from Aria', async () => { + const option = { + aria: { + enabled: true, + data: { + columnsToExclude: [1] + } + }, + xAxis: { + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: [150, 230, 224, 218, 135, 147, 260], + type: 'line' + } + ] + }; + chart.setOption(option); + const el = chart.getDom(); + const ariaValue = el.getAttribute('aria-label'); + expect(ariaValue).not.toContain('Tue'); + }); + + it('data for columns in columnsToExclude (first and seventh, Monday and Sunday) should be omitted from Aria', async () => { + const option = { + aria: { + enabled: true, + data: { + columnsToExclude: [0,6] + } + }, + xAxis: { + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: [150, 230, 224, 218, 135, 147, 260], + type: 'line' + } + ] + }; + chart.setOption(option); + const el = chart.getDom(); + const ariaValue = el.getAttribute('aria-label'); + expect(ariaValue).not.toContain('Mon'); + expect(ariaValue).not.toContain('Sun'); + }); + +}); From 5ad8f6e4736a2686b19f78704b6b3ada4a478fd5 Mon Sep 17 00:00:00 2001 From: semla Date: Sat, 15 Jun 2024 21:02:48 +0200 Subject: [PATCH 03/25] exclude aria columns directly maxDataCnt should then compare towards the remaining columns --- src/visual/aria.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/visual/aria.ts b/src/visual/aria.ts index e87994e204..8e302b0c70 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -202,7 +202,11 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { seriesType: getSeriesTypeName(seriesModel.subType as SeriesTypes) }); - const data = seriesModel.getData(); + let data = seriesModel.getData(); + const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); + if (columnsToExclude) { + data = data.filterSelf(idx => !columnsToExclude?.includes(idx)); + } if (data.count() > maxDataCnt) { // Show part of data const partialLabel = labelModel.get(['data', 'partialData']); @@ -216,10 +220,9 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { const middleSeparator = labelModel.get(['data', 'separator', 'middle']); const endSeparator = labelModel.get(['data', 'separator', 'end']); - const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); const dataLabels = []; for (let i = 0; i < data.count(); i++) { - if (i < maxDataCnt && !columnsToExclude?.includes(i)) { + if (i < maxDataCnt) { const name = data.getName(i); const value = data.getValues(i); const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); From 77580e930fa5083752e0d632e4f6f1d9631dabac Mon Sep 17 00:00:00 2001 From: semla Date: Thu, 20 Jun 2024 10:59:45 +0200 Subject: [PATCH 04/25] fix(aria): filter columns and only aria --- src/visual/aria.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/visual/aria.ts b/src/visual/aria.ts index 8e302b0c70..e87994e204 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -202,11 +202,7 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { seriesType: getSeriesTypeName(seriesModel.subType as SeriesTypes) }); - let data = seriesModel.getData(); - const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); - if (columnsToExclude) { - data = data.filterSelf(idx => !columnsToExclude?.includes(idx)); - } + const data = seriesModel.getData(); if (data.count() > maxDataCnt) { // Show part of data const partialLabel = labelModel.get(['data', 'partialData']); @@ -220,9 +216,10 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { const middleSeparator = labelModel.get(['data', 'separator', 'middle']); const endSeparator = labelModel.get(['data', 'separator', 'end']); + const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); const dataLabels = []; for (let i = 0; i < data.count(); i++) { - if (i < maxDataCnt) { + if (i < maxDataCnt && !columnsToExclude?.includes(i)) { const name = data.getName(i); const value = data.getValues(i); const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); From 1b630cbf553f4400a0410c966c3717201a8648c7 Mon Sep 17 00:00:00 2001 From: semla Date: Thu, 20 Jun 2024 11:12:28 +0200 Subject: [PATCH 05/25] test(aria): add test for not modifying graph data and for nested array --- test/ut/spec/series/aria.test.ts | 72 +++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/test/ut/spec/series/aria.test.ts b/test/ut/spec/series/aria.test.ts index 97b297f386..d1832aa934 100644 --- a/test/ut/spec/series/aria.test.ts +++ b/test/ut/spec/series/aria.test.ts @@ -16,9 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - import { EChartsType } from '@/src/echarts'; -import { createChart } from '../../core/utHelper'; +import { createChart, getECModel } from '../../core/utHelper'; describe('omit some aria data', function() { @@ -59,12 +58,12 @@ describe('omit some aria data', function() { expect(ariaValue).not.toContain('Tue'); }); - it('data for columns in columnsToExclude (first and seventh, Monday and Sunday) should be omitted from Aria', async () => { + it('data for graph should not be omitted', async () => { const option = { aria: { enabled: true, data: { - columnsToExclude: [0,6] + columnsToExclude: [0] } }, xAxis: { @@ -82,10 +81,71 @@ describe('omit some aria data', function() { ] }; chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.getName(0)).toEqual('Mon'); + expect(listData.getValues(0)).toEqual([0, 150]); + expect(listData.count()).toEqual(7); + }); + + it('aria should be omitted correctly in nested array', async () => { + const option = { + aria: { + enabled: true, + data: { + columnsToExclude: [0] + } + }, + xAxis: { + data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27'] + }, + yAxis: {}, + series: [ + { + type: 'candlestick', + data: [ + [20, 34, 10, 38], + [40, 35, 30, 50], + [31, 38, 33, 44], + [38, 15, 5, 42] + ] + } + ] + }; + chart.setOption(option); const el = chart.getDom(); const ariaValue = el.getAttribute('aria-label'); - expect(ariaValue).not.toContain('Mon'); - expect(ariaValue).not.toContain('Sun'); + expect(ariaValue).not.toContain('2017-10-24'); + }); + + + it('data for graph should not be omitted', async () => { + const option = { + aria: { + enabled: true, + data: { + columnsToExclude: [0] + } + }, + xAxis: { + data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27'] + }, + yAxis: {}, + series: [ + { + type: 'candlestick', + data: [ + [20, 34, 10, 38], + [40, 35, 30, 50], + [31, 38, 33, 44], + [38, 15, 5, 42] + ] + } + ] + }; + chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.getName(0)).toEqual('2017-10-24'); + expect(listData.count()).toEqual(4); }); }); From bcd1a81f033eb4a7ef85ee259885e0ced99adbce Mon Sep 17 00:00:00 2001 From: semla Date: Fri, 21 Jun 2024 20:49:24 +0200 Subject: [PATCH 06/25] fix(aria): filter columns, not data items --- src/visual/aria.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/visual/aria.ts b/src/visual/aria.ts index e87994e204..e70fe23c44 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -219,9 +219,10 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); const dataLabels = []; for (let i = 0; i < data.count(); i++) { - if (i < maxDataCnt && !columnsToExclude?.includes(i)) { + if (i < maxDataCnt) { const name = data.getName(i); - const value = data.getValues(i); + const value = !columnsToExclude ? data.getValues(i) + : data.getValues(i).filter((value, j) => !columnsToExclude?.includes(j)); const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); dataLabels.push( replace(dataLabel, { From 9cb5d9a819dcfcb58e46c0b815a912f1d451a897 Mon Sep 17 00:00:00 2001 From: semla Date: Fri, 21 Jun 2024 20:52:49 +0200 Subject: [PATCH 07/25] test(aria): update test to realistic use case that omits lat & long for aria-label --- test/ut/spec/series/aria.test.ts | 168 ++++++++++--------------------- 1 file changed, 55 insertions(+), 113 deletions(-) diff --git a/test/ut/spec/series/aria.test.ts b/test/ut/spec/series/aria.test.ts index d1832aa934..feed8cdf86 100644 --- a/test/ut/spec/series/aria.test.ts +++ b/test/ut/spec/series/aria.test.ts @@ -19,133 +19,75 @@ import { EChartsType } from '@/src/echarts'; import { createChart, getECModel } from '../../core/utHelper'; -describe('omit some aria data', function() { - +describe('aria, omit data', function () { let chart: EChartsType; - beforeEach(function() { + const option = { + 'aria': { + 'enabled': true, + 'data': { + 'columnsToExclude': [0, 1, 2] + }, + }, + 'dataset': [ + { + 'dimensions': [ + 'lng', + 'lat', + 'name', + 'value', + 'capacity', + ], + 'source': [ + [ + 1.58285827, + 42.099784969, + 'Llosa del Cavall (Navès)', + 17.945, + 80, + ], + [ + 0.960270444, + 41.134931354, + 'Riudecanyes', + 0.401, + 5.32, + ], + ] + + } + ], + 'series': [ + { + 'coordinateSystem': 'geo', + 'encode': { + 'itemName': 'name' + }, + 'type': 'scatter', + } + ], + }; + beforeEach(function () { chart = createChart(); }); - afterEach(function() { + afterEach(function () { chart.dispose(); }); - it('data for column index in columnsToExclude (Tuesday, second column) should be omitted from Aria', async () => { - const option = { - aria: { - enabled: true, - data: { - columnsToExclude: [1] - } - }, - xAxis: { - type: 'category', - data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - }, - yAxis: { - type: 'value' - }, - series: [ - { - data: [150, 230, 224, 218, 135, 147, 260], - type: 'line' - } - ] - }; + it('specified columns should be omitted from Aria (geolocation and name)', () => { chart.setOption(option); const el = chart.getDom(); const ariaValue = el.getAttribute('aria-label'); - expect(ariaValue).not.toContain('Tue'); + expect(ariaValue).not.toContain(1.58285827); + expect(ariaValue).not.toContain(42.099784969); + expect(ariaValue).not.toContain(0.960270444); + expect(ariaValue).not.toContain(41.134931354); }); - it('data for graph should not be omitted', async () => { - const option = { - aria: { - enabled: true, - data: { - columnsToExclude: [0] - } - }, - xAxis: { - type: 'category', - data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - }, - yAxis: { - type: 'value' - }, - series: [ - { - data: [150, 230, 224, 218, 135, 147, 260], - type: 'line' - } - ] - }; - chart.setOption(option); - const listData = getECModel(chart).getSeries()[0].getData(); - expect(listData.getName(0)).toEqual('Mon'); - expect(listData.getValues(0)).toEqual([0, 150]); - expect(listData.count()).toEqual(7); - }); - - it('aria should be omitted correctly in nested array', async () => { - const option = { - aria: { - enabled: true, - data: { - columnsToExclude: [0] - } - }, - xAxis: { - data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27'] - }, - yAxis: {}, - series: [ - { - type: 'candlestick', - data: [ - [20, 34, 10, 38], - [40, 35, 30, 50], - [31, 38, 33, 44], - [38, 15, 5, 42] - ] - } - ] - }; - chart.setOption(option); - const el = chart.getDom(); - const ariaValue = el.getAttribute('aria-label'); - expect(ariaValue).not.toContain('2017-10-24'); - }); - - - it('data for graph should not be omitted', async () => { - const option = { - aria: { - enabled: true, - data: { - columnsToExclude: [0] - } - }, - xAxis: { - data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27'] - }, - yAxis: {}, - series: [ - { - type: 'candlestick', - data: [ - [20, 34, 10, 38], - [40, 35, 30, 50], - [31, 38, 33, 44], - [38, 15, 5, 42] - ] - } - ] - }; + it('should not modify the data of the chart', async () => { chart.setOption(option); const listData = getECModel(chart).getSeries()[0].getData(); - expect(listData.getName(0)).toEqual('2017-10-24'); - expect(listData.count()).toEqual(4); + expect(listData.getValues(0)).toEqual([1.58285827, 42.099784969, 'Llosa del Cavall (Navès)', 17.945, 80]); }); }); From da33a83c6e16c82c0668b2da0d590b50b8cba0a3 Mon Sep 17 00:00:00 2001 From: semla Date: Tue, 16 Jul 2024 14:48:18 +0200 Subject: [PATCH 08/25] test(aria): rename test file --- .../ut/spec/series/{aria.test.ts => aria-columns-exclude.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/ut/spec/series/{aria.test.ts => aria-columns-exclude.test.ts} (100%) diff --git a/test/ut/spec/series/aria.test.ts b/test/ut/spec/series/aria-columns-exclude.test.ts similarity index 100% rename from test/ut/spec/series/aria.test.ts rename to test/ut/spec/series/aria-columns-exclude.test.ts From 34b4eed5a1e74bd284de7091e86829326139e16d Mon Sep 17 00:00:00 2001 From: plainheart Date: Mon, 8 Jul 2024 18:40:53 +0800 Subject: [PATCH 09/25] fix(legend): fix legend action is not isolated from other legend components (resolves #20128) --- src/component/legend/LegendView.ts | 3 ++- src/component/legend/legendAction.ts | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 35b0779f2b..9016468240 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -329,7 +329,8 @@ class LegendView extends ComponentView { }, onclick() { api.dispatchAction({ - type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect' + type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect', + legendId: legendModel.id }); } }); diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index 04ed3723a8..2edc27dfc2 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -17,16 +17,22 @@ * under the License. */ -// @ts-nocheck +import {curry, each, hasOwn} from 'zrender/src/core/util'; +import { EChartsExtensionInstallRegisters } from '../../extension'; +import { Payload } from '../../util/types'; +import type GlobalModel from '../../model/Global'; +import type LegendModel from './LegendModel'; -import {curry, each} from 'zrender/src/core/util'; +type LegendSelectMethodNames = + 'select' | 'unSelect' | + 'toggleSelected' | 'toggleSelected' | + 'allSelect' | 'inverseSelect'; -function legendSelectActionHandler(methodName, payload, ecModel) { - const selectedMap = {}; +function legendSelectActionHandler(methodName: LegendSelectMethodNames, payload: Payload, ecModel: GlobalModel) { + const selectedMap: Record = {}; const isToggleSelect = methodName === 'toggleSelected'; - let isSelected; - // Update all legend components - ecModel.eachComponent('legend', function (legendModel) { + let isSelected: boolean; + ecModel.eachComponent({ mainType: 'legend', query: payload }, function (legendModel: LegendModel) { if (isToggleSelect && isSelected != null) { // Force other legend has same selected status // Or the first is toggled to true and other are toggled to false @@ -49,7 +55,7 @@ function legendSelectActionHandler(methodName, payload, ecModel) { return; } const isItemSelected = legendModel.isSelected(name); - if (selectedMap.hasOwnProperty(name)) { + if (hasOwn(selectedMap, name)) { // Unselected if any legend is unselected selectedMap[name] = selectedMap[name] && isItemSelected; } @@ -69,7 +75,7 @@ function legendSelectActionHandler(methodName, payload, ecModel) { }; } -export function installLegendAction(registers) { +export function installLegendAction(registers: EChartsExtensionInstallRegisters) { /** * @event legendToggleSelect * @type {Object} @@ -113,4 +119,4 @@ export function installLegendAction(registers) { 'legendUnSelect', 'legendunselected', curry(legendSelectActionHandler, 'unSelect') ); -} \ No newline at end of file +} From 7b5e85b680fa2cbd4e1ef0285046516861b59e90 Mon Sep 17 00:00:00 2001 From: plainheart Date: Tue, 9 Jul 2024 10:01:03 +0800 Subject: [PATCH 10/25] test(legend): add a test case for legend action --- test/legend-action.html | 106 ++++++++++++++++++++++++ test/runTest/actions/__meta__.json | 1 + test/runTest/actions/legend-action.json | 1 + 3 files changed, 108 insertions(+) create mode 100644 test/legend-action.html create mode 100644 test/runTest/actions/legend-action.json diff --git a/test/legend-action.html b/test/legend-action.html new file mode 100644 index 0000000000..07f3688843 --- /dev/null +++ b/test/legend-action.html @@ -0,0 +1,106 @@ + + + + + + + + + + + + +
+ + + + diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 26eac6dd21..54a683e54f 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -122,6 +122,7 @@ "label-position": 1, "largeLine-tooltip": 1, "legend": 11, + "legend-action": 1, "legend-visualMapColor": 2, "line": 1, "line-animation": 1, diff --git a/test/runTest/actions/legend-action.json b/test/runTest/actions/legend-action.json new file mode 100644 index 0000000000..31ec2f6443 --- /dev/null +++ b/test/runTest/actions/legend-action.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":223,"x":351,"y":33},{"type":"mousemove","time":427,"x":312,"y":71},{"type":"mousemove","time":661,"x":309,"y":75},{"type":"mousedown","time":679,"x":308,"y":75},{"type":"mouseup","time":789,"x":308,"y":75},{"time":790,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":876,"x":308,"y":75},{"type":"mousemove","time":1156,"x":300,"y":68},{"type":"mousemove","time":1360,"x":279,"y":57},{"type":"mousemove","time":1573,"x":255,"y":71},{"type":"mousedown","time":1734,"x":253,"y":74},{"type":"mousemove","time":1775,"x":253,"y":74},{"type":"mouseup","time":1869,"x":253,"y":74},{"time":1870,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2189,"x":253,"y":71},{"type":"mousemove","time":2389,"x":310,"y":49},{"type":"mousemove","time":2589,"x":492,"y":43},{"type":"mousemove","time":2789,"x":722,"y":33},{"type":"mousemove","time":2989,"x":712,"y":64},{"type":"mousemove","time":3195,"x":678,"y":81},{"type":"mousemove","time":3273,"x":680,"y":81},{"type":"mousemove","time":3476,"x":696,"y":76},{"type":"mousedown","time":3479,"x":696,"y":76},{"type":"mouseup","time":3581,"x":696,"y":76},{"time":3582,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3706,"x":692,"y":76},{"type":"mousemove","time":3909,"x":648,"y":75},{"type":"mousedown","time":4135,"x":648,"y":75},{"type":"mouseup","time":4309,"x":648,"y":75},{"time":4310,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4339,"x":647,"y":75},{"type":"mousemove","time":4789,"x":647,"y":74},{"type":"mousemove","time":4993,"x":652,"y":71},{"type":"mousemove","time":5226,"x":653,"y":54},{"type":"mousemove","time":5523,"x":649,"y":51},{"type":"mousemove","time":5723,"x":406,"y":53},{"type":"mousemove","time":5931,"x":329,"y":74},{"type":"mousemove","time":6139,"x":299,"y":66},{"type":"mousemove","time":6339,"x":270,"y":73},{"type":"mousedown","time":6470,"x":265,"y":75},{"type":"mousemove","time":6544,"x":265,"y":75},{"type":"mouseup","time":6597,"x":265,"y":75},{"time":6598,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6889,"x":269,"y":75},{"type":"mousemove","time":7089,"x":289,"y":68},{"type":"mousedown","time":7270,"x":292,"y":68},{"type":"mousemove","time":7295,"x":292,"y":68},{"type":"mouseup","time":7430,"x":292,"y":68},{"time":7431,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7790,"x":292,"y":66},{"type":"mousemove","time":7993,"x":295,"y":66},{"type":"mousemove","time":8573,"x":297,"y":67},{"type":"mousemove","time":8776,"x":312,"y":80},{"type":"mousemove","time":8989,"x":304,"y":83},{"type":"mousemove","time":9193,"x":251,"y":73},{"type":"mousedown","time":9303,"x":251,"y":73},{"type":"mouseup","time":9413,"x":251,"y":73},{"time":9414,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9756,"x":251,"y":72},{"type":"mousemove","time":9956,"x":265,"y":68},{"type":"mousemove","time":10160,"x":379,"y":39}],"scrollY":0,"scrollX":0,"timestamp":1720490304919}] \ No newline at end of file From d76ce4e8004cd47c298faef8c5027befa8f0ee06 Mon Sep 17 00:00:00 2001 From: plainheart Date: Thu, 11 Jul 2024 13:34:12 +0800 Subject: [PATCH 11/25] fix(legend): fix legend item is selected but corresponding series may not show after dispatching `legendAllSelect` action. --- src/component/legend/legendAction.ts | 77 +++++++++++++++---------- test/legend-action.html | 38 ++++++++++-- test/runTest/actions/legend-action.json | 2 +- 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index 2edc27dfc2..92e3d14d0e 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -17,7 +17,7 @@ * under the License. */ -import {curry, each, hasOwn} from 'zrender/src/core/util'; +import {curry, each, hasOwn, indexOf, map} from 'zrender/src/core/util'; import { EChartsExtensionInstallRegisters } from '../../extension'; import { Payload } from '../../util/types'; import type GlobalModel from '../../model/Global'; @@ -29,45 +29,44 @@ type LegendSelectMethodNames = 'allSelect' | 'inverseSelect'; function legendSelectActionHandler(methodName: LegendSelectMethodNames, payload: Payload, ecModel: GlobalModel) { + const isAllSelect = methodName === 'allSelect' || methodName === 'inverseSelect'; const selectedMap: Record = {}; - const isToggleSelect = methodName === 'toggleSelected'; - let isSelected: boolean; + + const actionLegendIndices: number[] = []; ecModel.eachComponent({ mainType: 'legend', query: payload }, function (legendModel: LegendModel) { - if (isToggleSelect && isSelected != null) { - // Force other legend has same selected status - // Or the first is toggled to true and other are toggled to false - // In the case one legend has some item unSelected in option. And if other legend - // doesn't has the item, they will assume it is selected. - legendModel[isSelected ? 'select' : 'unSelect'](payload.name); - } - else if (methodName === 'allSelect' || methodName === 'inverseSelect') { + if (isAllSelect) { legendModel[methodName](); } else { legendModel[methodName](payload.name); - isSelected = legendModel.isSelected(payload.name); } - const legendData = legendModel.getData(); - each(legendData, function (model) { - const name = model.get('name'); - // Wrap element - if (name === '\n' || name === '') { - return; - } - const isItemSelected = legendModel.isSelected(name); - if (hasOwn(selectedMap, name)) { - // Unselected if any legend is unselected - selectedMap[name] = selectedMap[name] && isItemSelected; - } - else { - selectedMap[name] = isItemSelected; - } + + makeSelectedMap(legendModel, selectedMap); + + actionLegendIndices.push(legendModel.componentIndex); + }); + + const allSelectedMap: Record = {}; + + // make selectedMap from all legend components + ecModel.eachComponent('legend', function (legendModel: LegendModel) { + each(selectedMap, function (isSelected, name) { + // Force other legend has same selected status + // Or the first is toggled to true and other are toggled to false + // In the case one legend has some item unSelected in option. And if other legend + // doesn't has the item, they will assume it is selected. + legendModel[isSelected ? 'select' : 'unSelect'](name); }); + + makeSelectedMap(legendModel, allSelectedMap); }); + // Return the event explicitly - return (methodName === 'allSelect' || methodName === 'inverseSelect') + return isAllSelect ? { - selected: selectedMap + selected: selectedMap, + // return legendIndex array to tell the developers which legends are allSelect / inverseSelect + legendIndex: actionLegendIndices } : { name: payload.name, @@ -75,6 +74,26 @@ function legendSelectActionHandler(methodName: LegendSelectMethodNames, payload: }; } +function makeSelectedMap(legendModel: LegendModel, out?: Record) { + const selectedMap: Record = out || {}; + each(legendModel.getData(), function (model) { + const name = model.get('name'); + // Wrap element + if (name === '\n' || name === '') { + return; + } + const isItemSelected = legendModel.isSelected(name); + if (hasOwn(selectedMap, name)) { + // Unselected if any legend is unselected + selectedMap[name] = selectedMap[name] && isItemSelected; + } + else { + selectedMap[name] = isItemSelected; + } + }); + return selectedMap; +} + export function installLegendAction(registers: EChartsExtensionInstallRegisters) { /** * @event legendToggleSelect diff --git a/test/legend-action.html b/test/legend-action.html index 07f3688843..90dda2e3f2 100644 --- a/test/legend-action.html +++ b/test/legend-action.html @@ -46,6 +46,7 @@ legend: [ { id: 1, + name: 'legend1', data: ['Steppe', 'Forest'], left: '10%', selector: [ @@ -59,7 +60,8 @@ }, { id: 2, - data: ['Desert', 'Wetland'], + name: 'legend2', + data: ['Desert', 'Wetland', 'Steppe' /* test for the same item in other legend */], right: '10%', selector: [ { @@ -94,12 +96,40 @@ } ] }; - var chart = testHelper.create(echarts, 'main0', { + var chart = window.myChart0 = testHelper.create(echarts, 'main0', { title: [ - 'Legend action should be isolated from other legend components' + 'Legend action should be isolated from other legend components\n(except for the items with the same name)' ], - option: option + option: option, + buttons: [ + { + text: 'Dispatch `legendAllSelect` for both legend components', + onclick() { + chart.dispatchAction({ + type: 'legendAllSelect', + legendName: ['legend1', 'legend2'], + // legendId: ['0', '1'], + // legendIndex: [0, 1] + }) + } + }, + { + text: 'Dispatch `legendInverseSelect` for both legend components', + onclick() { + chart.dispatchAction({ + type: 'legendInverseSelect', + // legendName: ['legend1', 'legend2'], + legendId: ['1', '2'], + // legendIndex: [0, 1] + }) + } + } + ] }); + var logFn = e => console.log(JSON.stringify(e, null, 2)); + chart.on('legendselectall', logFn); + chart.on('legendinverseselect', logFn); + chart.on('legendselectchanged', logFn); }); diff --git a/test/runTest/actions/legend-action.json b/test/runTest/actions/legend-action.json index 31ec2f6443..bd0b61577f 100644 --- a/test/runTest/actions/legend-action.json +++ b/test/runTest/actions/legend-action.json @@ -1 +1 @@ -[{"name":"Action 1","ops":[{"type":"mousemove","time":223,"x":351,"y":33},{"type":"mousemove","time":427,"x":312,"y":71},{"type":"mousemove","time":661,"x":309,"y":75},{"type":"mousedown","time":679,"x":308,"y":75},{"type":"mouseup","time":789,"x":308,"y":75},{"time":790,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":876,"x":308,"y":75},{"type":"mousemove","time":1156,"x":300,"y":68},{"type":"mousemove","time":1360,"x":279,"y":57},{"type":"mousemove","time":1573,"x":255,"y":71},{"type":"mousedown","time":1734,"x":253,"y":74},{"type":"mousemove","time":1775,"x":253,"y":74},{"type":"mouseup","time":1869,"x":253,"y":74},{"time":1870,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2189,"x":253,"y":71},{"type":"mousemove","time":2389,"x":310,"y":49},{"type":"mousemove","time":2589,"x":492,"y":43},{"type":"mousemove","time":2789,"x":722,"y":33},{"type":"mousemove","time":2989,"x":712,"y":64},{"type":"mousemove","time":3195,"x":678,"y":81},{"type":"mousemove","time":3273,"x":680,"y":81},{"type":"mousemove","time":3476,"x":696,"y":76},{"type":"mousedown","time":3479,"x":696,"y":76},{"type":"mouseup","time":3581,"x":696,"y":76},{"time":3582,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3706,"x":692,"y":76},{"type":"mousemove","time":3909,"x":648,"y":75},{"type":"mousedown","time":4135,"x":648,"y":75},{"type":"mouseup","time":4309,"x":648,"y":75},{"time":4310,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4339,"x":647,"y":75},{"type":"mousemove","time":4789,"x":647,"y":74},{"type":"mousemove","time":4993,"x":652,"y":71},{"type":"mousemove","time":5226,"x":653,"y":54},{"type":"mousemove","time":5523,"x":649,"y":51},{"type":"mousemove","time":5723,"x":406,"y":53},{"type":"mousemove","time":5931,"x":329,"y":74},{"type":"mousemove","time":6139,"x":299,"y":66},{"type":"mousemove","time":6339,"x":270,"y":73},{"type":"mousedown","time":6470,"x":265,"y":75},{"type":"mousemove","time":6544,"x":265,"y":75},{"type":"mouseup","time":6597,"x":265,"y":75},{"time":6598,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6889,"x":269,"y":75},{"type":"mousemove","time":7089,"x":289,"y":68},{"type":"mousedown","time":7270,"x":292,"y":68},{"type":"mousemove","time":7295,"x":292,"y":68},{"type":"mouseup","time":7430,"x":292,"y":68},{"time":7431,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7790,"x":292,"y":66},{"type":"mousemove","time":7993,"x":295,"y":66},{"type":"mousemove","time":8573,"x":297,"y":67},{"type":"mousemove","time":8776,"x":312,"y":80},{"type":"mousemove","time":8989,"x":304,"y":83},{"type":"mousemove","time":9193,"x":251,"y":73},{"type":"mousedown","time":9303,"x":251,"y":73},{"type":"mouseup","time":9413,"x":251,"y":73},{"time":9414,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9756,"x":251,"y":72},{"type":"mousemove","time":9956,"x":265,"y":68},{"type":"mousemove","time":10160,"x":379,"y":39}],"scrollY":0,"scrollX":0,"timestamp":1720490304919}] \ No newline at end of file +[{"name":"Action 1","ops":[{"type":"mousemove","time":301,"x":470,"y":19},{"type":"mousemove","time":500,"x":289,"y":47},{"type":"mousemove","time":700,"x":197,"y":115},{"type":"mousemove","time":903,"x":188,"y":132},{"type":"mousedown","time":1024,"x":188,"y":132},{"type":"mouseup","time":1169,"x":188,"y":132},{"time":1170,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1483,"x":201,"y":132},{"type":"mousemove","time":1689,"x":273,"y":120},{"type":"mousemove","time":1900,"x":325,"y":117},{"type":"mousemove","time":2103,"x":242,"y":126},{"type":"mousemove","time":2266,"x":242,"y":127},{"type":"mousedown","time":2408,"x":257,"y":133},{"type":"mousemove","time":2469,"x":257,"y":133},{"type":"mouseup","time":2552,"x":257,"y":133},{"time":2553,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2899,"x":261,"y":133},{"type":"mousemove","time":3099,"x":268,"y":132},{"type":"mousemove","time":3299,"x":297,"y":129},{"type":"mousedown","time":3472,"x":299,"y":129},{"type":"mousemove","time":3503,"x":299,"y":129},{"type":"mouseup","time":3646,"x":299,"y":129},{"time":3647,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4016,"x":304,"y":129},{"type":"mousemove","time":4216,"x":474,"y":132},{"type":"mousemove","time":4421,"x":548,"y":141},{"type":"mousemove","time":4669,"x":525,"y":131},{"type":"mousedown","time":4681,"x":525,"y":131},{"type":"mouseup","time":4816,"x":525,"y":131},{"time":4817,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4999,"x":526,"y":130},{"type":"mousemove","time":5199,"x":622,"y":130},{"type":"mousemove","time":5399,"x":646,"y":131},{"type":"mousedown","time":5521,"x":651,"y":132},{"type":"mousemove","time":5604,"x":651,"y":133},{"type":"mouseup","time":5655,"x":651,"y":133},{"time":5656,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5886,"x":651,"y":133},{"type":"mousemove","time":6050,"x":651,"y":134},{"type":"mousemove","time":6333,"x":661,"y":135},{"type":"mousemove","time":6533,"x":689,"y":134},{"type":"mousedown","time":6648,"x":690,"y":134},{"type":"mousemove","time":6736,"x":690,"y":134},{"type":"mouseup","time":6759,"x":690,"y":134},{"time":6760,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6983,"x":688,"y":134},{"type":"mousemove","time":7187,"x":659,"y":133},{"type":"mousedown","time":7304,"x":659,"y":133},{"type":"mouseup","time":7438,"x":659,"y":133},{"time":7439,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7699,"x":651,"y":133},{"type":"mousemove","time":7899,"x":583,"y":135},{"type":"mousemove","time":8105,"x":521,"y":135},{"type":"mousedown","time":8203,"x":521,"y":135},{"type":"mouseup","time":8310,"x":521,"y":135},{"time":8311,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8432,"x":542,"y":135},{"type":"mousemove","time":8636,"x":640,"y":135},{"type":"mousemove","time":8870,"x":656,"y":134},{"type":"mousedown","time":8896,"x":656,"y":134},{"type":"mouseup","time":9038,"x":656,"y":134},{"time":9039,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9184,"x":654,"y":130},{"type":"mousemove","time":9387,"x":613,"y":109},{"type":"mousemove","time":9599,"x":582,"y":96},{"type":"mousemove","time":9803,"x":577,"y":94},{"type":"mousedown","time":9927,"x":577,"y":94},{"type":"mouseup","time":10136,"x":577,"y":94},{"time":10137,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10401,"x":574,"y":93},{"type":"mousemove","time":10603,"x":407,"y":94},{"type":"mousemove","time":10820,"x":304,"y":94},{"type":"mousemove","time":11116,"x":301,"y":94},{"type":"mousedown","time":11319,"x":296,"y":97},{"type":"mousemove","time":11323,"x":296,"y":97},{"type":"mouseup","time":11430,"x":296,"y":97},{"time":11431,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11616,"x":296,"y":98},{"type":"mousemove","time":11817,"x":246,"y":118},{"type":"mousemove","time":12021,"x":218,"y":135},{"type":"mousedown","time":12137,"x":218,"y":135},{"type":"mouseup","time":12230,"x":218,"y":135},{"time":12231,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12382,"x":293,"y":133},{"type":"mousemove","time":12583,"x":397,"y":129},{"type":"mousemove","time":12786,"x":441,"y":129},{"type":"mousedown","time":12804,"x":441,"y":129},{"type":"mouseup","time":12886,"x":441,"y":129},{"time":12887,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12950,"x":419,"y":128},{"type":"mousemove","time":13154,"x":244,"y":79},{"type":"mousemove","time":13366,"x":244,"y":91},{"type":"mousedown","time":13431,"x":245,"y":92},{"type":"mousemove","time":13569,"x":245,"y":92},{"type":"mouseup","time":13574,"x":245,"y":92},{"time":13575,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13749,"x":256,"y":94},{"type":"mousemove","time":13949,"x":451,"y":91},{"type":"mousemove","time":14153,"x":473,"y":88},{"type":"mousedown","time":14353,"x":473,"y":87},{"type":"mousemove","time":14371,"x":473,"y":87},{"type":"mouseup","time":14520,"x":473,"y":87},{"time":14521,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15049,"x":474,"y":87},{"type":"mousemove","time":15252,"x":516,"y":83},{"type":"mousemove","time":15487,"x":519,"y":92},{"type":"mousemove","time":15736,"x":514,"y":97},{"type":"mousedown","time":15920,"x":514,"y":97},{"type":"mouseup","time":16054,"x":514,"y":97},{"time":16055,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16666,"x":531,"y":94},{"type":"mousemove","time":16870,"x":612,"y":81}],"scrollY":0,"scrollX":0,"timestamp":1720675612620}] \ No newline at end of file From f2c165d1189ff3d84a9665895d4852d3b363f8bb Mon Sep 17 00:00:00 2001 From: plainheart Date: Thu, 11 Jul 2024 13:39:06 +0800 Subject: [PATCH 12/25] fix(legend): remove unused import --- src/component/legend/legendAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index 92e3d14d0e..6576af60e7 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -17,7 +17,7 @@ * under the License. */ -import {curry, each, hasOwn, indexOf, map} from 'zrender/src/core/util'; +import {curry, each, hasOwn} from 'zrender/src/core/util'; import { EChartsExtensionInstallRegisters } from '../../extension'; import { Payload } from '../../util/types'; import type GlobalModel from '../../model/Global'; From 5a1ad78f305d33ee53a0ee807dd5d300e0b5ecbe Mon Sep 17 00:00:00 2001 From: plainheart Date: Thu, 11 Jul 2024 17:14:12 +0800 Subject: [PATCH 13/25] fix(legend): remove duplicated type of legend select method name --- src/component/legend/legendAction.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index 6576af60e7..9a05c367e2 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -23,10 +23,7 @@ import { Payload } from '../../util/types'; import type GlobalModel from '../../model/Global'; import type LegendModel from './LegendModel'; -type LegendSelectMethodNames = - 'select' | 'unSelect' | - 'toggleSelected' | 'toggleSelected' | - 'allSelect' | 'inverseSelect'; +type LegendSelectMethodNames = 'select' | 'unSelect' | 'toggleSelected' | 'allSelect' | 'inverseSelect'; function legendSelectActionHandler(methodName: LegendSelectMethodNames, payload: Payload, ecModel: GlobalModel) { const isAllSelect = methodName === 'allSelect' || methodName === 'inverseSelect'; From 1a456fa75edcdaa8bac706dc0fd5ce1e1672e96c Mon Sep 17 00:00:00 2001 From: Zhongxiang Wang Date: Thu, 11 Jul 2024 22:57:22 +0800 Subject: [PATCH 14/25] fix(legend): return allSelectedMap rather than selectedMap --- src/component/legend/legendAction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index 9a05c367e2..d0771e79aa 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -61,13 +61,13 @@ function legendSelectActionHandler(methodName: LegendSelectMethodNames, payload: // Return the event explicitly return isAllSelect ? { - selected: selectedMap, + selected: allSelectedMap, // return legendIndex array to tell the developers which legends are allSelect / inverseSelect legendIndex: actionLegendIndices } : { name: payload.name, - selected: selectedMap + selected: allSelectedMap }; } From c74c28af20cbaf09b63042187af223b9908c590a Mon Sep 17 00:00:00 2001 From: plainheart Date: Tue, 2 Jul 2024 12:12:25 +0800 Subject: [PATCH 15/25] fix(candlestick): add back missing support for non-normal states since v5.0.0 --- src/chart/candlestick/CandlestickView.ts | 23 +++++++--- src/chart/candlestick/candlestickVisual.ts | 31 +++++++------ test/candlestick-case.html | 51 +++++++++++++++++++++- test/runTest/actions/__meta__.json | 2 +- test/runTest/actions/candlestick-case.json | 2 +- 5 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/chart/candlestick/CandlestickView.ts b/src/chart/candlestick/CandlestickView.ts index 3d05e81c70..728291424c 100644 --- a/src/chart/candlestick/CandlestickView.ts +++ b/src/chart/candlestick/CandlestickView.ts @@ -20,7 +20,7 @@ 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 { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states'; import Path, { PathProps } from 'zrender/src/graphic/Path'; import {createClipPath} from '../helper/createClipPathFromCoordSys'; import CandlestickSeriesModel, { CandlestickDataItemOption } from './CandlestickSeries'; @@ -33,6 +33,7 @@ import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem'; import Model from '../../model/Model'; import { saveOldStyle } from '../../animation/basicTransition'; import Element from 'zrender/src/Element'; +import { getBorderColor, getColor } from './candlestickVisual'; const SKIP_PROPS = ['color', 'borderColor'] as const; @@ -294,6 +295,19 @@ function setBoxCommon(el: NormalBoxPath, data: SeriesData, dataIndex: number, is el.__simpleBox = isSimpleBox; setStatesStylesFromModel(el, itemModel); + + const sign = data.getItemLayout(dataIndex).sign; + zrUtil.each(el.states, (state, stateName) => { + const stateModel = itemModel.getModel(stateName as any); + const color = getColor(sign, stateModel); + const borderColor = getBorderColor(sign, stateModel) || color; + const stateStyle = state.style || (state.style = {}); + color && (stateStyle.fill = color); + borderColor && (stateStyle.stroke = borderColor); + }); + + const emphasisModel = itemModel.getModel('emphasis'); + toggleHoverEmphasis(el, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled')); } function transInit(points: number[][], itemLayout: CandlestickItemLayout) { @@ -391,12 +405,9 @@ function createLarge( function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickSeriesModel, data: SeriesData) { // TODO put in visual? - let borderColor = seriesModel.get(['itemStyle', sign > 0 ? 'borderColor' : 'borderColor0']) + const borderColor = getBorderColor(sign, seriesModel) // Use color for border color by default. - || seriesModel.get(['itemStyle', sign > 0 ? 'color' : 'color0']); - if (sign === 0) { - borderColor = seriesModel.get(['itemStyle', 'borderColorDoji']); - } + || getColor(sign, seriesModel); // Color must be excluded. // Because symbol provide setColor individually to set fill and stroke diff --git a/src/chart/candlestick/candlestickVisual.ts b/src/chart/candlestick/candlestickVisual.ts index 8882d63333..a665ea3b69 100644 --- a/src/chart/candlestick/candlestickVisual.ts +++ b/src/chart/candlestick/candlestickVisual.ts @@ -29,6 +29,21 @@ const dojiBorderColorQuery = ['itemStyle', 'borderColorDoji'] as const; const positiveColorQuery = ['itemStyle', 'color'] as const; const negativeColorQuery = ['itemStyle', 'color0'] as const; +export function getColor(sign: number, model: Model>) { + return model.get( + sign > 0 ? positiveColorQuery : negativeColorQuery + ); +} + +export function getBorderColor(sign: number, model: Model>) { + return model.get( + sign === 0 ? dojiBorderColorQuery + : sign > 0 + ? positiveBorderColorQuery + : negativeBorderColorQuery + ); +} + const candlestickVisual: StageHandler = { seriesType: 'candlestick', @@ -39,22 +54,6 @@ const candlestickVisual: StageHandler = { performRawSeries: true, reset: function (seriesModel: CandlestickSeriesModel, ecModel) { - - function getColor(sign: number, model: Model>) { - return model.get( - sign > 0 ? positiveColorQuery : negativeColorQuery - ); - } - - function getBorderColor(sign: number, model: Model>) { - return model.get( - sign === 0 ? dojiBorderColorQuery - : sign > 0 - ? positiveBorderColorQuery - : negativeBorderColorQuery - ); - } - // Only visible series has each data be visual encoded if (ecModel.isSeriesFiltered(seriesModel)) { return; diff --git a/test/candlestick-case.html b/test/candlestick-case.html index 2e5a33f747..6a408a644a 100644 --- a/test/candlestick-case.html +++ b/test/candlestick-case.html @@ -38,7 +38,7 @@
- +
@@ -363,6 +363,55 @@ }); + diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 54a683e54f..c9abb1e8c8 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -45,7 +45,7 @@ "calendar-heatmap": 1, "calendar-month": 1, "candlestick": 2, - "candlestick-case": 1, + "candlestick-case": 2, "candlestick-empty": 1, "candlestick-large": 4, "candlestick-large2": 1, diff --git a/test/runTest/actions/candlestick-case.json b/test/runTest/actions/candlestick-case.json index bc02692b43..0b07b405ea 100644 --- a/test/runTest/actions/candlestick-case.json +++ b/test/runTest/actions/candlestick-case.json @@ -1 +1 @@ -[{"name":"Action 1","ops":[{"type":"mousedown","time":300,"x":50,"y":77},{"type":"mouseup","time":423,"x":50,"y":77},{"time":424,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1626405373145}] \ No newline at end of file +[{"name":"Action 1","ops":[{"type":"mousedown","time":300,"x":50,"y":77},{"type":"mouseup","time":423,"x":50,"y":77},{"time":424,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1626405373145},{"name":"Action 2","ops":[{"type":"mousemove","time":303,"x":145,"y":215},{"type":"mousemove","time":505,"x":169,"y":336},{"type":"mousemove","time":722,"x":170,"y":369},{"type":"screenshot","time":1492},{"type":"mousemove","time":1769,"x":199,"y":364},{"type":"mousemove","time":1969,"x":287,"y":329},{"type":"mousemove","time":2172,"x":315,"y":329},{"type":"screenshot","time":2995},{"type":"mousemove","time":3319,"x":318,"y":329},{"type":"mousemove","time":3523,"x":446,"y":340},{"type":"mousemove","time":3742,"x":462,"y":343},{"type":"screenshot","time":4427},{"type":"mousemove","time":4885,"x":462,"y":344},{"type":"mousemove","time":5085,"x":639,"y":376},{"type":"mousemove","time":5290,"x":638,"y":381},{"type":"screenshot","time":6027}],"scrollY":357,"scrollX":0,"timestamp":1719893340178}] \ No newline at end of file From 5bcac1c3fd5da3b7549bc740a66b28aa1243f48a Mon Sep 17 00:00:00 2001 From: plainheart Date: Tue, 2 Jul 2024 12:57:03 +0800 Subject: [PATCH 16/25] fix(candlestick): disable emphasis state by default for forward compatibility --- src/chart/candlestick/CandlestickSeries.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chart/candlestick/CandlestickSeries.ts b/src/chart/candlestick/CandlestickSeries.ts index 019e1c55f4..141fbf5723 100644 --- a/src/chart/candlestick/CandlestickSeries.ts +++ b/src/chart/candlestick/CandlestickSeries.ts @@ -124,7 +124,8 @@ class CandlestickSeriesModel extends SeriesModel { }, emphasis: { - scale: true, + // disable emphasis state by default for forward compatibility + disabled: true, itemStyle: { borderWidth: 2 } From c6300179a81ea9f63f55c65a2eb0f9a1fca9eaf8 Mon Sep 17 00:00:00 2001 From: Zhongxiang Wang Date: Mon, 15 Jul 2024 20:28:40 +0800 Subject: [PATCH 17/25] fix(candelstick): still enable emphasis state by default --- src/chart/candlestick/CandlestickSeries.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/chart/candlestick/CandlestickSeries.ts b/src/chart/candlestick/CandlestickSeries.ts index 141fbf5723..f152fce2cf 100644 --- a/src/chart/candlestick/CandlestickSeries.ts +++ b/src/chart/candlestick/CandlestickSeries.ts @@ -124,8 +124,6 @@ class CandlestickSeriesModel extends SeriesModel { }, emphasis: { - // disable emphasis state by default for forward compatibility - disabled: true, itemStyle: { borderWidth: 2 } From 0ab51ee822ac1f8ca9e7129bb894165487c3c632 Mon Sep 17 00:00:00 2001 From: plainheart Date: Mon, 15 Jul 2024 17:11:09 +0800 Subject: [PATCH 18/25] perf(line): prebind context of `_changePolyState` function to current instance rather than create an new function to reduce runtime memory cost (fix #20151) --- src/chart/line/LineView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts index 9672eb720b..be4ff3571e 100644 --- a/src/chart/line/LineView.ts +++ b/src/chart/line/LineView.ts @@ -624,6 +624,8 @@ class LineView extends ChartView { this._symbolDraw = symbolDraw; this._lineGroup = lineGroup; + + this._changePolyState = zrUtil.bind(this._changePolyState, this); } render(seriesModel: LineSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { @@ -885,9 +887,7 @@ class LineView extends ChartView { toggleHoverEmphasis(polygon, focus, blurScope, emphasisDisabled); } - const changePolyState = (toState: DisplayState) => { - this._changePolyState(toState); - }; + const changePolyState = this._changePolyState; data.eachItemGraphicEl(function (el) { // Switch polyline / polygon state if element changed its state. From 212ee25deddd0a6363701dc9533ef2708b1b1587 Mon Sep 17 00:00:00 2001 From: hxada Date: Thu, 4 Jul 2024 14:13:58 +0800 Subject: [PATCH 19/25] feature(axis): add feature to remove SplitLine on specified tick --- src/component/axis/CartesianAxisView.ts | 11 +++- src/coord/Axis.ts | 4 +- src/coord/axisCommonTypes.ts | 6 +- src/coord/axisDefault.ts | 2 + test/axis-splitLine.html | 83 +++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 test/axis-splitLine.html diff --git a/src/component/axis/CartesianAxisView.ts b/src/component/axis/CartesianAxisView.ts index 3cc48e3041..abc3e8cff7 100644 --- a/src/component/axis/CartesianAxisView.ts +++ b/src/component/axis/CartesianAxisView.ts @@ -123,6 +123,8 @@ const axisElementBuilders: Record boolean) + interval?: 'auto' | number | ((index:number, value: string) => boolean), + // true | false + showMinLine?: boolean, + // true | false + showMaxLine?: boolean, // colors will display in turn lineStyle?: LineStyleOption } diff --git a/src/coord/axisDefault.ts b/src/coord/axisDefault.ts index 4d2c6674b3..ae589f365a 100644 --- a/src/coord/axisDefault.ts +++ b/src/coord/axisDefault.ts @@ -93,6 +93,8 @@ const defaultOption: AxisBaseOption = { }, splitLine: { show: true, + showMinLine: true, + showMaxLine: true, lineStyle: { color: ['#E0E6F1'], width: 1, diff --git a/test/axis-splitLine.html b/test/axis-splitLine.html new file mode 100644 index 0000000000..2dfd1a320d --- /dev/null +++ b/test/axis-splitLine.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + + + From 01c2e6ebbc09a6b34abaa6b11f0d15e32167d1d3 Mon Sep 17 00:00:00 2001 From: zhangyu Date: Tue, 25 Jun 2024 17:01:24 +0800 Subject: [PATCH 20/25] fix(pie): Missing pie chart label display. close #20070 --- src/chart/pie/labelLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts index 934b49cadc..4736a4b937 100644 --- a/src/chart/pie/labelLayout.ts +++ b/src/chart/pie/labelLayout.ts @@ -89,7 +89,7 @@ function adjustSingleSide( const rA = r + item.len; const rA2 = rA * rA; // Use ellipse implicit function to calculate x - const dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2); + const dx = Math.sqrt(Math.abs((1 - Math.abs(dy * dy / rB2)) * rA2)); const newX = cx + (dx + item.len2) * dir; const deltaX = newX - item.label.x; const newTargetWidth = item.targetTextWidth - deltaX * dir; From b3eac2715652f60fdd7445cb8c63306e0f831a64 Mon Sep 17 00:00:00 2001 From: zhangyu Date: Tue, 9 Jul 2024 17:39:45 +0800 Subject: [PATCH 21/25] fix(pie): fix some labels may not show #20074 --- src/chart/pie/labelLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts index 4736a4b937..5052ca236b 100644 --- a/src/chart/pie/labelLayout.ts +++ b/src/chart/pie/labelLayout.ts @@ -89,7 +89,7 @@ function adjustSingleSide( const rA = r + item.len; const rA2 = rA * rA; // Use ellipse implicit function to calculate x - const dx = Math.sqrt(Math.abs((1 - Math.abs(dy * dy / rB2)) * rA2)); + const dx = Math.sqrt(Math.abs((1 - dy * dy / rB2) * rA2)); const newX = cx + (dx + item.len2) * dir; const deltaX = newX - item.label.x; const newTargetWidth = item.targetTextWidth - deltaX * dir; From 1577bce179273d3d1f5d80a36ce7a5b8c5d2a84e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 02:39:06 +0000 Subject: [PATCH 22/25] chore(deps-dev): bump socket.io-parser in /test/runTest Bumps [socket.io-parser](https://github.com/Automattic/socket.io-parser) from 3.3.3 to 3.3.4. - [Release notes](https://github.com/Automattic/socket.io-parser/releases) - [Changelog](https://github.com/socketio/socket.io-parser/blob/3.3.4/CHANGELOG.md) - [Commits](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4) --- updated-dependencies: - dependency-name: socket.io-parser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- test/runTest/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/runTest/package-lock.json b/test/runTest/package-lock.json index cd221ad51a..2cce4e0929 100644 --- a/test/runTest/package-lock.json +++ b/test/runTest/package-lock.json @@ -2416,9 +2416,9 @@ "dev": true }, "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz", - "integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.4.tgz", + "integrity": "sha512-z/pFQB3x+EZldRRzORYW1vwVO8m/3ILkswtnpoeU6Ve3cbMWkmHEWDAVJn4QJtchiiFTo5j7UG2QvwxvaA9vow==", "dev": true, "dependencies": { "component-emitter": "~1.3.0", @@ -4724,9 +4724,9 @@ "dev": true }, "socket.io-parser": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz", - "integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.4.tgz", + "integrity": "sha512-z/pFQB3x+EZldRRzORYW1vwVO8m/3ILkswtnpoeU6Ve3cbMWkmHEWDAVJn4QJtchiiFTo5j7UG2QvwxvaA9vow==", "dev": true, "requires": { "component-emitter": "~1.3.0", From 1a06ff8b9bcf88add0bcaf726bd0cc71915941f8 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 24 Jul 2024 14:50:46 +0800 Subject: [PATCH 23/25] fix(axis): ticks overflowing grid area with dataZoom #20185 --- src/coord/axisTickLabelBuilder.ts | 9 +- test/axis-customTicks.html | 186 ++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 2 deletions(-) diff --git a/src/coord/axisTickLabelBuilder.ts b/src/coord/axisTickLabelBuilder.ts index e7f7bbb088..329ad6447b 100644 --- a/src/coord/axisTickLabelBuilder.ts +++ b/src/coord/axisTickLabelBuilder.ts @@ -84,8 +84,11 @@ export function createAxisLabels(axis: Axis): { const custom = axis.getLabelModel().get('customValues'); if (custom) { const labelFormatter = makeLabelFormatter(axis); + const extent = axis.scale.getExtent(); + const tickNumbers = tickValuesToNumbers(axis, custom); + const ticks = zrUtil.filter(tickNumbers, val => val >= extent[0] && val <= extent[1]); return { - labels: tickValuesToNumbers(axis, custom).map(numval => { + labels: zrUtil.map(ticks, numval => { const tick = {value: numval}; return { formattedLabel: labelFormatter(tick), @@ -115,8 +118,10 @@ export function createAxisTicks(axis: Axis, tickModel: AxisBaseModel): { } { const custom = axis.getTickModel().get('customValues'); if (custom) { + const extent = axis.scale.getExtent(); + const tickNumbers = tickValuesToNumbers(axis, custom); return { - ticks: tickValuesToNumbers(axis, custom) + ticks: zrUtil.filter(tickNumbers, val => val >= extent[0] && val <= extent[1]) }; } // Only ordinal scale support tick interval diff --git a/test/axis-customTicks.html b/test/axis-customTicks.html index 24f9c7dc97..7f79f099d8 100644 --- a/test/axis-customTicks.html +++ b/test/axis-customTicks.html @@ -38,6 +38,8 @@
+
+
+ + + + + From 50339df94a089d3c055aae8fdc11e2ed9c495fec Mon Sep 17 00:00:00 2001 From: semla Date: Fri, 26 Jul 2024 12:28:52 +0200 Subject: [PATCH 24/25] perf(aria): use functions from zrender --- src/util/types.ts | 2 +- src/visual/aria.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/types.ts b/src/util/types.ts index 0d06cfdd63..c29d158747 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1734,7 +1734,7 @@ export interface AriaLabelOption { middle?: string; end?: string; }, - columnsToExclude?: number[] + excludeDataId?: number[] } } diff --git a/src/visual/aria.ts b/src/visual/aria.ts index d82baa8e8d..d44aa6e4bb 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -218,13 +218,13 @@ export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { const middleSeparator = labelModel.get(['data', 'separator', 'middle']); const endSeparator = labelModel.get(['data', 'separator', 'end']); - const columnsToExclude = labelModel.get(['data', 'columnsToExclude']); + const excludeDataId = labelModel.get(['data', 'excludeDataId']); const dataLabels = []; for (let i = 0; i < data.count(); i++) { if (i < maxDataCnt) { const name = data.getName(i); - const value = !columnsToExclude ? data.getValues(i) - : data.getValues(i).filter((value, j) => !columnsToExclude?.includes(j)); + const value = !excludeDataId ? data.getValues(i) + : zrUtil.filter(data.getValues(i), (v, j) => zrUtil.indexOf(excludeDataId, j) === -1); const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); dataLabels.push( replace(dataLabel, { From 9a65f9dc7acaa44a0549cff7ba3586b521408d76 Mon Sep 17 00:00:00 2001 From: semla Date: Fri, 26 Jul 2024 12:38:37 +0200 Subject: [PATCH 25/25] test(aria): also test values that should exist --- test/ut/spec/series/aria-columns-exclude.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/ut/spec/series/aria-columns-exclude.test.ts b/test/ut/spec/series/aria-columns-exclude.test.ts index feed8cdf86..b67da69140 100644 --- a/test/ut/spec/series/aria-columns-exclude.test.ts +++ b/test/ut/spec/series/aria-columns-exclude.test.ts @@ -25,7 +25,7 @@ describe('aria, omit data', function () { 'aria': { 'enabled': true, 'data': { - 'columnsToExclude': [0, 1, 2] + 'excludeDataId': [0, 1, 2] }, }, 'dataset': [ @@ -78,6 +78,8 @@ describe('aria, omit data', function () { chart.setOption(option); const el = chart.getDom(); const ariaValue = el.getAttribute('aria-label'); + expect(ariaValue).toContain('Llosa del Cavall (Navès) is 17.945, 80'); + expect(ariaValue).toContain('Riudecanyes is 0.401, 5.32'); expect(ariaValue).not.toContain(1.58285827); expect(ariaValue).not.toContain(42.099784969); expect(ariaValue).not.toContain(0.960270444); @@ -88,6 +90,7 @@ describe('aria, omit data', function () { chart.setOption(option); const listData = getECModel(chart).getSeries()[0].getData(); expect(listData.getValues(0)).toEqual([1.58285827, 42.099784969, 'Llosa del Cavall (Navès)', 17.945, 80]); + expect(listData.getValues(1)).toEqual([0.960270444, 41.134931354, 'Riudecanyes', 0.401, 5.32]); }); });