From ecd2ff02cef2b79ee3bee6b530cb8fa32057784e Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 9 Apr 2024 16:05:14 +0800 Subject: [PATCH 01/49] feat: init matrix coordinate --- src/component/matrix/MatrixView.ts | 35 +++++++++++++ src/component/matrix/install.ts | 29 +++++++++++ src/coord/matrix/Matrix.ts | 84 ++++++++++++++++++++++++++++++ src/coord/matrix/MatrixModel.ts | 35 +++++++++++++ src/export/components.ts | 2 + src/export/option.ts | 2 + src/util/types.ts | 5 ++ 7 files changed, 192 insertions(+) create mode 100644 src/component/matrix/MatrixView.ts create mode 100644 src/component/matrix/install.ts create mode 100644 src/coord/matrix/Matrix.ts create mode 100644 src/coord/matrix/MatrixModel.ts diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts new file mode 100644 index 0000000000..c4401f0836 --- /dev/null +++ b/src/component/matrix/MatrixView.ts @@ -0,0 +1,35 @@ +/* +* 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 MatrixModel from '../../coord/matrix/MatrixModel'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import { ComponentView } from '../../echarts.all'; +import GlobalModel from '../../model/Global'; + +class MatrixView extends ComponentView { + + static type = 'matrix'; + type = MatrixView.type; + + render(matrixModel: MatrixModel, ecModel: GlobalModel, api: ExtensionAPI) { + // TODO + } +} + +export default MatrixView; diff --git a/src/component/matrix/install.ts b/src/component/matrix/install.ts new file mode 100644 index 0000000000..86a0ccc85b --- /dev/null +++ b/src/component/matrix/install.ts @@ -0,0 +1,29 @@ +/* +* 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 { EChartsExtensionInstallRegisters } from '../../extension'; +import MatrixModel from '../../coord/matrix/MatrixModel'; +import MatrixView from './MatrixView'; +import Matrix from '../../coord/matrix/Matrix'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(MatrixModel); + registers.registerComponentView(MatrixView); + registers.registerCoordinateSystem('matrix', Matrix); +} diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts new file mode 100644 index 0000000000..349e251d54 --- /dev/null +++ b/src/coord/matrix/Matrix.ts @@ -0,0 +1,84 @@ +/* +* 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 BoundingRect from 'zrender/src/core/BoundingRect'; +import { MatrixArray } from 'zrender/src/core/matrix'; +import { PrepareCustomInfo } from '../../chart/custom/CustomSeries'; +import { ComponentModel, SeriesModel } from '../../echarts.all'; +import { ComponentOption, ScaleDataValue, SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; +import Axis from '../Axis'; +import { CoordinateSystem, CoordinateSystemClipArea, CoordinateSystemMaster } from '../CoordinateSystem'; +import GlobalModel from '../../model/Global'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import MatrixModel from './MatrixModel'; + +class Matrix implements CoordinateSystem, CoordinateSystemMaster { + + static readonly dimensions = ['']; + + static create(ecModel: GlobalModel, api: ExtensionAPI) { + const matrixList: Matrix[] = []; + + ecModel.eachComponent('matrix', function (matrixModel: MatrixModel) { + const matrix = new Matrix(matrixModel, ecModel, api); + matrixList.push(matrix); + matrixModel.coordinateSystem = matrix; + }); + + ecModel.eachSeries(function (matrixSeries: SeriesModel) { + if (matrixSeries.get('coordinateSystem') === 'matrix') { + // Inject coordinate system + matrixSeries.coordinateSystem = matrixList[matrixSeries.get('matrixIndex') || 0]; + } + }); + return matrixList; + } + + constructor(matrixModel: MatrixModel, ecModel: GlobalModel, api: ExtensionAPI) { + this._model = matrixModel; + } + + private _model: MatrixModel; + + type: string; + master?: CoordinateSystemMaster; + dimensions: string[]; + model?: ComponentModel; + dataToPoint(data: ScaleDataValue | ScaleDataValue[], reserved?: any, out?: number[]): number[] { + throw new Error('Method not implemented.'); + } + pointToData?(point: number[], clamp?: boolean): number | number[] { + throw new Error('Method not implemented.'); + } + containPoint(point: number[]): boolean { + throw new Error('Method not implemented.'); + } + getAxes?: () => Axis[]; + getAxis?: (dim?: string) => Axis; + getBaseAxis?: () => Axis; + getOtherAxis?: (baseAxis: Axis) => Axis; + clampData?: (data: ScaleDataValue[], out?: number[]) => number[]; + getRoamTransform?: () => MatrixArray; + getArea?: (tolerance?: number) => CoordinateSystemClipArea; + getBoundingRect?: () => BoundingRect; + getAxesByScale?: (scaleType: string) => Axis[]; + prepareCustoms?: PrepareCustomInfo; +} + +export default Matrix; diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts new file mode 100644 index 0000000000..de0cba6ccc --- /dev/null +++ b/src/coord/matrix/MatrixModel.ts @@ -0,0 +1,35 @@ +/* +* 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 ComponentModel from '../../model/Component'; +import { BoxLayoutOptionMixin, ComponentOption } from '../../util/types'; +import Matrix from './Matrix'; + +export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { + +} + +class MatrixModel extends ComponentModel { + static type = 'matrix'; + type = MatrixModel.type; + + coordinateSystem: Matrix; +} + +export default MatrixModel; diff --git a/src/export/components.ts b/src/export/components.ts index 8e4e14e26b..934b99027e 100644 --- a/src/export/components.ts +++ b/src/export/components.ts @@ -28,6 +28,7 @@ export {install as GeoComponent} from '../component/geo/install'; export {install as SingleAxisComponent} from '../component/singleAxis/install'; export {install as ParallelComponent} from '../component/parallel/install'; export {install as CalendarComponent} from '../component/calendar/install'; +export {install as MatrixComponent} from '../component/matrix/install'; export {install as GraphicComponent} from '../component/graphic/install'; @@ -77,6 +78,7 @@ export { SingleAxisComponentOption, ParallelComponentOption, CalendarComponentOption, + MatrixComponentOption, GraphicComponentOption, diff --git a/src/export/option.ts b/src/export/option.ts index d25e8197a3..b03e79b7d7 100644 --- a/src/export/option.ts +++ b/src/export/option.ts @@ -33,6 +33,7 @@ import type {SingleAxisOption as SingleAxisComponentOption} from '../coord/singl import type {ParallelAxisOption as ParallelAxisComponentOption} from '../coord/parallel/AxisModel'; import type {ParallelCoordinateSystemOption as ParallelComponentOption} from '../coord/parallel/ParallelModel'; import type {CalendarOption as CalendarComponentOption} from '../coord/calendar/CalendarModel'; +import type {MatrixOption as MatrixComponentOption} from '../coord/matrix/MatrixModel'; import type {ToolboxOption} from '../component/toolbox/ToolboxModel'; import type { TooltipOption as TooltipComponentOption, @@ -155,6 +156,7 @@ export { AngleAxisComponentOption, ParallelComponentOption, CalendarComponentOption, + MatrixComponentOption, TooltipComponentOption, AxisPointerComponentOption, BrushComponentOption, diff --git a/src/util/types.ts b/src/util/types.ts index 01e9321888..9045eadc4a 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1665,6 +1665,11 @@ export interface SeriesOnCalendarOptionMixin { calendarId?: string } +export interface SeriesOnMatrixOptionMixin { + matrixIndex?: number + matrixId?: string +} + export interface SeriesLargeOptionMixin { large?: boolean largeThreshold?: number From 780dd4b09fb642ccdc2af279792e34917c4ef0a7 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 11 Apr 2024 16:13:36 +0800 Subject: [PATCH 02/49] feat: render a rect for matrix coordinate --- src/component/matrix/MatrixView.ts | 21 ++++++- src/coord/matrix/Matrix.ts | 24 +++++++- src/echarts.all.ts | 10 +++- test/matrix.html | 90 ++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 test/matrix.html diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index c4401f0836..e1b7d3984e 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -21,6 +21,7 @@ import MatrixModel from '../../coord/matrix/MatrixModel'; import ExtensionAPI from '../../core/ExtensionAPI'; import { ComponentView } from '../../echarts.all'; import GlobalModel from '../../model/Global'; +import * as graphic from '../../util/graphic'; class MatrixView extends ComponentView { @@ -28,7 +29,25 @@ class MatrixView extends ComponentView { type = MatrixView.type; render(matrixModel: MatrixModel, ecModel: GlobalModel, api: ExtensionAPI) { - // TODO + + const group = this.group; + + group.removeAll(); + + this._renderGrid(matrixModel); + } + + protected _renderGrid(matrixModel: MatrixModel) { + const coordSys = matrixModel.coordinateSystem; + const rect = coordSys.getRect(); + this.group.add(new graphic.Rect({ + shape: rect, + style: { + fill: 'none', + stroke: '#333', + lineWidth: 2 + } + })); } } diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 349e251d54..f6e9555a5f 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -27,11 +27,15 @@ import { CoordinateSystem, CoordinateSystemClipArea, CoordinateSystemMaster } fr import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; +import { LayoutRect, getLayoutRect } from '../../util/layout'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { static readonly dimensions = ['']; + private _model: MatrixModel; + private _rect: LayoutRect; + static create(ecModel: GlobalModel, api: ExtensionAPI) { const matrixList: Matrix[] = []; @@ -54,14 +58,30 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { this._model = matrixModel; } - private _model: MatrixModel; + getRect(): LayoutRect { + return this._rect; + } + + update(ecModel: GlobalModel, api: ExtensionAPI) { + this.resize(this._model, api); + } + + resize(matrixModel: MatrixModel, api: ExtensionAPI) { + const boxLayoutParams = matrixModel.getBoxLayoutParams(); + const gridRect = getLayoutRect( + boxLayoutParams, { + width: api.getWidth(), + height: api.getHeight() + }); + this._rect = gridRect; + } type: string; master?: CoordinateSystemMaster; dimensions: string[]; model?: ComponentModel; dataToPoint(data: ScaleDataValue | ScaleDataValue[], reserved?: any, out?: number[]): number[] { - throw new Error('Method not implemented.'); + return [0, 0]; } pointToData?(point: number[], clamp?: boolean): number | number[] { throw new Error('Method not implemented.'); diff --git a/src/echarts.all.ts b/src/echarts.all.ts index 5eba7d34c7..edfe83ca5e 100644 --- a/src/echarts.all.ts +++ b/src/echarts.all.ts @@ -65,6 +65,7 @@ import { SingleAxisComponent, ParallelComponent, CalendarComponent, + MatrixComponent, GraphicComponent, ToolboxComponent, TooltipComponent, @@ -209,7 +210,14 @@ use(ParallelComponent); // ); use(CalendarComponent); - +// `matrix` coordinate system. for example, +// chart.setOption({ +// matrix: {...}, +// series: [{ +// coordinateSystem: 'matrix' +// }] +// ); +use(MatrixComponent); // ------------------ // Other components diff --git a/test/matrix.html b/test/matrix.html new file mode 100644 index 0000000000..55484d2ab2 --- /dev/null +++ b/test/matrix.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + From 3c1dcb4132b66c3c201d082ebdf42996c70db6a9 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Mon, 15 Apr 2024 18:50:40 +0800 Subject: [PATCH 03/49] feat(matrix): basic table labels --- src/component/matrix.ts | 23 +++++ src/component/matrix/MatrixView.ts | 50 +++++++++- src/coord/matrix/Matrix.ts | 10 +- src/coord/matrix/MatrixDim.ts | 141 +++++++++++++++++++++++++++++ src/coord/matrix/MatrixModel.ts | 25 +++++ src/export/option.ts | 1 + src/model/Global.ts | 1 + test/matrix.html | 10 +- 8 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 src/component/matrix.ts create mode 100644 src/coord/matrix/MatrixDim.ts diff --git a/src/component/matrix.ts b/src/component/matrix.ts new file mode 100644 index 0000000000..2c2752d3ef --- /dev/null +++ b/src/component/matrix.ts @@ -0,0 +1,23 @@ +/* +* 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 { use } from '../extension'; +import { install } from './matrix/install'; + +use(install); diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index e1b7d3984e..4d45962a94 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -17,6 +17,7 @@ * under the License. */ +import { MatrixNodeOption } from '../../coord/matrix/MatrixDim'; import MatrixModel from '../../coord/matrix/MatrixModel'; import ExtensionAPI from '../../core/ExtensionAPI'; import { ComponentView } from '../../echarts.all'; @@ -34,10 +35,10 @@ class MatrixView extends ComponentView { group.removeAll(); - this._renderGrid(matrixModel); + this._renderTable(matrixModel); } - protected _renderGrid(matrixModel: MatrixModel) { + protected _renderTable(matrixModel: MatrixModel) { const coordSys = matrixModel.coordinateSystem; const rect = coordSys.getRect(); this.group.add(new graphic.Rect({ @@ -45,9 +46,52 @@ class MatrixView extends ComponentView { style: { fill: 'none', stroke: '#333', - lineWidth: 2 + lineWidth: 1 } })); + + const xDim = coordSys.getDim('x'); + const yDim = coordSys.getDim('y'); + const xLeavesCnt = xDim.getLeavesCount(); + const yLeavesCnt = yDim.getLeavesCount(); + + const xCells = xDim.getCells(); + const xHeight = xDim.getHeight(); + const yCells = yDim.getCells(); + const yHeight = yDim.getHeight(); + + const cellWidth = rect.width / (xLeavesCnt + yHeight); + const cellHeight = rect.height / (yLeavesCnt + xHeight); + + const xLeft = rect.x + cellWidth * yHeight; + for (let i = 0; i < xCells.length; i++) { + const cell = xCells[i]; + const width = cellWidth * cell.colSpan; + const height = cellHeight * cell.rowSpan; + this.group.add(new graphic.Text({ + x: xLeft + cellWidth * cell.colId + width / 2, + y: rect.y + cellHeight * cell.rowId + height / 2, + style: { + text: cell.value, + fill: '#333', + } + })); + } + + const yTop = cellHeight * xHeight; + for (let i = 0; i < yCells.length; i++) { + const cell = yCells[i]; + const width = cellWidth * cell.colSpan; + const height = cellHeight * cell.rowSpan; + this.group.add(new graphic.Text({ + x: rect.x + cellWidth * cell.rowId + width / 2, + y: yTop + cellHeight * cell.colId + height / 2, + style: { + text: cell.value, + fill: '#333', + } + })); + } } } diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index f6e9555a5f..81523a87e1 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -28,6 +28,7 @@ import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; import { LayoutRect, getLayoutRect } from '../../util/layout'; +import { MatrixDim } from './MatrixDim'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { @@ -35,6 +36,8 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { private _model: MatrixModel; private _rect: LayoutRect; + private _xDim: MatrixDim; + private _yDim: MatrixDim; static create(ecModel: GlobalModel, api: ExtensionAPI) { const matrixList: Matrix[] = []; @@ -56,12 +59,18 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { constructor(matrixModel: MatrixModel, ecModel: GlobalModel, api: ExtensionAPI) { this._model = matrixModel; + this._xDim = new MatrixDim(matrixModel.get('x')); + this._yDim = new MatrixDim(matrixModel.get('y')); } getRect(): LayoutRect { return this._rect; } + getDim(dim: 'x' | 'y'): MatrixDim { + return dim === 'x' ? this._xDim : this._yDim; + } + update(ecModel: GlobalModel, api: ExtensionAPI) { this.resize(this._model, api); } @@ -90,7 +99,6 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { throw new Error('Method not implemented.'); } getAxes?: () => Axis[]; - getAxis?: (dim?: string) => Axis; getBaseAxis?: () => Axis; getOtherAxis?: (baseAxis: Axis) => Axis; clampData?: (data: ScaleDataValue[], out?: number[]) => number[]; diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts new file mode 100644 index 0000000000..325f22f18e --- /dev/null +++ b/src/coord/matrix/MatrixDim.ts @@ -0,0 +1,141 @@ +/* +* 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 type MatrixNodeOption = { + value?: string; + children?: MatrixNodeOption[]; +}; + +export type MatrixNodeRawOption = string | MatrixNodeOption; + +export interface MatrixDimOption { + data?: MatrixNodeOption[]; +} +export interface MatrixDimRawOption { + data?: MatrixNodeRawOption[]; +} + +export interface MatrixCell { + value: string; + rowId: number; + rowSpan: number; + colId: number; + colSpan: number; +} + +export class MatrixDim { + + private _option: MatrixDimOption; + private _cells: MatrixCell[]; + + constructor(option: MatrixDimOption) { + this._option = option || { data: [] }; + if (!this._option.data) { + this._option.data = []; + } + this._initCells(); + console.log(this._cells) + } + + getLeavesCount() { + if (!this._option.data) { + return 0; + } + if (typeof this._option.data === 'string') { + return 1; + } + let cnt = 0; + for (let i = 0; i < this._option.data.length; i++) { + cnt += this._countLeaves(this._option.data[i]); + } + return cnt; + } + + getHeight() { + if (!this._option.data) { + return 0; + } + if (typeof this._option.data === 'string') { + return 1; + } + let height = 0; + for (let i = 0; i < this._option.data.length; i++) { + height = Math.max(height, this._countHeight(this._option.data[i])); + } + return height; + } + + getCells() { + return this._cells; + } + + private _initCells(): void { + this._cells = []; + for (let i = 0, rowId = 0, colId = 0; i < this._option.data.length; i++) { + const node = this._option.data[i]; + const result = this._traverseInitCells(node, rowId, colId); + rowId = result.rowId; + colId = result.colId; + } + } + + private _traverseInitCells(node: MatrixNodeOption, rowId: number, colId: number = 0): { rowId: number, colId: number } { + const cell: MatrixCell = { + value: typeof node === 'string' ? node : node.value, + rowId, + colId, + rowSpan: 1, // Assuming single rowSpan for now + colSpan: node.children ? node.children.length : 1 // Assuming colSpan is the number of children + }; + + this._cells.push(cell); + + if (node.children) { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + colId = this._traverseInitCells(child, rowId + 1, colId).colId; + } + } + + return { rowId, colId: colId + cell.colSpan }; + } + + private _countHeight(node: MatrixNodeOption): number { + if (typeof node === 'string') { + return 1; + } + let height = 0; + for (let i = 0; i < node.children.length; i++) { + height = Math.max(height, this._countHeight(node.children[i])); + } + return height + 1; + } + + private _countLeaves(node: MatrixNodeOption): number { + if (typeof node === 'string') { + return 1; + } + let cnt = 0; + for (let i = 0; i < node.children.length; i++) { + cnt += this._countLeaves(node.children[i]); + } + return cnt; + } + +} diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index de0cba6ccc..38be7d43e3 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -20,9 +20,19 @@ import ComponentModel from '../../model/Component'; import { BoxLayoutOptionMixin, ComponentOption } from '../../util/types'; import Matrix from './Matrix'; +import { MatrixNodeOption } from './MatrixDim'; export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { + mainType?: 'matrix'; + containLabel?: boolean; + + x?: { + data?: MatrixNodeOption[]; + } + y?: { + data?: MatrixNodeOption[]; + } } class MatrixModel extends ComponentModel { @@ -30,6 +40,21 @@ class MatrixModel extends ComponentModel { type = MatrixModel.type; coordinateSystem: Matrix; + + static defaultOption: MatrixOption = { + z: 2, + left: '10%', + top: '10%', + right: '10%', + bottom: '10%', + containLabel: false, + x: { + data: [] + }, + y: { + data: [] + } + } } export default MatrixModel; diff --git a/src/export/option.ts b/src/export/option.ts index b03e79b7d7..af2788d9c7 100644 --- a/src/export/option.ts +++ b/src/export/option.ts @@ -261,6 +261,7 @@ export interface EChartsOption extends ECBasicOption { parallel?: ParallelComponentOption | ParallelComponentOption[]; parallelAxis?: ParallelAxisComponentOption | ParallelAxisComponentOption[]; calendar?: CalendarComponentOption | CalendarComponentOption[]; + matrix?: MatrixComponentOption | MatrixComponentOption[]; toolbox?: ToolboxComponentOption | ToolboxComponentOption[]; tooltip?: TooltipComponentOption | TooltipComponentOption[]; axisPointer?: AxisPointerComponentOption | AxisPointerComponentOption[]; diff --git a/src/model/Global.ts b/src/model/Global.ts index b845d7263d..8b64071a51 100644 --- a/src/model/Global.ts +++ b/src/model/Global.ts @@ -87,6 +87,7 @@ const BUITIN_COMPONENTS_MAP = { singleAxis: 'SingleAxisComponent', parallel: 'ParallelComponent', calendar: 'CalendarComponent', + matrix: 'MatrixComponent', graphic: 'GraphicComponent', toolbox: 'ToolboxComponent', tooltip: 'TooltipComponent', diff --git a/test/matrix.html b/test/matrix.html index 55484d2ab2..bc16b73ded 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -57,7 +57,10 @@ x: { data: [{ value: 'A', - children: ['A1', 'A2', 'A3'] + children: ['A1', 'A2', { + value: 'A3', + children: ['A31', 'A32'] + }] }] }, y: { @@ -67,7 +70,10 @@ series: { type: 'pie', coordinateSystem: 'matrix', - data: [['A1', 'U', 10], ['A1', 'V', 20], ['A2', 'U', 30], ['A2', 'V', 40], ['A3', 'U', 50], ['A3', 'V', 60]] + data: [ + ['A1', 'U', 10], ['A1', 'V', 20], ['A2', 'U', 30], + ['A2', 'V', 40], ['A3', 'U', 50], ['A3', 'V', 60] + ] } }; From ce4530f5ed367452540d842809fa4a13fdef3dae Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 17 Apr 2024 18:52:38 +0800 Subject: [PATCH 04/49] feat(matrix): correct cell info and draw table borders --- src/component/matrix/MatrixView.ts | 94 ++++++++++++++++++++++++++++-- src/coord/matrix/MatrixDim.ts | 49 +++++++++++----- 2 files changed, 124 insertions(+), 19 deletions(-) diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index 4d45962a94..00b850e464 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -45,7 +45,7 @@ class MatrixView extends ComponentView { shape: rect, style: { fill: 'none', - stroke: '#333', + stroke: '#888', lineWidth: 1 } })); @@ -59,38 +59,120 @@ class MatrixView extends ComponentView { const xHeight = xDim.getHeight(); const yCells = yDim.getCells(); const yHeight = yDim.getHeight(); + console.log(xCells) const cellWidth = rect.width / (xLeavesCnt + yHeight); const cellHeight = rect.height / (yLeavesCnt + xHeight); + for (let i = 1; i <= xHeight; ++i) { + this.group.add(new graphic.Line({ + shape: { + x1: rect.x + (i === xHeight ? 0 : cellWidth), + x2: rect.x + rect.width, + y1: rect.y + cellHeight * i, + y2: rect.y + cellHeight * i, + }, + style: { + stroke: '#ccc' + } + })); + } + for (let i = 1; i <= yHeight; ++i) { + this.group.add(new graphic.Line({ + shape: { + x1: rect.x + cellWidth * i, + x2: rect.x + cellWidth * i, + y1: rect.y + (i === yHeight ? 0 : cellHeight), + y2: rect.y + rect.height, + }, + style: { + stroke: '#ccc' + } + })); + } + const xLeft = rect.x + cellWidth * yHeight; for (let i = 0; i < xCells.length; i++) { const cell = xCells[i]; const width = cellWidth * cell.colSpan; const height = cellHeight * cell.rowSpan; + const left = xLeft + cellWidth * cell.colId; + const top = rect.y + cellHeight * cell.rowId; this.group.add(new graphic.Text({ - x: xLeft + cellWidth * cell.colId + width / 2, - y: rect.y + cellHeight * cell.rowId + height / 2, + x: left + width / 2, + y: top + height / 2, style: { text: cell.value, fill: '#333', } })); + + this.group.add(new graphic.Line({ + shape: { + x1: left, + x2: left, + y1: top, + y2: top + height, + }, + style: { + stroke: '#ccc' + } + })); + if (left + width < rect.x + rect.width) { + this.group.add(new graphic.Line({ + shape: { + x1: left + width, + x2: left + width, + y1: top, + y2: top + height, + }, + style: { + stroke: '#ccc' + } + })); + } } - const yTop = cellHeight * xHeight; + const yTop = rect.y + cellHeight * xHeight; for (let i = 0; i < yCells.length; i++) { const cell = yCells[i]; const width = cellWidth * cell.colSpan; const height = cellHeight * cell.rowSpan; + const left = rect.x + cellWidth * cell.rowId; + const top = yTop + cellHeight * cell.colId; this.group.add(new graphic.Text({ - x: rect.x + cellWidth * cell.rowId + width / 2, - y: yTop + cellHeight * cell.colId + height / 2, + x: left + width / 2, + y: top + height / 2, style: { text: cell.value, fill: '#333', } })); + + this.group.add(new graphic.Line({ + shape: { + x1: left, + x2: left + width, + y1: top, + y2: top, + }, + style: { + stroke: '#ccc' + } + })); + if (top + height < rect.y + rect.height) { + this.group.add(new graphic.Line({ + shape: { + x1: left, + x2: left + width, + y1: top + height, + y2: top + height, + }, + style: { + stroke: '#ccc' + } + })); + } } } } diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index 325f22f18e..a89b52d91c 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -17,6 +17,8 @@ * under the License. */ +import { reduce } from "zrender/src/core/util"; + export type MatrixNodeOption = { value?: string; children?: MatrixNodeOption[]; @@ -96,24 +98,45 @@ export class MatrixDim { } private _traverseInitCells(node: MatrixNodeOption, rowId: number, colId: number = 0): { rowId: number, colId: number } { - const cell: MatrixCell = { - value: typeof node === 'string' ? node : node.value, - rowId, - colId, - rowSpan: 1, // Assuming single rowSpan for now - colSpan: node.children ? node.children.length : 1 // Assuming colSpan is the number of children - }; + if (typeof node === 'string') { + // When node is a string, it's a leaf with colSpan of 1 + this._cells.push({ + value: node, + rowId, + colId, + rowSpan: 1, + colSpan: 1 + }); + return { rowId, colId: colId + 1 }; + } - this._cells.push(cell); + let currentColId = colId; + let totalColSpan = 0; + let childrenColSpans = []; - if (node.children) { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - colId = this._traverseInitCells(child, rowId + 1, colId).colId; + if (node.children && node.children.length) { + for (let child of node.children) { + const result = this._traverseInitCells(child, rowId + 1, currentColId); + const childColSpan = result.colId - currentColId; + childrenColSpans.push(childColSpan); + currentColId = result.colId; } + totalColSpan = reduce(childrenColSpans, (a, b) => a + b, 0); + } else { + // If no children, it's a leaf node with colSpan of 1 + totalColSpan = 1; } - return { rowId, colId: colId + cell.colSpan }; + // Create cell for the current node + this._cells.push({ + value: node.value, + rowId, + colId, + rowSpan: 1, + colSpan: totalColSpan + }); + + return { rowId, colId: colId + totalColSpan }; } private _countHeight(node: MatrixNodeOption): number { From 55883fca5484da5b7a3256845cd4b5c5092ec074 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 18 Apr 2024 16:29:47 +0800 Subject: [PATCH 05/49] feat(matrix): render cells border and text --- src/component/matrix/MatrixView.ts | 180 ++++++++++++++--------------- src/coord/matrix/MatrixModel.ts | 40 ++++++- 2 files changed, 119 insertions(+), 101 deletions(-) diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index 00b850e464..d866b1b291 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -17,10 +17,10 @@ * under the License. */ -import { MatrixNodeOption } from '../../coord/matrix/MatrixDim'; import MatrixModel from '../../coord/matrix/MatrixModel'; import ExtensionAPI from '../../core/ExtensionAPI'; -import { ComponentView } from '../../echarts.all'; +import ComponentView from '../../view/Component'; +import { createTextStyle } from '../../label/labelStyle'; import GlobalModel from '../../model/Global'; import * as graphic from '../../util/graphic'; @@ -40,57 +40,25 @@ class MatrixView extends ComponentView { protected _renderTable(matrixModel: MatrixModel) { const coordSys = matrixModel.coordinateSystem; - const rect = coordSys.getRect(); - this.group.add(new graphic.Rect({ - shape: rect, - style: { - fill: 'none', - stroke: '#888', - lineWidth: 1 - } - })); - const xDim = coordSys.getDim('x'); const yDim = coordSys.getDim('y'); + const xModel = matrixModel.getModel('x'); + const yModel = matrixModel.getModel('y'); + const xLabelModel = xModel.getModel('label'); + const yLabelModel = yModel.getModel('label'); + const xItemStyle = xModel.getModel('itemStyle').getItemStyle(); + const yItemStyle = yModel.getModel('itemStyle').getItemStyle(); + + const rect = coordSys.getRect(); const xLeavesCnt = xDim.getLeavesCount(); const yLeavesCnt = yDim.getLeavesCount(); - const xCells = xDim.getCells(); const xHeight = xDim.getHeight(); const yCells = yDim.getCells(); const yHeight = yDim.getHeight(); - console.log(xCells) - const cellWidth = rect.width / (xLeavesCnt + yHeight); const cellHeight = rect.height / (yLeavesCnt + xHeight); - for (let i = 1; i <= xHeight; ++i) { - this.group.add(new graphic.Line({ - shape: { - x1: rect.x + (i === xHeight ? 0 : cellWidth), - x2: rect.x + rect.width, - y1: rect.y + cellHeight * i, - y2: rect.y + cellHeight * i, - }, - style: { - stroke: '#ccc' - } - })); - } - for (let i = 1; i <= yHeight; ++i) { - this.group.add(new graphic.Line({ - shape: { - x1: rect.x + cellWidth * i, - x2: rect.x + cellWidth * i, - y1: rect.y + (i === yHeight ? 0 : cellHeight), - y2: rect.y + rect.height, - }, - style: { - stroke: '#ccc' - } - })); - } - const xLeft = rect.x + cellWidth * yHeight; for (let i = 0; i < xCells.length; i++) { const cell = xCells[i]; @@ -98,39 +66,25 @@ class MatrixView extends ComponentView { const height = cellHeight * cell.rowSpan; const left = xLeft + cellWidth * cell.colId; const top = rect.y + cellHeight * cell.rowId; - this.group.add(new graphic.Text({ - x: left + width / 2, - y: top + height / 2, - style: { - text: cell.value, - fill: '#333', - } - })); - this.group.add(new graphic.Line({ + this.group.add(new graphic.Rect({ shape: { - x1: left, - x2: left, - y1: top, - y2: top + height, + x: left, + y: top, + width: width, + height: height }, - style: { - stroke: '#ccc' - } + style: xItemStyle + })); + this.group.add(new graphic.Text({ + style: createTextStyle(xLabelModel, { + text: cell.value, + x: left + width / 2, + y: top + height / 2, + verticalAlign: 'middle', + align: 'center' + }) })); - if (left + width < rect.x + rect.width) { - this.group.add(new graphic.Line({ - shape: { - x1: left + width, - x2: left + width, - y1: top, - y2: top + height, - }, - style: { - stroke: '#ccc' - } - })); - } } const yTop = rect.y + cellHeight * xHeight; @@ -140,40 +94,74 @@ class MatrixView extends ComponentView { const height = cellHeight * cell.rowSpan; const left = rect.x + cellWidth * cell.rowId; const top = yTop + cellHeight * cell.colId; - this.group.add(new graphic.Text({ - x: left + width / 2, - y: top + height / 2, - style: { - text: cell.value, - fill: '#333', - } - })); - this.group.add(new graphic.Line({ + this.group.add(new graphic.Rect({ shape: { - x1: left, - x2: left + width, - y1: top, - y2: top, + x: left, + y: top, + width: width, + height: height }, - style: { - stroke: '#ccc' - } + style: yItemStyle })); - if (top + height < rect.y + rect.height) { - this.group.add(new graphic.Line({ + this.group.add(new graphic.Text({ + style: createTextStyle(yLabelModel, { + text: cell.value, + x: left + width / 2, + y: top + height / 2, + verticalAlign: 'middle', + align: 'center' + }) + })); + } + + // Inner cells + const innerBackgroundStyle = matrixModel + .getModel('innerBackgroundStyle') + .getItemStyle(); + for (let i = 0; i < xLeavesCnt; i++) { + for (let j = 0; j < yLeavesCnt; j++) { + const left = xLeft + cellWidth * i; + const top = yTop + cellHeight * j; + this.group.add(new graphic.Rect({ shape: { - x1: left, - x2: left + width, - y1: top + height, - y2: top + height, + x: left, + y: top, + width: cellWidth, + height: cellHeight }, - style: { - stroke: '#ccc' - } + style: innerBackgroundStyle })); } } + + // Outer border + const backgroundStyle = matrixModel + .getModel('backgroundStyle') + .getItemStyle(); + this.group.add(new graphic.Rect({ + shape: rect, + style: backgroundStyle + })); + // Header border + this.group.add(new graphic.Line({ + shape: { + x1: rect.x, + y1: yTop, + x2: rect.x + rect.width, + y2: yTop + }, + style: backgroundStyle + })); + this.group.add(new graphic.Line({ + shape: { + x1: xLeft, + y1: rect.y, + x2: xLeft, + y2: rect.y + rect.height + }, + style: backgroundStyle + })); } } diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 38be7d43e3..704dd0649a 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -18,7 +18,7 @@ */ import ComponentModel from '../../model/Component'; -import { BoxLayoutOptionMixin, ComponentOption } from '../../util/types'; +import { BoxLayoutOptionMixin, ComponentOption, ItemStyleOption, LabelOption } from '../../util/types'; import Matrix from './Matrix'; import { MatrixNodeOption } from './MatrixDim'; @@ -29,12 +29,32 @@ export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { x?: { data?: MatrixNodeOption[]; + label?: LabelOption; + itemStyle?: ItemStyleOption; } y?: { data?: MatrixNodeOption[]; + label?: LabelOption; + itemStyle?: ItemStyleOption; } + + backgroundStyle?: ItemStyleOption; + innerBackgroundStyle?: ItemStyleOption; } +const defaultDimOption = { + data: [] as MatrixNodeOption[], + label: { + show: true, + color: '#333' + }, + itemStyle: { + color: 'none', + borderWidth: 1, + borderColor: '#ccc' + } +}; + class MatrixModel extends ComponentModel { static type = 'matrix'; type = MatrixModel.type; @@ -48,11 +68,21 @@ class MatrixModel extends ComponentModel { right: '10%', bottom: '10%', containLabel: false, - x: { - data: [] + x: defaultDimOption, + y: defaultDimOption, + backgroundStyle: { + color: 'none', + borderColor: '#777', + borderWidth: 1, + borderType: 'solid', + opacity: 1 }, - y: { - data: [] + innerBackgroundStyle: { + color: 'none', + borderColor: '#ccc', + borderWidth: 1, + borderType: 'solid', + opacity: 1 } } } From ef9c3bd1926a9ab8008620b16316d8e52cd04d0a Mon Sep 17 00:00:00 2001 From: Ovilia Date: Mon, 22 Apr 2024 16:57:05 +0800 Subject: [PATCH 06/49] feat(matrix): matrix with heatmap --- src/chart/heatmap/HeatmapView.ts | 28 +++++++++++++----- src/coord/matrix/Matrix.ts | 49 ++++++++++++++++++++++++++------ src/coord/matrix/MatrixDim.ts | 13 +++++++++ src/coord/matrix/MatrixModel.ts | 2 +- test/matrix.html | 12 ++++++-- 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts index d0de889ba1..069b30c3c1 100644 --- a/src/chart/heatmap/HeatmapView.ts +++ b/src/chart/heatmap/HeatmapView.ts @@ -34,6 +34,7 @@ import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Calendar from '../../coord/calendar/Calendar'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import type Element from 'zrender/src/Element'; +import Matrix from '../../coord/matrix/Matrix'; // Coord can be 'geo' 'bmap' 'amap' 'leaflet'... interface GeoLikeCoordSys extends CoordinateSystem { @@ -129,8 +130,11 @@ class HeatmapView extends ChartView { this.group.removeAll(); const coordSys = seriesModel.coordinateSystem; - if (coordSys.type === 'cartesian2d' || coordSys.type === 'calendar') { - this._renderOnCartesianAndCalendar(seriesModel, api, 0, seriesModel.getData().count()); + if (coordSys.type === 'cartesian2d' + || coordSys.type === 'calendar' + || coordSys.type === 'matrix' + ) { + this._renderOnGridLike(seriesModel, api, 0, seriesModel.getData().count()); } else if (isGeoCoordSys(coordSys)) { this._renderOnGeo( @@ -157,7 +161,7 @@ class HeatmapView extends ChartView { } else { this._progressiveEls = []; - this._renderOnCartesianAndCalendar(seriesModel, api, params.start, params.end, true); + this._renderOnGridLike(seriesModel, api, params.start, params.end, true); } } } @@ -166,16 +170,16 @@ class HeatmapView extends ChartView { graphic.traverseElements(this._progressiveEls || this.group, cb); } - _renderOnCartesianAndCalendar( + _renderOnGridLike( seriesModel: HeatmapSeriesModel, api: ExtensionAPI, start: number, end: number, incremental?: boolean ) { - - const coordSys = seriesModel.coordinateSystem as Cartesian2D | Calendar; + const coordSys = seriesModel.coordinateSystem as Cartesian2D | Calendar | Matrix; const isCartesian2d = isCoordinateSystemType(coordSys, 'cartesian2d'); + const isMatrix = isCoordinateSystemType(coordSys, 'matrix'); let width; let height; let xAxisExtent; @@ -214,7 +218,7 @@ class HeatmapView extends ChartView { let blurScope = emphasisModel.get('blurScope'); let emphasisDisabled = emphasisModel.get('disabled'); - const dataDims = isCartesian2d + const dataDims = (isCartesian2d || isMatrix) ? [ data.mapDimension('x'), data.mapDimension('y'), @@ -260,6 +264,16 @@ class HeatmapView extends ChartView { style }); } + else if (isMatrix) { + rect = new graphic.Rect({ + z2: 1, + shape: coordSys.dataToRect( + data.get(dataDims[0], idx) as string, + data.get(dataDims[1], idx) as string + ), + style + }); + } else { // Ignore empty data if (isNaN(data.get(dataDims[1], idx) as number)) { diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 81523a87e1..2092e708d7 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -17,11 +17,11 @@ * under the License. */ -import BoundingRect from 'zrender/src/core/BoundingRect'; +import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import { MatrixArray } from 'zrender/src/core/matrix'; import { PrepareCustomInfo } from '../../chart/custom/CustomSeries'; import { ComponentModel, SeriesModel } from '../../echarts.all'; -import { ComponentOption, ScaleDataValue, SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; +import { ComponentOption, OrdinalRawValue, ScaleDataValue, SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; import Axis from '../Axis'; import { CoordinateSystem, CoordinateSystemClipArea, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; @@ -32,12 +32,15 @@ import { MatrixDim } from './MatrixDim'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { - static readonly dimensions = ['']; + static readonly dimensions = ['x', 'y', 'value']; + readonly dimensions = Matrix.dimensions; + readonly type = 'matrix'; private _model: MatrixModel; private _rect: LayoutRect; private _xDim: MatrixDim; private _yDim: MatrixDim; + private _lineWidth: number; static create(ecModel: GlobalModel, api: ExtensionAPI) { const matrixList: Matrix[] = []; @@ -83,15 +86,45 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { height: api.getHeight() }); this._rect = gridRect; + this._lineWidth = matrixModel.getModel('backgroundStyle') + .getItemStyle().lineWidth || 0; + } + + dataToPoint(x: string, y: string, reserved?: any, out?: number[]): number[] { + const xCell = this._xDim.getCell(x); + const yCell = this._yDim.getCell(y); + const xLeavesCnt = this._xDim.getLeavesCount(); + const yLeavesCnt = this._yDim.getLeavesCount(); + const xHeight = this._xDim.getHeight(); + const yHeight = this._yDim.getHeight(); + return [ + 0, 0 + ] + } + + dataToRect(x: string, y: string, clamp?: boolean): RectLike { + const xCell = this._xDim.getCell(x); + const yCell = this._yDim.getCell(y); + const xLeavesCnt = this._xDim.getLeavesCount(); + const yLeavesCnt = this._yDim.getLeavesCount(); + const xHeight = this._xDim.getHeight(); + const yHeight = this._yDim.getHeight(); + // TODO: each cell may have different width and height + const cellWidth = this._rect.width / (xLeavesCnt + yHeight) * xCell.colSpan; + const cellHeight = this._rect.height / (yLeavesCnt + xHeight) * yCell.rowSpan; + const halfLineWidth = this._lineWidth / 2; + return { + x: this._rect.x + this._rect.width / (xLeavesCnt + yHeight) + * (xCell.colId + yHeight) + halfLineWidth, + y: this._rect.y + this._rect.height / (yLeavesCnt + xHeight) + * (yCell.colId + xHeight) + halfLineWidth, + width: cellWidth - halfLineWidth * 2, + height: cellHeight - halfLineWidth * 2 + }; } - type: string; master?: CoordinateSystemMaster; - dimensions: string[]; model?: ComponentModel; - dataToPoint(data: ScaleDataValue | ScaleDataValue[], reserved?: any, out?: number[]): number[] { - return [0, 0]; - } pointToData?(point: number[], clamp?: boolean): number | number[] { throw new Error('Method not implemented.'); } diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index a89b52d91c..a21a9a0bfd 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -45,6 +45,7 @@ export class MatrixDim { private _option: MatrixDimOption; private _cells: MatrixCell[]; + private _height: number; constructor(option: MatrixDimOption) { this._option = option || { data: [] }; @@ -70,6 +71,9 @@ export class MatrixDim { } getHeight() { + if (this._height != null) { + return this._height; + } if (!this._option.data) { return 0; } @@ -80,6 +84,7 @@ export class MatrixDim { for (let i = 0; i < this._option.data.length; i++) { height = Math.max(height, this._countHeight(this._option.data[i])); } + this._height = height; return height; } @@ -87,6 +92,14 @@ export class MatrixDim { return this._cells; } + getCell(value: string) { + for (let i = 0; i < this._cells.length; i++) { + if (this._cells[i].value === value) { + return this._cells[i]; + } + } + } + private _initCells(): void { this._cells = []; for (let i = 0, rowId = 0, colId = 0; i < this._option.data.length; i++) { diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 704dd0649a..93663a1ec6 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -72,7 +72,7 @@ class MatrixModel extends ComponentModel { y: defaultDimOption, backgroundStyle: { color: 'none', - borderColor: '#777', + borderColor: '#aaa', borderWidth: 1, borderType: 'solid', opacity: 1 diff --git a/test/matrix.html b/test/matrix.html index bc16b73ded..fd0841a729 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -67,12 +67,20 @@ data: ['U', 'V'] } }, + visualMap: { + type: 'continuous', + min: 0, + max: 80, + top: 'middle', + dimension: 2, + calculable: true + }, series: { - type: 'pie', + type: 'heatmap', coordinateSystem: 'matrix', data: [ ['A1', 'U', 10], ['A1', 'V', 20], ['A2', 'U', 30], - ['A2', 'V', 40], ['A3', 'U', 50], ['A3', 'V', 60] + ['A2', 'V', 40], ['A31', 'U', 50], ['A3', 'V', 60] ] } }; From 0b90a953003b61a2113e09754656a013175e2bfb Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 25 Apr 2024 18:46:32 +0800 Subject: [PATCH 07/49] test(matrix): add test for pie --- test/matrix.html | 64 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/test/matrix.html b/test/matrix.html index fd0841a729..c4e80ee7a7 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -38,13 +38,14 @@
+
- --> + + + + + From ce5fa43307adb09cfbf3105fa5e20adc8ff5ab9d Mon Sep 17 00:00:00 2001 From: Ovilia Date: Fri, 26 Apr 2024 15:52:49 +0800 Subject: [PATCH 08/49] feat(matrix): pie series with matrix --- src/chart/pie/PieSeries.ts | 3 ++- src/coord/matrix/Matrix.ts | 28 ++++++++++++++++++++-------- src/coord/matrix/MatrixDim.ts | 8 +++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts index 13e220dc58..c50da04054 100644 --- a/src/chart/pie/PieSeries.ts +++ b/src/chart/pie/PieSeries.ts @@ -168,8 +168,9 @@ class PieSeriesModel extends SeriesModel { * @overwrite */ getInitialData(this: PieSeriesModel): SeriesData { + const isMatrix = this.option.coordinateSystem === 'matrix'; return createSeriesDataSimply(this, { - coordDimensions: ['value'], + coordDimensions: isMatrix ? ['x', 'y', 'value'] : ['value'], encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) }); } diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 2092e708d7..bef3b5ffdf 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -21,7 +21,10 @@ import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import { MatrixArray } from 'zrender/src/core/matrix'; import { PrepareCustomInfo } from '../../chart/custom/CustomSeries'; import { ComponentModel, SeriesModel } from '../../echarts.all'; -import { ComponentOption, OrdinalRawValue, ScaleDataValue, SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; +import { + ComponentOption, ParsedValue, ScaleDataValue, + SeriesOnMatrixOptionMixin, SeriesOption +} from '../../util/types'; import Axis from '../Axis'; import { CoordinateSystem, CoordinateSystemClipArea, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; @@ -29,10 +32,15 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; import { LayoutRect, getLayoutRect } from '../../util/layout'; import { MatrixDim } from './MatrixDim'; +import { isArray } from 'zrender/src/core/util'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { static readonly dimensions = ['x', 'y', 'value']; + static getDimensionsInfo() { + return ['x', 'y', 'value']; + } + readonly dimensions = Matrix.dimensions; readonly type = 'matrix'; @@ -90,26 +98,30 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { .getItemStyle().lineWidth || 0; } - dataToPoint(x: string, y: string, reserved?: any, out?: number[]): number[] { - const xCell = this._xDim.getCell(x); - const yCell = this._yDim.getCell(y); + dataToPoint(data: string[]): number[] { + const xCell = this._xDim.getCell(data[0]); + const yCell = this._yDim.getCell(data[1]); const xLeavesCnt = this._xDim.getLeavesCount(); const yLeavesCnt = this._yDim.getLeavesCount(); const xHeight = this._xDim.getHeight(); const yHeight = this._yDim.getHeight(); + const cellWidth = this._rect.width / (xLeavesCnt + yHeight) * xCell.colSpan; + const cellHeight = this._rect.height / (yLeavesCnt + xHeight) * yCell.rowSpan; return [ - 0, 0 - ] + this._rect.x + this._rect.width / (xLeavesCnt + yHeight) + * (xCell.colId + yHeight) + cellWidth / 2, + this._rect.y + this._rect.height / (yLeavesCnt + xHeight) + * (yCell.colId + xHeight) + cellHeight / 2 + ]; } - dataToRect(x: string, y: string, clamp?: boolean): RectLike { + dataToRect(x: string, y: string): RectLike { const xCell = this._xDim.getCell(x); const yCell = this._yDim.getCell(y); const xLeavesCnt = this._xDim.getLeavesCount(); const yLeavesCnt = this._yDim.getLeavesCount(); const xHeight = this._xDim.getHeight(); const yHeight = this._yDim.getHeight(); - // TODO: each cell may have different width and height const cellWidth = this._rect.width / (xLeavesCnt + yHeight) * xCell.colSpan; const cellHeight = this._rect.height / (yLeavesCnt + xHeight) * yCell.rowSpan; const halfLineWidth = this._lineWidth / 2; diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index a21a9a0bfd..a004362f30 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -46,6 +46,7 @@ export class MatrixDim { private _option: MatrixDimOption; private _cells: MatrixCell[]; private _height: number; + private _leavesCount: number; constructor(option: MatrixDimOption) { this._option = option || { data: [] }; @@ -53,20 +54,25 @@ export class MatrixDim { this._option.data = []; } this._initCells(); - console.log(this._cells) } getLeavesCount() { + if (this._leavesCount != null) { + return this._leavesCount; + } if (!this._option.data) { + this._leavesCount = 0; return 0; } if (typeof this._option.data === 'string') { + this._leavesCount = 1; return 1; } let cnt = 0; for (let i = 0; i < this._option.data.length; i++) { cnt += this._countLeaves(this._option.data[i]); } + this._leavesCount = cnt; return cnt; } From 9b1c9a089bfeaf941ee6ebb3cd27faea57a672f0 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Sun, 28 Apr 2024 18:29:09 +0800 Subject: [PATCH 09/49] test(matrix): test case for pie series --- test/matrix.html | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/test/matrix.html b/test/matrix.html index c4e80ee7a7..eb94a03f39 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -45,7 +45,7 @@ - + @@ -110,7 +110,6 @@ // './data/nutrients.json' ], function (echarts) { var option; - option = { matrix: { x: { @@ -126,17 +125,47 @@ data: ['U', 'V'] } }, - series: { + series: [{ type: 'pie', coordinateSystem: 'matrix', + center: ['A1', 'U'], + radius: 18, data: [ - ['A1', 'U', 10], ['A1', 'V', 20], ['A2', 'U', 30], - ['A2', 'V', 40], ['A31', 'U', 50], ['A3', 'V', 60] + { + value: 12, + name: 'x' + }, { + value: 24, + name: 'y' + }, { + value: 23, + name: 'z' + } ], label: { show: true } - } + }, { + type: 'pie', + coordinateSystem: 'matrix', + center: ['A31', 'V'], + radius: 18, + data: [ + { + value: 18, + name: 'x' + }, { + value: 44, + name: 'y' + }, { + value: 43, + name: 'z' + } + ], + label: { + show: true + } + }] }; var chart = testHelper.create(echarts, 'main1', { From 859ab1d229d22cf6473da8a5adcb8a03ede9723e Mon Sep 17 00:00:00 2001 From: Ovilia Date: Mon, 29 Apr 2024 16:29:00 +0800 Subject: [PATCH 10/49] feat(matrix): graph, scatter, custom --- src/chart/custom/CustomView.ts | 4 ++- src/chart/heatmap/HeatmapView.ts | 6 +++-- src/component/matrix/MatrixView.ts | 4 +-- src/coord/matrix/Matrix.ts | 10 +++---- src/coord/matrix/MatrixDim.ts | 6 +++-- src/coord/matrix/prepareCustom.ts | 43 ++++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/coord/matrix/prepareCustom.ts diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts index ee4e244cf7..603b5ebb96 100644 --- a/src/chart/custom/CustomView.ts +++ b/src/chart/custom/CustomView.ts @@ -50,6 +50,7 @@ import prepareGeo from '../../coord/geo/prepareCustom'; import prepareSingleAxis from '../../coord/single/prepareCustom'; import preparePolar from '../../coord/polar/prepareCustom'; import prepareCalendar from '../../coord/calendar/prepareCustom'; +import prepareMatrix from '../../coord/matrix/prepareCustom'; import SeriesData, { DefaultDataVisual } from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; @@ -166,7 +167,8 @@ const prepareCustoms: Dictionary = { geo: prepareGeo, single: prepareSingleAxis, polar: preparePolar, - calendar: prepareCalendar + calendar: prepareCalendar, + matrix: prepareMatrix }; diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts index 069b30c3c1..10f169aa05 100644 --- a/src/chart/heatmap/HeatmapView.ts +++ b/src/chart/heatmap/HeatmapView.ts @@ -268,8 +268,10 @@ class HeatmapView extends ChartView { rect = new graphic.Rect({ z2: 1, shape: coordSys.dataToRect( - data.get(dataDims[0], idx) as string, - data.get(dataDims[1], idx) as string + [ + data.get(dataDims[0], idx) as string, + data.get(dataDims[1], idx) as string + ] ), style }); diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index d866b1b291..a3f9217bec 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -90,8 +90,8 @@ class MatrixView extends ComponentView { const yTop = rect.y + cellHeight * xHeight; for (let i = 0; i < yCells.length; i++) { const cell = yCells[i]; - const width = cellWidth * cell.colSpan; - const height = cellHeight * cell.rowSpan; + const width = cellWidth * cell.rowSpan; + const height = cellHeight * cell.colSpan; const left = rect.x + cellWidth * cell.rowId; const top = yTop + cellHeight * cell.colId; diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index bef3b5ffdf..50573db759 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -22,7 +22,7 @@ import { MatrixArray } from 'zrender/src/core/matrix'; import { PrepareCustomInfo } from '../../chart/custom/CustomSeries'; import { ComponentModel, SeriesModel } from '../../echarts.all'; import { - ComponentOption, ParsedValue, ScaleDataValue, + ComponentOption, ScaleDataValue, SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; import Axis from '../Axis'; @@ -98,7 +98,7 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { .getItemStyle().lineWidth || 0; } - dataToPoint(data: string[]): number[] { + dataToPoint(data: [string, string]): number[] { const xCell = this._xDim.getCell(data[0]); const yCell = this._yDim.getCell(data[1]); const xLeavesCnt = this._xDim.getLeavesCount(); @@ -115,9 +115,9 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { ]; } - dataToRect(x: string, y: string): RectLike { - const xCell = this._xDim.getCell(x); - const yCell = this._yDim.getCell(y); + dataToRect(data: [string, string]): RectLike { + const xCell = this._xDim.getCell(data[0]); + const yCell = this._yDim.getCell(data[1]); const xLeavesCnt = this._xDim.getLeavesCount(); const yLeavesCnt = this._yDim.getLeavesCount(); const xHeight = this._xDim.getHeight(); diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index a004362f30..48390abc59 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -18,6 +18,7 @@ */ import { reduce } from "zrender/src/core/util"; +import { ParsedValue } from "../../util/types"; export type MatrixNodeOption = { value?: string; @@ -98,9 +99,10 @@ export class MatrixDim { return this._cells; } - getCell(value: string) { + getCell(value: ParsedValue) { for (let i = 0; i < this._cells.length; i++) { - if (this._cells[i].value === value) { + // value can be number while this._cells[i].value is string + if (this._cells[i].value == value) { return this._cells[i]; } } diff --git a/src/coord/matrix/prepareCustom.ts b/src/coord/matrix/prepareCustom.ts new file mode 100644 index 0000000000..b014c22ba0 --- /dev/null +++ b/src/coord/matrix/prepareCustom.ts @@ -0,0 +1,43 @@ +/* +* 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 Matrix from './Matrix'; + +export default function matrixPrepareCustom(coordSys: Matrix) { + const rect = coordSys.getRect(); + + return { + coordSys: { + type: 'matrix', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + }, + api: { + coord: function (data: [string, string]) { + return coordSys.dataToPoint(data); + }, + size: function (data: [string, string]) { + const rect = coordSys.dataToRect(data); + return [rect.width, rect.height]; + } + } + }; +} From 0daaae1c76ed3c90264dcff32dbf12cea4869779 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 8 May 2024 18:01:44 +0800 Subject: [PATCH 11/49] test(matrix): update test cases --- src/coord/matrix/Matrix.ts | 1 - test/matrix.html | 225 ++++++++++++++++++++++++++++++++++++ test/matrix_covariance.html | 131 +++++++++++++++++++++ 3 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 test/matrix_covariance.html diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 50573db759..e3eeeee939 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -32,7 +32,6 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; import { LayoutRect, getLayoutRect } from '../../util/layout'; import { MatrixDim } from './MatrixDim'; -import { isArray } from 'zrender/src/core/util'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { diff --git a/test/matrix.html b/test/matrix.html index eb94a03f39..162087e7ad 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -39,6 +39,9 @@
+
+
+
@@ -176,6 +179,228 @@ }); }); + + + + + + + + + + + + + + + + + + + + + diff --git a/test/matrix_covariance.html b/test/matrix_covariance.html new file mode 100644 index 0000000000..56bc49d1e9 --- /dev/null +++ b/test/matrix_covariance.html @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + From e510f3e57cc010a80b9e38be4fc94756f19f1a2b Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 9 May 2024 15:40:45 +0800 Subject: [PATCH 12/49] fix(matrix): fix lint errors --- src/coord/matrix/Matrix.ts | 31 ++++-------------- src/coord/matrix/MatrixDim.ts | 18 +++++++---- src/coord/matrix/MatrixModel.ts | 2 +- test/matrix.html | 56 +++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index e3eeeee939..e2f585fe65 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -17,16 +17,10 @@ * under the License. */ -import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; -import { MatrixArray } from 'zrender/src/core/matrix'; -import { PrepareCustomInfo } from '../../chart/custom/CustomSeries'; -import { ComponentModel, SeriesModel } from '../../echarts.all'; -import { - ComponentOption, ScaleDataValue, - SeriesOnMatrixOptionMixin, SeriesOption -} from '../../util/types'; -import Axis from '../Axis'; -import { CoordinateSystem, CoordinateSystemClipArea, CoordinateSystemMaster } from '../CoordinateSystem'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { SeriesModel } from '../../echarts.all'; +import { SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; +import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; @@ -134,23 +128,10 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { }; } - master?: CoordinateSystemMaster; - model?: ComponentModel; - pointToData?(point: number[], clamp?: boolean): number | number[] { - throw new Error('Method not implemented.'); - } containPoint(point: number[]): boolean { - throw new Error('Method not implemented.'); + console.warn('Not implemented.'); + return false; } - getAxes?: () => Axis[]; - getBaseAxis?: () => Axis; - getOtherAxis?: (baseAxis: Axis) => Axis; - clampData?: (data: ScaleDataValue[], out?: number[]) => number[]; - getRoamTransform?: () => MatrixArray; - getArea?: (tolerance?: number) => CoordinateSystemClipArea; - getBoundingRect?: () => BoundingRect; - getAxesByScale?: (scaleType: string) => Axis[]; - prepareCustoms?: PrepareCustomInfo; } export default Matrix; diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index 48390abc59..94e7391f3d 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -17,8 +17,8 @@ * under the License. */ -import { reduce } from "zrender/src/core/util"; -import { ParsedValue } from "../../util/types"; +import { reduce } from 'zrender/src/core/util'; +import { ParsedValue } from '../../util/types'; export type MatrixNodeOption = { value?: string; @@ -102,6 +102,7 @@ export class MatrixDim { getCell(value: ParsedValue) { for (let i = 0; i < this._cells.length; i++) { // value can be number while this._cells[i].value is string + // eslint-disable-next-line eqeqeq if (this._cells[i].value == value) { return this._cells[i]; } @@ -118,7 +119,11 @@ export class MatrixDim { } } - private _traverseInitCells(node: MatrixNodeOption, rowId: number, colId: number = 0): { rowId: number, colId: number } { + private _traverseInitCells( + node: MatrixNodeOption, + rowId: number, + colId: number = 0 + ): { rowId: number, colId: number } { if (typeof node === 'string') { // When node is a string, it's a leaf with colSpan of 1 this._cells.push({ @@ -133,17 +138,18 @@ export class MatrixDim { let currentColId = colId; let totalColSpan = 0; - let childrenColSpans = []; + const childrenColSpans = []; if (node.children && node.children.length) { - for (let child of node.children) { + for (const child of node.children) { const result = this._traverseInitCells(child, rowId + 1, currentColId); const childColSpan = result.colId - currentColId; childrenColSpans.push(childColSpan); currentColId = result.colId; } totalColSpan = reduce(childrenColSpans, (a, b) => a + b, 0); - } else { + } + else { // If no children, it's a leaf node with colSpan of 1 totalColSpan = 1; } diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 93663a1ec6..6f41a1783c 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -84,7 +84,7 @@ class MatrixModel extends ComponentModel { borderType: 'solid', opacity: 1 } - } + }; } export default MatrixModel; diff --git a/test/matrix.html b/test/matrix.html index 162087e7ad..f3d369e7f7 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -38,6 +38,7 @@
+
@@ -101,6 +102,61 @@ }); + + From 0b3f14bc892127b379b3854ec677e37713234437 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Sat, 11 May 2024 14:44:53 +0800 Subject: [PATCH 13/49] fix(matrix): fix import --- src/coord/matrix/Matrix.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index e2f585fe65..fd22d09755 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -18,8 +18,8 @@ */ import { RectLike } from 'zrender/src/core/BoundingRect'; -import { SeriesModel } from '../../echarts.all'; -import { SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; +import type SeriesModel from '../../model/Series'; +import type { SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; From 9d1b48d0e44279c8a0c2e1f02db75548be2a0f2c Mon Sep 17 00:00:00 2001 From: Ovilia Date: Sat, 11 May 2024 16:06:31 +0800 Subject: [PATCH 14/49] test(matrix): more applications --- src/coord/matrix/MatrixDim.ts | 4 +- test/matrix_application.html | 458 ++++++++++++++++++++++++++++++++++ test/matrix_covariance.html | 131 ---------- 3 files changed, 460 insertions(+), 133 deletions(-) create mode 100644 test/matrix_application.html delete mode 100644 test/matrix_covariance.html diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index 94e7391f3d..b64b90d90a 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -167,7 +167,7 @@ export class MatrixDim { } private _countHeight(node: MatrixNodeOption): number { - if (typeof node === 'string') { + if (typeof node === 'string' || !node.children) { return 1; } let height = 0; @@ -178,7 +178,7 @@ export class MatrixDim { } private _countLeaves(node: MatrixNodeOption): number { - if (typeof node === 'string') { + if (typeof node === 'string' || !node.children) { return 1; } let cnt = 0; diff --git a/test/matrix_application.html b/test/matrix_application.html new file mode 100644 index 0000000000..633e0ba3ec --- /dev/null +++ b/test/matrix_application.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + diff --git a/test/matrix_covariance.html b/test/matrix_covariance.html deleted file mode 100644 index 56bc49d1e9..0000000000 --- a/test/matrix_covariance.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - From 6e8c626fdebcb29f0c147390d834e8601b9ca393 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Sat, 11 May 2024 16:37:59 +0800 Subject: [PATCH 15/49] test(matrix): fix test cases --- test/matrix_application.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/matrix_application.html b/test/matrix_application.html index 633e0ba3ec..5683e31276 100644 --- a/test/matrix_application.html +++ b/test/matrix_application.html @@ -201,7 +201,6 @@ type: 'continuous', min: -1, max: 1, - top: 'middle', dimension: 2, calculable: true, orient: 'horizontal', @@ -296,7 +295,6 @@ type: 'continuous', min: -1, max: 1, - top: 'middle', dimension: 2, calculable: true, orient: 'horizontal', @@ -367,7 +365,6 @@ for (let i = 1; i <= size; ++i) { for (let j = 1; j <= size; ++j) { - const isDiag = Math.abs(i - j) < 3; let base = i === j ? 100 : 20; const iGroup = Math.ceil(i / 5); const jGroup = Math.ceil(j / 5); @@ -384,7 +381,7 @@ data.push([ i + '', j + '', - temp[j + "_" + i] + temp[j + '_' + i] ]); } else { // Calculate a new value and save it for future use @@ -394,7 +391,7 @@ j + '', value ]); - temp[i + "_" + j] = value; + temp[i + '_' + j] = value; } } } @@ -415,7 +412,6 @@ type: 'continuous', min: 15, max: 120, - top: 'middle', dimension: 2, calculable: true, orient: 'horizontal', From b0529f915de4522af10c8f6675d82c7e779b4ffb Mon Sep 17 00:00:00 2001 From: Ovilia Date: Sat, 11 May 2024 18:26:33 +0800 Subject: [PATCH 16/49] feat(matrix): support convertToPixel and add a case for Periodic Table --- src/component/matrix/MatrixView.ts | 40 +++-- src/coord/matrix/Matrix.ts | 40 +++++ src/coord/matrix/MatrixDim.ts | 8 + src/util/model.ts | 1 + test/matrix_application.html | 261 +++++++++++++++++++++++++++++ 5 files changed, 332 insertions(+), 18 deletions(-) diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index a3f9217bec..757d1ead41 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -76,15 +76,17 @@ class MatrixView extends ComponentView { }, style: xItemStyle })); - this.group.add(new graphic.Text({ - style: createTextStyle(xLabelModel, { - text: cell.value, - x: left + width / 2, - y: top + height / 2, - verticalAlign: 'middle', - align: 'center' - }) - })); + if (xLabelModel.get('show')) { + this.group.add(new graphic.Text({ + style: createTextStyle(xLabelModel, { + text: cell.value, + x: left + width / 2, + y: top + height / 2, + verticalAlign: 'middle', + align: 'center' + }) + })); + } } const yTop = rect.y + cellHeight * xHeight; @@ -104,15 +106,17 @@ class MatrixView extends ComponentView { }, style: yItemStyle })); - this.group.add(new graphic.Text({ - style: createTextStyle(yLabelModel, { - text: cell.value, - x: left + width / 2, - y: top + height / 2, - verticalAlign: 'middle', - align: 'center' - }) - })); + if (yLabelModel.get('show')) { + this.group.add(new graphic.Text({ + style: createTextStyle(yLabelModel, { + text: cell.value, + x: left + width / 2, + y: top + height / 2, + verticalAlign: 'middle', + align: 'center' + }) + })); + } } // Inner cells diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index fd22d09755..202bf5c2ef 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -26,6 +26,7 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import MatrixModel from './MatrixModel'; import { LayoutRect, getLayoutRect } from '../../util/layout'; import { MatrixDim } from './MatrixDim'; +import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; class Matrix implements CoordinateSystem, CoordinateSystemMaster { @@ -128,10 +129,49 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { }; } + pointToData(point: number[]): number[] { + const xLeavesCnt = this._xDim.getLeavesCount(); + const yLeavesCnt = this._yDim.getLeavesCount(); + const xHeight = this._xDim.getHeight(); + const yHeight = this._yDim.getHeight(); + const cellWidth = this._rect.width / (xLeavesCnt + yHeight); + const cellHeight = this._rect.height / (yLeavesCnt + xHeight); + const xIdx = Math.floor((point[0] - this._rect.x) / cellWidth); + const yIdx = Math.floor((point[1] - this._rect.y) / cellHeight); + + const xCell = this._xDim.getCellByColId(xIdx - yHeight); + const yCell = this._yDim.getCellByColId(yIdx - xHeight); + + return [xCell.colId, yCell.rowId, xCell.colSpan, yCell.rowSpan]; + } + + convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: [string, string]) { + const coordSys = getCoordSys(finder); + return coordSys === this ? coordSys.dataToPoint(value) : null; + } + + convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { + const coordSys = getCoordSys(finder); + return coordSys === this ? coordSys.pointToData(pixel) : null; + } + containPoint(point: number[]): boolean { console.warn('Not implemented.'); return false; } } +function getCoordSys(finder: ParsedModelFinderKnown): Matrix { + const matrixModel = finder.matrixModel as MatrixModel; + const seriesModel = finder.seriesModel; + + const coordSys = matrixModel + ? matrixModel.coordinateSystem + : seriesModel + ? seriesModel.coordinateSystem + : null; + + return coordSys as Matrix; +} + export default Matrix; diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index b64b90d90a..12170dbfe9 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -109,6 +109,14 @@ export class MatrixDim { } } + getCellByColId(id: number) { + for (let i = 0; i < this._cells.length; i++) { + if (this._cells[i].colId === id) { + return this._cells[i]; + } + } + } + private _initCells(): void { this._cells = []; for (let i = 0, rowId = 0, colId = 0; i < this._option.data.length; i++) { diff --git a/src/util/model.ts b/src/util/model.ts index 7b6ca499dd..9b3e65ff36 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -777,6 +777,7 @@ export type ModelFinderObject = { xAxisIndex?: ModelFinderIndexQuery, xAxisId?: ModelFinderIdQuery, xAxisName?: ModelFinderNameQuery yAxisIndex?: ModelFinderIndexQuery, yAxisId?: ModelFinderIdQuery, yAxisName?: ModelFinderNameQuery gridIndex?: ModelFinderIndexQuery, gridId?: ModelFinderIdQuery, gridName?: ModelFinderNameQuery + matrixIndex?: ModelFinderIndexQuery, matrixId?: ModelFinderIdQuery, matrixName?: ModelFinderNameQuery dataIndex?: number, dataIndexInside?: number // ... (can be extended) }; diff --git a/test/matrix_application.html b/test/matrix_application.html index 5683e31276..7779cc4c4c 100644 --- a/test/matrix_application.html +++ b/test/matrix_application.html @@ -39,6 +39,7 @@
+
+ + From 8bedb782f985f0e2e79fc1b07d0deee9bfa99b10 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 14 May 2024 15:05:20 +0800 Subject: [PATCH 17/49] feat(matrix): support darkMode, headers can be hidden --- src/coord/matrix/Matrix.ts | 5 +++ src/coord/matrix/MatrixDim.ts | 4 +++ src/coord/matrix/MatrixModel.ts | 3 ++ src/theme/dark.ts | 18 ++++++++++ test/matrix.html | 61 ++++++++++++++++++++++++++++++++- test/matrix_application.html | 11 ++++-- 6 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 202bf5c2ef..37dd9a60e4 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -95,6 +95,11 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { dataToPoint(data: [string, string]): number[] { const xCell = this._xDim.getCell(data[0]); const yCell = this._yDim.getCell(data[1]); + if (!xCell || !yCell) { + // Point not found + return [NaN, NaN]; + } + const xLeavesCnt = this._xDim.getLeavesCount(); const yLeavesCnt = this._yDim.getLeavesCount(); const xHeight = this._xDim.getHeight(); diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index 12170dbfe9..ef6a833774 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -28,6 +28,7 @@ export type MatrixNodeOption = { export type MatrixNodeRawOption = string | MatrixNodeOption; export interface MatrixDimOption { + show?: boolean; data?: MatrixNodeOption[]; } export interface MatrixDimRawOption { @@ -78,6 +79,9 @@ export class MatrixDim { } getHeight() { + if (!this._option.show) { + return 0; + } if (this._height != null) { return this._height; } diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 6f41a1783c..ab64b6e294 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -28,11 +28,13 @@ export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { containLabel?: boolean; x?: { + show?: boolean; data?: MatrixNodeOption[]; label?: LabelOption; itemStyle?: ItemStyleOption; } y?: { + show?: boolean; data?: MatrixNodeOption[]; label?: LabelOption; itemStyle?: ItemStyleOption; @@ -43,6 +45,7 @@ export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { } const defaultDimOption = { + show: true, data: [] as MatrixNodeOption[], label: { show: true, diff --git a/src/theme/dark.ts b/src/theme/dark.ts index 2fa611a693..f5ade28a98 100644 --- a/src/theme/dark.ts +++ b/src/theme/dark.ts @@ -43,6 +43,14 @@ const axisCommon = function () { } }; }; +const matrixAxis = { + label: { + color: contrastColor + }, + itemStyle: { + borderColor: '#484753' + } +}; const colorPalette = [ '#4992ff', @@ -169,6 +177,16 @@ const theme = { color: contrastColor } }, + matrix: { + x: matrixAxis, + y: matrixAxis, + backgroundColor: { + borderColor: '#817f91' + }, + innerBackgroundStyle: { + borderColor: '#484753' + } + }, timeAxis: axisCommon(), logAxis: axisCommon(), valueAxis: axisCommon(), diff --git a/test/matrix.html b/test/matrix.html index f3d369e7f7..41b8f938af 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -39,6 +39,7 @@
+
@@ -102,6 +103,64 @@ }); + + + + - + + - @@ -48,33 +53,105 @@ - diff --git a/test/lib/reset.css b/test/lib/reset.css index 6a2ddce4ed..6457741849 100644 --- a/test/lib/reset.css +++ b/test/lib/reset.css @@ -51,9 +51,222 @@ body > .main { padding-left: 2px; padding-right: 2px; } -.test-buttons button { + +/* + * [ CAUTION ] + * The existing CSS class names below MUST NOT be modified; + * otherwise, some recorded visual test cases may fail. + */ + +.test-inputs { + background: #eee; +} +.test-inputs button { + margin: 10px 5px; +} +.test-inputs-fix-height { + position: relative; + overflow: scroll; + box-sizing: border-box; + background: #eee; + padding: 0; +} +.test-inputs-fix-height::before { + content: 'Scroll ⬇'; + position: sticky; + float: right; + top: 0; + right: 0; + padding: 0 2px 0 5px; + height: 14px; + line-height: 14px; + font-size: 11px; + color: #333; + z-index: 9999; + background: #ccc; + text-align: right; +} +.test-inputs-style-compact button { + margin-top: 2px; + margin-bottom: 2px; +} +.test-inputs-slider { + white-space: nowrap; + display: inline-block; + margin: 10px 5px; + font-size: 14px; + padding: 0; +} +.test-inputs-style-compact .test-inputs-slider { + margin-top: 2px; + margin-bottom: 2px; +} +.test-inputs-slider-input { + width: 109px; + height: 16px; +} +.test-inputs-slider-no-delta-buttons .test-inputs-slider-input { + width: 129px; +} +.test-inputs-style-compact .test-inputs-slider-input { + width: 75px; +} +.test-inputs-slider-sub { + margin-left: -10px; + margin-top: 0; + margin-bottom: 0; +} +.test-inputs-style-compact .test-inputs-slider-sub { + margin-top: 0; + margin-bottom: 0; +} +.test-inputs-slider span { + vertical-align: middle; + margin: 0 3px; +} +.test-inputs-slider-disabled span { + color: #aaa; +} +.test-inputs-slider input { + vertical-align: middle; + margin: 0 3px; +} +.test-inputs-slider-btn-incdec { + display: inline-block; + vertical-align: middle; + width: 0; + height: 0; + border-style: solid; + background: none; + border-color: transparent; + padding: 0; + user-select: none; + cursor: pointer; +} +.test-inputs-slider-btn-decrease { + border-width: 5px 7px 5px 0; + border-right-color: rgb(48,119,226); + margin: 0 1px 0 2px; +} +.test-inputs-slider-btn-decrease:hover { + border-right-color: #245dc1; +} +.test-inputs-slider-btn-increase { + border-width: 5px 0 5px 7px; + border-left-color: rgb(48,119,226); + margin: 0 2px 0 1px; +} +.test-inputs-slider-btn-increase:hover { + border-left-color: #245dc1; +} +.test-inputs-slider-disabled .test-inputs-slider-btn-decrease { + border-right-color: #bbb; + cursor: default; +} +.test-inputs-slider-disabled .test-inputs-slider-btn-increase { + border-left-color: #bbb; + cursor: default; +} +.test-inputs-slider-disabled .test-inputs-slider-btn-decrease:hover { + border-left-color: #bbb; +} +.test-inputs-slider-disabled .test-inputs-slider-btn-increase:hover { + border-left-color: #bbb; +} +.test-inputs-select { + white-space: nowrap; + display: inline-block; + font-size: 14px; margin: 10px 5px; + margin-right: 10px; +} +.test-inputs-style-compact .test-inputs-select { + margin-top: 2px; + margin-bottom: 2px; +} +.test-inputs-select span { + vertical-align: middle; + margin: 0 3px; } +.test-inputs-select-disabled span { + color: #aaa; +} +.test-inputs-select select { + vertical-align: middle; + margin: 0; + padding: 0; + font-size: 13.3333px; + height: 19px; +} +.test-inputs-groupset { + position: relative; + overflow: auto; + border: 1px solid #bbb; + padding: 0; + margin: 0 3px; /* margin-bottom will collapse. */ + background: #fff; + display: block; + width: auto; +} +.test-inputs-groupset.test-inputs-fix-height::before { + right: 0; + left: 0; + height: 12px; + line-height: 12px; + font-size: 9px; +} +.test-inputs-groupset-text { + position: sticky; + display: block; + top: 0; + left: 0; + padding: 0; + margin: 0; + padding-left: 10px; + color: #333; + z-index: 9998; + height: 12px; + line-height: 12px; + font-size: 9px; + background: #ccc; +} +.test-inputs-groupset-group { + position: relative; + margin: 5px 2px; + background: #fff; +} +.test-inputs-groupset-margin-bottom { + /* A workaround for margin collapse, without breaking parent CSS (may fail previous visual tests) */ + height: 3px; + position: relative; + display: block; + width: auto; +} +.test-inputs-hr { + position: relative; + background: #ccc; + padding: 0; + height: 1px; + border-width: 0; + margin-block: unset; + margin-inline: unset; + margin: 8px 2px; +} +.test-inputs-hr-text { + position: absolute; + color: #333; + background: #ddd; + right: 0; + top: -6px; + text-align: right; + vertical-align: middle; + white-space: nowrap; + font-size: 10px; + line-height: 10px; + padding: 2px 5px; + margin: 0; +} + .test-chart-block { position: relative; } @@ -73,6 +286,21 @@ body > .main { .test-chart-block-has-right .test-chart-block-left { margin-right: 320px; } +.test-chart-wrapper { + position: relative; + padding: 0; + margin: 0; + border-width: 0; +} +.test-bounding-rects { + position: absolute; + left: 0; + top: 0; + padding: 0; + margin: 0; + border-width: 0; + z-index: 999999; +} .test-info { padding-left: 10px; overflow: auto; diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js index c570529d39..7eb2052b24 100644 --- a/test/lib/testHelper.js +++ b/test/lib/testHelper.js @@ -47,8 +47,7 @@ var myRandom = new seedrandom('echarts-random'); // Fixed random generator Math.random = function () { - const val = myRandom(); - return val; + return myRandom(); }; }); } @@ -58,25 +57,217 @@ /** * @param {Object} opt - * @param {string|Array.} [opt.title] If array, each item is on a single line. + * @param {string|string[]} [opt.title] If array, each item is on a single line. * Can use '**abc**', means abc. - * @param {Option} opt.option - * @param {Object} [opt.info] info object to display. - * @param {string} [opt.infoKey='option'] - * @param {Object|Array} [opt.dataTable] - * @param {Array.} [opt.dataTables] Multiple dataTables. - * @param {number} [opt.dataTableLimit=DEFAULT_DATA_TABLE_LIMIT] - * @param {number} [opt.width] - * @param {number} [opt.height] - * @param {boolean} [opt.draggable] - * @param {boolean} [opt.lazyUpdate] - * @param {boolean} [opt.notMerge] - * @param {boolean} [opt.autoResize=true] - * @param {Array.|Object} [opt.button] {text: ..., onClick: ...}, or an array of them. - * @param {Array.|Object} [opt.buttons] {text: ..., onClick: ...}, or an array of them. - * @param {boolean} [opt.recordCanvas] 'test/lib/canteen.js' is required. - * @param {boolean} [opt.recordVideo] - * @param {string} [opt.renderer] 'canvas' or 'svg' + * @param {Option} opt.option The chart option. + * + * @param {number} [opt.width] Optional. Specify a different chart width. + * @param {number} [opt.height] Optional. Specify a different chart height. + * @param {boolean} [opt.notMerge] Optional. `chart.setOption(option, {norMerge});` + * @param {boolean} [opt.lazyUpdate] Optional. `chart.setOption(option, {lazyUpdate});` + * @param {boolean} [opt.autoResize=true] Optional. Enable chart auto response to window resize. + * @param {string} [opt.renderer] Optional. 'canvas' or 'svg'. DO NOT set it in formmal test cases; + * leave it controlled by __ECHARTS__DEFAULT__RENDERER__ for visual testing. + * + * @param {boolean} [opt.draggable] Optional. Add a draggable button to mutify the chart size. + * This feature require "test/lib/draggable.js" + * + * @param {string} [opt.inputsStyle='normal'] Optional, can be 'normal', 'compact'. + * Can be either `inputsStyle` or `buttonsStyle`. + * @param {number} [opt.inputsHeight] Optional. By default not fix height. If specified, a scroll + * bar will be displayed if overflow the height. In visual test, once a height changed + * by adding something, the subsequent position will be changed, leading to test failures. + * Fixing the height helps avoid this. + * Can be either `inputsHeight` or `buttonsHeight`. + * @param {boolean} [opt.saveInputsInitialState] Optional. + * Required by `chart.__testHelper.restoreInputsToInitialState` + * @param {InputDefine[]|InputDefine|()=>InputDefine[]} [opt.inputs] Optional. + * definitions of button/range/select/br/hr. + * They are the same: `opt.buttons` `opt.button`, `opt.inputs`, `opt.input`. + * It can be a function that return inputs definitions, like: + * inputs: chart => { return [{text: 'xxx', onclick: fn}, ...]; } + * Inputs can be these types: + * [ + * { + * // A button (default). + * text: 'xxx', + * // They are the same: `onclick`, `click` (capital insensitive) + * onclick: fn, + * disabled: false, // Optional. + * prevent: { // Optional. + * recordInputs: false, // Optional. + * inputsState: false, // Optional. + * }, + * }, + * { + * // A range slider (HTML ). + * type: 'range', // They are the same: 'range' 'slider' + * id: 'some_id', // Optional. Can be used in `switchGroup`. + * text: 'xxx', // Optional + * min: 0, // Optional + * max: 100, // Optional + * value: 30, // Optional. Must be a number. + * step: 1, // Optional + * disabled: false, // Optional. + * prevent: { // Optional. + * recordInputs: false, // Optional. + * inputsState: false, // Optional. + * }, + * // They are the same: `oninput` `input` + * // `onchange` `change` `onchanged` `changed` + * // `onselect` `select` (capital insensitive) + * onchange: function () { console.log(this.value); } + * }, + * { + * // A select (HTML ). + * type: 'select', // They are the same: 'select' 'selection' + * id: 'some_id', // Optional. Can be used in `getState` and `setState`. + * // Either `values` or `options` can be used. + * // Items in `values` or `options[i].value` can be any type, like `true`, `123`, etc. + * values: ['a', 'b', 'c'], + * options: [ + * {text: 'a', value: 123}, + * {value: {some: {some: 456}}}, // `text` can be omitted and auto generated by `value`. + * {text: 'c', input: ...}, // `input` can be used as shown below. + * ... + * ], + * // `options[i]` can nest other input type, currently only support `type: range`: + * options: [ + * {value: undefined}, + * {text: 'c', input: { + * type: 'range', + * // ... Other properties of `range` input except `onchange` and `text`. + * // When this option is not selected, the range input will be disabled. + * }} + * ], + * optionIndex: 0, // Optional. Or `valueIndex`. The initial value index. + * // By default, the first option. + * value: 'cval', // Optional. The initial value. By default, the first option. + * // Can be any type, like `true`, `123`, etc. + * // But can only be JS primitive type, as `===` is used internally. + * text: 'xxx', // Optional. + * disabled: false, // Optional. + * prevent: { // Optional. + * recordInputs: false, // Optional. + * inputsState: false, // Optional. + * }, + * // They are the same: `oninput` `input` + * // `onchange` `change` `onchanged` `changed` + * // `onselect` `select` (capital insensitive) + * onchange: function () { console.log(this.value); } + * }, + * { + * // Group inputs. Only one group can be displayed at a time with in a group set. + * type: 'groups', // They are the same: 'groups' 'group' 'groupset' + * // `inputsHeight` is mandatory in group set to avoid height change to affects visual testing + * // when switching groups. It will be applied to all groups. + * inputsHeight, + * // `inputsHeight` will be applied to all groups. + * inputsStyle, + * disabled: false, // Optional. Controlls all groups inside, + * // unless `group.disabled` or `input.disbles` specified. + * prevent: { // Optional. + * recordInputs: false, // Optional. + * inputsState: false, // Optional. + * }, + * groups: [{ + * id: 'group_A', + * text: 'xxx', // Optional. Or `title`. Displayed in the header line of the group content. + * disabled: false, // Optional. Controlls all inputs inside, unless `input.disabled` specified. + * inputs: [{...}, {...}, ...], + * }, { + * id: 'group_B', + * inputs: [{...}, {...}, ...], + * }, ...] + * // Group switching API: @see chart.__testHelper.switchGroup(groupId); + * }, + * { + * // A line break. + * // They are the same: `br` `lineBreak` `break` `wrap` `newLine` `endOfLine` `carriageReturn` + * // `lineFeed` `lineSeparator` `nextLine` (capital insensitive) + * type: 'br', + * }, + * { + * // A separate line. + * type: 'hr', + * text: 'xxx', // Optional. Display text on the split line. + * }, + * // ... + * ] + * ----------------------------- Inputs related API ----------------------------------- + * @function chart.__testHelper.switchGroup Switch group. + * chart.__testHelper.switchGroup( + * groupId: string, + * opt?: { + * recordInputs: boolean, // Optional. @see `chart.__testHelper.recordInputs`. + * } + * ); + * + * @function chart.__testHelper.disableInputs Disable the specified inputs. + * chart.__testHelper.disableInputs(opt: { + * disabled: boolean, // disables/enables + * inputId: string, // Optional. id or id array. disables/enables the id-specified inputs. + * groupId: string, // Optional. id or id array. disables/enables the inputs within the group. + * recordInputs: boolean, // Optional. @see `chart.__testHelper.recordInputs`. + * }) + * + * @function chart.__testHelper.recordInputs + * @see `prevent` in `inputs` to prevent record. + * chart.__testHelper.recordInputs(opt: { // start record inputs operations for replay. + * action: 'start' + * }) + * chart.__testHelper.recordInputs(opt: { // stop record inputs operations and output. + * action: 'stop', + * outputType?: 'clipboard' | 'console', // Optional. 'clipboard' by default. + * printObjectOpt?: {} // Optional. the opt of `testHelper.printObject`. + * }) + * Note: if some API `chart.__testHelper.xxx` has parameter `recordInputs`, it indicates that wether + * record this call. It is `false` by default, and: + * - When this API is called in a callback function of an input where no `prevent.recordInputs` is + * declared, this option should be kept `false`. (This is the most cases.) + * - Otherwise it should be `true`. + * @function chart.__testHelper.replayInputs + * chart.__testHelper.replayInputs(inputsRecord) + * + * (TL;DR) NOTE: Currently, echarts can not be restored to the initial state by + * `setOption({..., xxx: undefined})` or `setOption({..., xxx: 'auto'})` in most options. + * That is, the initial state can only be obtained by: + * - either "not specified in echarts option" from the beginning; + * - or "sepecify the exact default value to match the internal default value in echarts option". + * + * @function chart.__testHelper.getInputsState Get the current state of `inputs`. + * chart.__testHelper.getInputsState() + * e.g., result: {some_id_1: 'value1', some_id_2: 'value2'} + * @see `prevent` in `inputs` to prevent. + * @function chart.__testHelper.setInputsState Set the current state of `inputs`. + * chart.__testHelper.setInputsState(state) + * @function chart.__testHelper.restoreInputsToInitialState + * chart.__testHelper.restoreInputsToInitialState() + * @see opt.saveInputsInitialState which must be specified as true for this API. + * ------------------------------------------------------------------------------------ + * + * @param {BoundingRectOpt} [opt.boundingRect] Optional. + * @typedef {boolean | {color?: string, slient: boolean}} BoundingRectOpt + * Enable display bounding rect for zrender elements. + * - `true`: Simply display the bounding rects. + * - `opt.boundingRect.color`: a string to indicate the color, like 'red', 'rgba(0,0,0,0.2)', '#fff'. + * - `opt.boundingRect.silent`: by default `false`; + * if `false`, click on the bounding rect, window.$0 will be assigned the original zrender element. + * - Can be switched dynamically by: + * // Update BoundingRectOpt, typically used to show/hide bounding rects. + * @function chart.__testHelper.boundingRect + * chart.__testHelper.boundingRect(opt: BoundingRectOpt); + * chart.__testHelper.boundingRect(); // Use the last BoundingRectOpt. + * + * @param {boolean} [opt.recordCanvas] Optional. 'test/lib/canteen.js' is required. + * @param {boolean} [opt.recordVideo] Optional. + * + * @param {Object} [opt.info] Optional. info object to display. + * @api info can be updated by `chart.__testHelper.updateInfo(someInfoObj, 'some_info_key');` + * @param {string} [opt.infoKey='option'] Optional. + * @param {Object|Array} [opt.dataTable] Optional. + * @param {Array.} [opt.dataTables] Optional. Multiple dataTables. + * @param {number} [opt.dataTableLimit=DEFAULT_DATA_TABLE_LIMIT] Optional. */ testHelper.create = function (echarts, domOrId, opt) { var dom = getDom(domOrId); @@ -85,24 +276,30 @@ return; } - var title = document.createElement('div'); + var errMsgPrefix = '[testHelper dom: ' + domOrId + ']'; + + var titleContainer = document.createElement('div'); var left = document.createElement('div'); + var chartContainerWrapper = document.createElement('div'); var chartContainer = document.createElement('div'); - var buttonsContainer = document.createElement('div'); + var inputsContainer = document.createElement('div'); var dataTableContainer = document.createElement('div'); var infoContainer = document.createElement('div'); var recordCanvasContainer = document.createElement('div'); var recordVideoContainer = document.createElement('div'); + var boundingRectsContainer = document.createElement('div'); - title.setAttribute('title', dom.getAttribute('id')); + titleContainer.setAttribute('title', dom.getAttribute('id')); - title.className = 'test-title'; + titleContainer.className = 'test-title'; dom.className = 'test-chart-block'; left.className = 'test-chart-block-left'; + chartContainerWrapper.className = 'test-chart-wrapper'; chartContainer.className = 'test-chart'; - buttonsContainer.className = 'test-buttons'; dataTableContainer.className = 'test-data-table'; infoContainer.className = 'test-info'; + boundingRectsContainer.className = 'test-bounding-rects'; + boundingRectsContainer.style.display = 'none'; recordCanvasContainer.className = 'record-canvas'; recordVideoContainer.className = 'record-video'; @@ -113,77 +310,1226 @@ left.appendChild(recordCanvasContainer); left.appendChild(recordVideoContainer); - left.appendChild(buttonsContainer); + left.appendChild(inputsContainer); left.appendChild(dataTableContainer); - left.appendChild(chartContainer); + left.appendChild(chartContainerWrapper); + chartContainerWrapper.appendChild(chartContainer); + chartContainerWrapper.appendChild(boundingRectsContainer); dom.appendChild(infoContainer); dom.appendChild(left); - dom.parentNode.insertBefore(title, dom); + dom.parentNode.insertBefore(titleContainer, dom); - var chart; + initTestTitle(opt, titleContainer); + + var chart = testHelper.createChart(echarts, chartContainer, opt.option, opt, opt.setOptionOpts, errMsgPrefix); + chart.__testHelper = {}; + + initDataTables(opt, dataTableContainer); + if (chart) { + initInputs(chart, opt, inputsContainer, errMsgPrefix); + initUpdateInfo(opt, chart, infoContainer); + initRecordCanvas(opt, chart, recordCanvasContainer); + if (opt.recordVideo) { + testHelper.createRecordVideo(chart, recordVideoContainer); + } + initShowBoundingRects(chart, echarts, opt, boundingRectsContainer); + } + + return chart; + }; + + function initTestTitle(opt, titleContainer) { var optTitle = opt.title; if (optTitle) { if (optTitle instanceof Array) { optTitle = optTitle.join('\n'); } - title.innerHTML = '
' - + testHelper.encodeHTML(optTitle) + titleContainer.innerHTML = '
' + + encodeHTML(optTitle) .replace(/\*\*([^*]+?)\*\*/g, '$1') .replace(/\n/g, '
') + '
'; } + } - chart = testHelper.createChart(echarts, chartContainer, opt.option, opt, opt.setOptionOpts); + function initUpdateInfo(opt, chart, infoContainer) { + assert(chart.__testHelper); - var dataTables = opt.dataTables; - if (!dataTables && opt.dataTable) { - dataTables = [opt.dataTable]; + if (opt.info) { + updateInfo(opt.info, opt.infoKey); } - if (dataTables) { - var tableHTML = []; - for (var i = 0; i < dataTables.length; i++) { - tableHTML.push(createDataTableHTML(dataTables[i], opt)); + + function updateInfo(info, infoKey) { + infoContainer.innerHTML = createObjectHTML(info, infoKey || 'option'); + } + + chart.__testHelper.updateInfo = updateInfo; + } + + function initInputs(chart, opt, inputsContainer, errMsgPrefix) { + assert(chart.__testHelper); + + var NAMES_ON_INPUT_CHANGE = makeFlexibleNames([ + 'input', 'on-input', 'change', 'on-change', 'changed', 'on-changed', 'select', 'on-select' + ]); + var NAMES_ON_CLICK = makeFlexibleNames([ + 'click', 'on-click' + ]); + var NAMES_TYPE_BUTTON = makeFlexibleNames(['button', 'btn']); + var NAMES_TYPE_RANGE = makeFlexibleNames(['range', 'slider']); + var NAMES_TYPE_SELECT = makeFlexibleNames(['select', 'selection']); + var NAMES_TYPE_BR = makeFlexibleNames([ + 'br', 'line-break', 'break', 'wrap', 'new-line', 'end-of-line', + 'carriage-return', 'line-feed', 'line-separator', 'next-line' + ]); + var NAMES_TYPE_HR = makeFlexibleNames([ + 'hr', 'horizontal-line', 'divider', 'separate-line' + ]); + var NAMES_TYPE_GROUP_SET = makeFlexibleNames(['group', 'groups', 'group-set']); + /** + * key: inputId, + * value: { + * id: inputId, + * disable?, + * switchGroup?, + * setState?, + * getState?, + * } + */ + var _inputsDict = {}; + var NAMES_RECORD_INPUTS_ACTION_START = makeFlexibleNames(['start', 'begin']); + var NAMES_RECORD_INPUTS_ACTION_STOP = makeFlexibleNames(['stop', 'end', 'finish']); + var _inputsRecord = null; + /** + * key: inputId + * value: @see makeInputRecorder + */ + var _inputRecorderWrapperMap = {}; + var _INPUTS_RECORD_VERSION = '1.0.0'; + var NANES_PREVENT_INPUTS_STATE = makeFlexibleNames([ + 'inputs-state', 'input-state', 'inputs-states', 'input-states' + ]); + var NANES_PREVENT_RECORD_INPUTS = makeFlexibleNames([ + 'record-inputs', 'record-input', + 'input-record', 'inputs-record', + ]); + var _initStateBackup = null; + + initInputsContainer(inputsContainer, opt); + var inputsDefineList = retrieveInputDefineList(opt); + dealInitEachInput(inputsDefineList, inputsContainer); + + // --- Input operation related API --- + chart.__testHelper.switchGroup + = makeSwitchGroup(); + chart.__testHelper.disableInputs + = chart.__testHelper.disableInput + = makeDisableInputs(); + + // --- Input meta related API --- + chart.__testHelper.recordInputs + = recordInputs; + chart.__testHelper.replayInputs + = chart.__testHelper.replayInput + = replayInputs; + chart.__testHelper.getInputsState + = chart.__testHelper.getInputState + = getInputsState; + chart.__testHelper.setInputsState + = chart.__testHelper.setInputState + = setInputsState; + chart.__testHelper.restoreInputsToInitialState + = restoreInputsToInitialState; + + if (opt.saveInputsInitialState) { + _initStateBackup = chart.__testHelper.getInputsState(); + } + + return; + + function makeDisableInputs() { + var inputRecorderWrapper = makeInputRecorder(); + inputRecorderWrapper.setupInputId('__\0testHelper_disableInputs'); + var disableInputsWithRecordInputs = inputRecorderWrapper.inputRecorder.wrapUserInputListener({ + listener: disableInputs, + op: 'disableInputs' + }); + + /** + * @param {string|Array.?} opt.groupId + * @param {string|Array.?} opt.inputId + * @param {boolean} opt.recordInputs + */ + return function (opt) { + opt.recordInputs + ? disableInputsWithRecordInputs(opt) + : disableInputs(opt); + } + + function disableInputs(opt) { + assert(opt, '[disableInputs] requires parameters.'); + var groupId = opt.groupId; + var inputId = opt.inputId; + assert( + groupId != null || inputId != null, + '[disableInputs] requires `groupId` or/and `inputId`.' + ); + var inputIdList = []; + if (inputId != null) { + if (getType(inputId) !== 'array') { + inputId = [inputId]; + } + for (var idx = 0; idx < inputId.length; idx++) { + var id = inputId[idx]; + findInputCreatedAndCheck(id, {throw: true}); + inputIdList.push(id); + } + } + if (groupId != null) { + if (getType(groupId) !== 'array') { + groupId = [groupId]; + } + for (var idx = 0; idx < groupId.length; idx++) { + inputIdList = inputIdList.concat(retrieveAndVerifyGroup(groupId[idx]).idList); + } + } + var disabled = opt.disabled; + for (var idx = 0; idx < inputIdList.length; idx++) { + var id = inputIdList[idx]; + if (_inputsDict[id].disable) { + _inputsDict[id].disable({disabled: disabled}); + } + } + } + } + + /** + * @param {string} opt.action 'start' or 'stop'. + * @param {string} opt.outputType Optional. 'clipboard' or 'console'. + * @param {Object} opt.printObjectOpt Optional. The opt of `testHelper.printObject`. + */ + function recordInputs(opt) { + var action = opt.action; + assert( + NAMES_RECORD_INPUTS_ACTION_START.indexOf(action) >= 0 + || NAMES_RECORD_INPUTS_ACTION_STOP.indexOf(action) >= 0, + 'Invalide recordInputs action: ' + action + '. Should be ' + + NAMES_RECORD_INPUTS_ACTION_START + ' ' + NAMES_RECORD_INPUTS_ACTION_STOP + ); + if (NAMES_RECORD_INPUTS_ACTION_START.indexOf(action) >= 0) { + _inputsRecord = { + version: _INPUTS_RECORD_VERSION, + startTime: +(new Date()), + operations: [], + }; + } + else if (NAMES_RECORD_INPUTS_ACTION_STOP.indexOf(action) >= 0) { + if (_inputsRecord == null) { + console.error( + 'Inputs record is not started. Please call' + + ' `chart.__testHelper.recordInputs({action: "start"})` first.' + ); + return; + } + _inputsRecord.endTime = +(new Date()); + var inputsRecord = _inputsRecord; + _inputsRecord = null; + outputInputsRecord(inputsRecord); + return inputsRecord; + } + + function outputInputsRecord(record) { + if (opt.outputType === 'console') { + console.log(testHelper.printObject(record, opt.printObjectOpt)); + } + else { + testHelper.clipboard(record, opt.printObjectOpt); + } } - dataTableContainer.innerHTML = tableHTML.join(''); } - var buttons = opt.buttons || opt.button; - if (!(buttons instanceof Array)) { - buttons = buttons ? [buttons] : []; + function replayInputs(inputsRecord) { + assert( + inputsRecord.version === _INPUTS_RECORD_VERSION, + 'Not supported inputs record version. expect' + _INPUTS_RECORD_VERSION + ' Need to re-record.' + ); + for (var idx = 0; idx < inputsRecord.operations.length; idx++) { + var opItem = inputsRecord.operations[idx]; + findInputCreatedAndCheck(opItem.id, {throw: true}); + assert( + !shouldPrevent(opItem.id, NANES_PREVENT_RECORD_INPUTS), + 'Input (id:' + opItem.id + ') has prevented recording. This may caused by test case change.' + ); + var inputRecorderWrapper = _inputRecorderWrapperMap[opItem.id]; + assert(inputRecorderWrapper); + assert(getType(opItem.op) === 'string', 'Invalid op: ' + opItem.op); + var listenerDefine = inputRecorderWrapper.listenerDefineMap[opItem.op]; + assert( + listenerDefine, + 'Can not find listener by op: ' + opItem.op + ' This may caused by test case change.' + ); + var prepared = {this: [], arguments: {}}; + if (listenerDefine.prepareReplay) { + prepared = listenerDefine.prepareReplay(opItem.args); + assert( + isObject(prepared) + && prepared.hasOwnProperty('this') + && getType(prepared.arguments) === 'array', + '`prepareReplay` must return an object: {this: any, arguments: []}.' + ); + } + listenerDefine.listener.apply(prepared.this, prepared.arguments); + } + } + + function makeInputRecorder() { + var _inputId = null; + var inputRecorderWrapper = { + setupInputId: function (inputId) { + _inputId = inputId; + _inputRecorderWrapperMap[inputId] = inputRecorderWrapper; + }, + inputRecorder: { + wrapUserInputListener: wrapUserInputListener + }, + /** + * key: op, + */ + listenerDefineMap: {}, + }; + + return inputRecorderWrapper; + + function wrapUserInputListener(listenerDefine) { + assert( + getType(listenerDefine.listener) === 'function', + 'Must provide a function `listener`.' + ); + assert( + getType(listenerDefine.op) === 'string', + 'Must provide an `op` string to identify this listener.' + ); + + assert( + !inputRecorderWrapper.listenerDefineMap[listenerDefine.op], + '`op` ' + listenerDefine.op + ' overlapped.' + ); + inputRecorderWrapper.listenerDefineMap[listenerDefine.op] = listenerDefine; + + return function wrappedListener() { + assert(_inputId != null); + if (_inputsRecord && !shouldPrevent(_inputId, NANES_PREVENT_RECORD_INPUTS)) { + var recordWrapper = {id: _inputId, op: listenerDefine.op}; + if (listenerDefine.createRecordArgs) { + recordWrapper.args = listenerDefine.createRecordArgs.apply(this, arguments); + } + _inputsRecord.operations.push(recordWrapper); + } + return listenerDefine.listener.apply(this, arguments); + }; + } } - if (buttons.length) { - for (var i = 0; i < buttons.length; i++) { - var btnDefine = buttons[i]; - if (btnDefine) { - var btn = document.createElement('button'); - btn.innerHTML = testHelper.encodeHTML(btnDefine.name || btnDefine.text || 'button'); - btn.addEventListener('click', btnDefine.onClick || btnDefine.onclick); - buttonsContainer.appendChild(btn); + + function setInputsState(state) { + var changedCreatedList = []; + for (var id in state) { + if (state.hasOwnProperty(id)) { + var inputCreated = findInputCreatedAndCheck(id, {log: true}); + if (!inputCreated) { + continue; + } + if (shouldPrevent(id, NANES_PREVENT_INPUTS_STATE) || !inputCreated.setState) { + continue; + } + inputCreated.setState(state[id]); + changedCreatedList.push(inputCreated); } } } - if (opt.info) { - updateInfo(opt.info, opt.infoKey); + function getInputsState() { + var result = {}; + for (var id in _inputsDict) { + if (_inputsDict.hasOwnProperty(id)) { + var inputCreated = _inputsDict[id]; + if (shouldPrevent(id, NANES_PREVENT_INPUTS_STATE) || !inputCreated.getState) { + continue; + } + if (inputCreated.idCanNotPersist) { + throw new Error( + errMsgPrefix + '[getInputsState]. Please specify an id explicitly or unique text' + + ' for input:' + printObject(inputCreated.__inputDefine) + ); + } + result[id] = inputCreated.getState(); + } + } + return result; } - function updateInfo(info, infoKey) { - infoContainer.innerHTML = createObjectHTML(info, infoKey || 'option'); + function restoreInputsToInitialState() { + assert( + _initStateBackup != null, + 'opt.saveInputsInitialState must be true to use `restoreInputsToInitialState`.' + ); + setInputsState(_initStateBackup); } - initRecordCanvas(opt, chart, recordCanvasContainer); + function initInputsContainer(container, define, features) { + assert(container.tagName.toLowerCase() === 'div'); + container.innerHTML = ''; + + var ignoreFixHeight = features && features.ignoreFixHeight; + var ignoreInputsStyle = features && features.ignoreInputsStyle; + + var inputsHeight = retrieveValue(define.inputsHeight, define.buttonsHeight, null); + if (inputsHeight != null) { + inputsHeight = parseFloat(inputsHeight); + } - if (opt.recordVideo) { - testHelper.createRecordVideo(chart, recordVideoContainer); + var classNameArr = []; + if (features && features.className) { + classNameArr.push(features.className); + } + if (!ignoreInputsStyle) { + classNameArr.push( + 'test-inputs', + 'test-buttons', // deprecated but backward compat. + 'test-inputs-style-' + (define.inputsStyle || define.buttonsStyle || 'normal') + ); + } + if (!ignoreFixHeight && inputsHeight != null) { + classNameArr.push('test-inputs-fix-height'); + container.style.cssText += [ + 'height:' + inputsHeight + 'px' + ].join(';') + ';'; + } + + container.className = classNameArr.join(' '); } - chart.__testHelper = { - updateInfo: updateInfo - }; + function dealInitEachInput(inputsDefineList, inputsContainer) { + var idList = []; + for (var i = 0; i < inputsDefineList.length; i++) { + var inputDefine = inputsDefineList[i]; + var inputRecorderWrapper = makeInputRecorder(); + var inputCreated = createInputByDefine( + inputDefine, + inputRecorderWrapper.inputRecorder + ); + if (!inputCreated) { + continue; + } + for (var j = 0; j < inputCreated.elList.length; j++) { + inputsContainer.appendChild(inputCreated.elList[j]); + } + var id = storeToInputDict(inputDefine, inputCreated, inputRecorderWrapper.setupInputId); + idList.push(id); + } + return idList; + } - return chart; - }; + function storeToInputDict(inputDefine, inputCreated, inputRecorderSetupInputId) { + var id = retrieveId(inputDefine, 'id'); + if (id != null) { + id = '' + id; + if (_inputsDict[id]) { + throw new Error(errMsgPrefix + ' Duplicate input id: ' + id); + } + } + if (id == null) { + var text = retrieveValue(inputDefine.text, '') + ''; + if (text) { + var textBasedId = '__inputs|' + text + '|'; + if (!_inputsDict[textBasedId]) { + id = textBasedId; + } + } + } + if (id == null) { + id = generateNonPersistentId('__inputs_non_persist'); + assert(!_inputsDict[id]); + inputCreated.idCanNotPersist = true; + } + inputCreated.id = id; + inputCreated.__inputDefine = inputDefine; + _inputsDict[id] = inputCreated; + if (inputRecorderSetupInputId) { + inputRecorderSetupInputId(id); + } + return id; + } + + function retrieveAndVerifyGroup(groupId) { + var groupCreated = _inputsDict[groupId]; + assert(groupCreated, 'Can not find group by id: ' + groupId); + assert(groupCreated.groupParent, 'This is not a group. id: ' + groupId); + return groupCreated; + } + + function makeSwitchGroup() { + var inputRecorderWrapper = makeInputRecorder(); + inputRecorderWrapper.setupInputId('__\0testHelper_switchGroup'); + var switchGroupWithRecordInputs = inputRecorderWrapper.inputRecorder.wrapUserInputListener({ + listener: dealSwitchGroup, + op: 'switchGroup' + }); + + return function (groupId, opt) { + (opt && opt.recordInputs) + ? switchGroupWithRecordInputs(groupId, opt) + : dealSwitchGroup(groupId); + }; + + function dealSwitchGroup(groupId) { + var groupCreatedToShow = retrieveAndVerifyGroup(groupId); + var groupSetCreated = groupCreatedToShow.groupParent; + groupSetCreated.switchGroup(groupId); + } + } + + function showHideGroupInGroupSet(groupCreated, showOrHide) { + groupCreated.inputsContainerEl.style.display = showOrHide + ? 'block' : 'none'; + var groupDefine = groupCreated.groupDefine; + groupCreated.groupSetTextEl.innerHTML = showOrHide + ? encodeHTML(retrieveValue(groupDefine.text, groupDefine.title, '')) + : ''; + } + + function shouldPrevent(inputId, names) { + var prevent = _inputsDict[inputId].__inputDefine.prevent || {}; + for (var idx = 0; idx < names.length; idx++) { + if (prevent[names[idx]]) { + return true; + } + } + return false; + } + + function findInputCreatedAndCheck(inputId, errorHandling) { + var inputCreated = _inputsDict[inputId]; + if (!inputCreated) { + var errMsg = errMsgPrefix + ' No input found by id: ' + inputId + '. May caused by test case change.'; + if (errorHandling.log) { + console.error(errMsg); + } + else if (errorHandling.throw) { + throw new Error(errMsg); + } + else { + throw new Error('internal failure.') + } + } + return inputCreated; + } + + function retrieveInputDefineList(define) { + var defineList = retrieveValue(define.buttons, define.button, define.input, define.inputs); + if (typeof defineList === 'function') { + defineList = defineList(chart); + } + if (!(defineList instanceof Array)) { + defineList = defineList ? [defineList] : []; + } + return defineList; + } + + function getInputsTextHTML(inputDefine, defaultText) { + return encodeHTML(retrieveValue(inputDefine.name, inputDefine.text, defaultText)); + } + + function getBtnEventListener(inputDefine, names) { + for (var idx = 0; idx < names.length; idx++) { + if (inputDefine[names[idx]]) { + return inputDefine[names[idx]]; + } + } + } + + function retrieveId(inputDefine, idPropName) { + if (inputDefine && inputDefine[idPropName] != null) { + var type = getType(inputDefine[idPropName]); + if (type !== 'string' && type != 'number') { + throw new Error(errMsgPrefix + ' id must be string or number.'); + } + return inputDefine[idPropName] + ''; + } + } + + function createInputByDefine(inputDefine, inputRecorder) { + if (!inputDefine) { + return; + } + var inputType = inputDefine.hasOwnProperty('type') ? inputDefine.type : 'button'; + + if (arrayIndexOf(NAMES_TYPE_RANGE, inputType) >= 0) { + return createRangeInput(inputDefine, null, inputRecorder); + } + else if (arrayIndexOf(NAMES_TYPE_SELECT, inputType) >= 0) { + return createSelectInput(inputDefine, inputRecorder); + } + else if (arrayIndexOf(NAMES_TYPE_BR, inputType) >= 0) { + return createBr(inputDefine, inputRecorder); + } + else if (arrayIndexOf(NAMES_TYPE_HR, inputType) >= 0) { + return createHr(inputDefine, inputRecorder); + } + else if (arrayIndexOf(NAMES_TYPE_BUTTON, inputType) >= 0) { + return createButtonInput(inputDefine, inputRecorder); + } + else if (arrayIndexOf(NAMES_TYPE_GROUP_SET, inputType) >= 0) { + return createGroupSetInput(inputDefine, inputRecorder); + } + else { + throw new Error(errMsgPrefix + ' Unsupported button type: ' + inputType); + } + } + + function createRangeInput(inputDefine, internallyForceDef, inputRecorder) { + var _currVal = +retrieveValue(inputDefine.value, 0); + var _disabled = false; + var _step = +retrieveValue(inputDefine.step, 1); + var _minVal = +retrieveValue(inputDefine.min, 0); + var _maxVal = +retrieveValue(inputDefine.max, 100); + var _precision = Math.max( + getPrecision(_minVal), + getPrecision(_maxVal), + getPrecision(_currVal), + getPrecision(_step) + ); + var _noDeltaButtons = !!inputDefine.noDeltaButtons; // Only for backward compat. + var _rangeInputWrapperEl; + var _rangeInputListener; + var _rangeInputEl; + var _rangeInputValueEl; + + dealInitRangeInput(); + + return { + elList: [_rangeInputWrapperEl], + disable: resetRangeInputDisabled, + getState: getRangeInputState, + setState: setRangeInputState, + }; + + function dealInitRangeInput() { + _rangeInputWrapperEl = document.createElement('span'); + resetRangeInputWrapperCSS(_rangeInputWrapperEl, false); + + _rangeInputListener = internallyForceDef + ? getBtnEventListener(internallyForceDef, NAMES_ON_INPUT_CHANGE) + : getBtnEventListener(inputDefine, NAMES_ON_INPUT_CHANGE); + if (!_rangeInputListener) { + throw new Error( + errMsgPrefix + ' No listener (either ' + + NAMES_ON_INPUT_CHANGE.join(', ') + ') specified for slider.' + ); + } + + var sliderTextEl = document.createElement('span'); + sliderTextEl.className = 'test-inputs-slider-text'; + sliderTextEl.innerHTML = internallyForceDef + ? getInputsTextHTML(internallyForceDef, '') + : getInputsTextHTML(inputDefine, ''); + _rangeInputWrapperEl.appendChild(sliderTextEl); + + function createRangeInputDeltaBtn(btnName, delta) { + if (_noDeltaButtons) { return; } + var sliderLRBtnEl = document.createElement('div'); + sliderLRBtnEl.className = 'test-inputs-slider-btn-incdec test-inputs-slider-btn-' + btnName; + _rangeInputWrapperEl.appendChild(sliderLRBtnEl); + sliderLRBtnEl.addEventListener('click', inputRecorder.wrapUserInputListener({ + listener: function () { + if (_disabled) { return; } + // 0.1 + 0.2 = 0.30000000000000004 + _currVal = round(_currVal + delta, _precision); + updateRangeInputViewValue(_currVal); + dispatchRangeInputChangedEvent(); + }, + op: btnName + })); + } + createRangeInputDeltaBtn('decrease', -_step); + createRangeInputDeltaBtn('increase', _step); + + _rangeInputEl = document.createElement('input'); + _rangeInputEl.className = 'test-inputs-slider-input'; + _rangeInputEl.setAttribute('type', 'range'); + _rangeInputEl.addEventListener('input', inputRecorder.wrapUserInputListener({ + listener: function () { + if (_disabled) { return; } + _currVal = +this.value; + updateRangeInputViewValue(_currVal); + dispatchRangeInputChangedEvent(); + }, + op: 'slide', + createRecordArgs: function () { + return [+this.value]; + }, + prepareReplay: function (recordArgs) { + _rangeInputEl.value = recordArgs[0]; + return { + this: _rangeInputEl, + arguments: [] + }; + } + })); + _rangeInputEl.setAttribute('min', _minVal); + _rangeInputEl.setAttribute('max', _maxVal); + _rangeInputEl.setAttribute('value', _currVal); + _rangeInputEl.setAttribute('step', _step); + _rangeInputWrapperEl.appendChild(_rangeInputEl); + + _rangeInputValueEl = document.createElement('span'); + _rangeInputValueEl.className = 'test-inputs-slider-value'; + _rangeInputWrapperEl.appendChild(_rangeInputValueEl); + + updateRangeInputViewValue(_currVal); + resetRangeInputDisabled(inputDefine); + } + + function updateRangeInputViewValue(newVal) { + _rangeInputEl.value = +newVal; + _rangeInputValueEl.innerHTML = encodeHTML(newVal + ''); + } + function resetRangeInputWrapperCSS(wrapperEl, disabled) { + wrapperEl.className = 'test-inputs-slider' + + (internallyForceDef ? ' test-inputs-slider-sub' : '') + + (disabled ? ' test-inputs-slider-disabled' : ''); + + (_noDeltaButtons ? ' test-inputs-slider-no-delta-buttons' : ''); + } + function setRangeInputState(state) { + if (!isObject(state)) { + console.error( + errMsgPrefix + ' Range input state must be object rather than ' + printObject(state) + + ' May caused by test case change.' + ); + return; + } + var newVal = +state.value; + if (!isFinite(newVal)) { + console.error( + errMsgPrefix + ' Range input state.value must be number rather than ' + printObject(state) + + ' May caused by test case change.' + ); + return; + } + _currVal = newVal; + resetRangeInputDisabled({disabled: state.disabled}); + updateRangeInputViewValue(_currVal); + } + function getRangeInputState() { + return { + value: _currVal, + disabled: _disabled, + }; + } + function resetRangeInputDisabled(opt) { + _disabled = !!opt.disabled; + _rangeInputEl.disabled = _disabled; + resetRangeInputWrapperCSS(_rangeInputWrapperEl, _disabled); + } + function dispatchRangeInputChangedEvent() { + if (_disabled) { return; } + var target = {value: _currVal}; + _rangeInputListener.call(target, {target: target}); + } + } // End of createRangeInput + + function createSelectInput(inputDefine, inputRecorder) { + var selectCtx = { + _optionList: [], + _selectWrapperEl: null, + _selectEl: null, + _optionIdxToSubInput: [], + _el: null, + _disabled: false, + }; + + var _SAMPLE_SELECT_DEFINITION = [ + '{', + ' type: "select",', + ' text?: "my select:",', + ' options: [', + ' {text?: string, value: any},', + ' {text?: string, input: {type: "range", ...}},', + ' ...,', + ' ],', + ' onchange() { ... },', + '}' + ].join('\n'); + + createSelectInputElements(); + + var _selectListener = getBtnEventListener(inputDefine, NAMES_ON_INPUT_CHANGE); + assert( + _selectListener, + errMsgPrefix + ' No listener specified for select. Should have either one of ' + + NAMES_ON_INPUT_CHANGE.join(', ') + '.' + ); + + initSelectInputOptions(inputDefine); + + selectCtx._selectEl.addEventListener('change', inputRecorder.wrapUserInputListener({ + listener: function dispatchSelectInputChangedEvent() { + if (selectCtx._disabled) { return; } + resetSelectInputSubInputsDisabled(); + triggerUserSelectChangedEvent(); + }, + op: 'select', + createRecordArgs: function () { + return [getSelectInputOptionIndex()]; + }, + prepareReplay: function (recordArgs) { + var optionIndex = recordArgs[0]; + validateOptionIndex(optionIndex); + selectCtx._selectEl.value = optionIndex; + return { + this: selectCtx._selectEl, + arguments: [] + }; + } + })); + + setSelectInputInitValue(inputDefine); + resetSelectInputDisabled(inputDefine); + + return { + elList: [selectCtx._el], + disable: resetSelectInputDisabled, + getState: getSelectInputState, + setState: setSelectInputState, + }; + + function createSelectInputElements() { + var selectWrapperEl = document.createElement('span'); + selectCtx._selectWrapperEl = selectWrapperEl; + resetSelectInputWrapperCSS(selectWrapperEl, false); + + var textEl = document.createElement('span'); + textEl.className = 'test-inputs-select-text'; + textEl.innerHTML = getInputsTextHTML(inputDefine, ''); + selectWrapperEl.appendChild(textEl); + + var selectEl = document.createElement('select'); + selectEl.className = 'test-inputs-select-select'; + selectWrapperEl.appendChild(selectEl); + + selectCtx._el = selectWrapperEl; + selectCtx._selectEl = selectEl; + } + + function resetSelectInputWrapperCSS(selectWrapperEl, disabled) { + selectWrapperEl.className = 'test-inputs-select' + + (disabled ? ' test-inputs-select-disabled' : ''); + } + + function initSelectInputOptions(inputDefine) { + // optionDef can be {text, value} or just value + // (value can be null/undefined/array/object/... everything). + // Convinient but might cause ambiguity when a value happens to be {text, value}, but rarely happen. + if (inputDefine.options) { + for (var optionIdx = 0; optionIdx < inputDefine.options.length; optionIdx++) { + var optionDef = inputDefine.options[optionIdx]; + assert(isObject(optionDef), [ + errMsgPrefix + ' Select option definition should be an object, such as,', + _SAMPLE_SELECT_DEFINITION + ].join('\n')); + assert(optionDef.hasOwnProperty('value') || isObject(optionDef.input), [ + errMsgPrefix + ' Select option definition should contain prop' + + ' either `value` or `option`, such as,', + _SAMPLE_SELECT_DEFINITION + ].join('\n')); + var text = getType(optionDef.text) === 'string' + ? optionDef.text + : makeSelectInputTextByValue(optionDef); + selectCtx._optionList.push({ + value: optionDef.value, + input: optionDef.input, + text: text + }); + } + } + else if (inputDefine.values) { + for (var optionIdx = 0; optionIdx < inputDefine.values.length; optionIdx++) { + var value = inputDefine.values[optionIdx]; + selectCtx._optionList.push({ + value: value, + text: makeSelectInputTextByValue({value: value}) + }); + } + } + if (!selectCtx._optionList.length) { + throw new Error(errMsgPrefix + ' No options specified for select.'); + } + + for (var optionIdx = 0; optionIdx < selectCtx._optionList.length; optionIdx++) { + var optionDef = selectCtx._optionList[optionIdx]; + selectCtx._optionList[optionIdx] = optionDef; + var optionEl = document.createElement('option'); + optionEl.innerHTML = encodeHTML(optionDef.text); + // HTML select.value is always string. But it would be more convenient to + // convert it to user's raw input value type. + // (The input raw value can be null/undefined/array/object/... everything). + optionEl.value = optionIdx; + selectCtx._selectEl.appendChild(optionEl); + + if (optionDef.input) { + if (arrayIndexOf(NAMES_TYPE_RANGE, optionDef.input.type) < 0) { + throw new Error(errMsgPrefix + ' Sub input only supported for range input.'); + } + var rangeInputCreated = createRangeInput(optionDef.input, { + text: '', + onchange: function () { + if (selectCtx._disabled) { return; } + triggerUserSelectChangedEvent(); + } + }, inputRecorder); + for (var idx = 0; idx < rangeInputCreated.elList.length; idx++) { + selectCtx._el.appendChild(rangeInputCreated.elList[idx]); + } + selectCtx._optionIdxToSubInput[optionIdx] = rangeInputCreated; + } + } + } + + function resetSelectInputDisabled(opt) { + selectCtx._disabled = !!opt.disabled; + selectCtx._selectEl.disabled = selectCtx._disabled; + resetSelectInputWrapperCSS(selectCtx._selectWrapperEl, selectCtx._disabled); + resetSelectInputSubInputsDisabled(); + } + + function getSelectInputState() { + var optionIndex = getSelectInputOptionIndex(); + var state = {}; + state.optionIndex = optionIndex; + state.disabled = selectCtx._disabled; + if (selectCtx._optionIdxToSubInput.length) { // Make literal state short to save space. + state.optionStateMap = {}; + for (var optionIdx = 0; optionIdx < selectCtx._optionIdxToSubInput.length; optionIdx++) { + if (selectCtx._optionIdxToSubInput[optionIdx]) { + state.optionStateMap[optionIdx] = selectCtx._optionIdxToSubInput[optionIdx].getState(); + } + } + } + return state; + } + + function setSelectInputState(state) { + if (!isObject(state)) { + console.error( + errMsgPrefix + ' Invalid select input state: ' + printObject(state) + + ' May caused by test case change.' + ); + return; + } + if (!validateOptionIndex(state.optionIndex)) { + return; + } + + var optionStateMap = state.optionStateMap || {}; + for (var optionIdx in optionStateMap) { + if (state.optionStateMap.hasOwnProperty(optionIdx)) { + var subInput = selectCtx._optionIdxToSubInput[optionIdx]; + if (!subInput) { + console.error( + errMsgPrefix + ' Invalid select input state: ' + printObject(state) + + ' Can not find a sub-input by optionIndex: ' + optionIdx + '.' + + ' May caused by test case change.' + ); + return; + } + } + } + for (var optionIdx in optionStateMap) { + if (state.optionStateMap.hasOwnProperty(optionIdx)) { + var subInput = selectCtx._optionIdxToSubInput[optionIdx]; + subInput.setState(state.optionStateMap[optionIdx]); + } + } + resetSelectInputDisabled({disabled: state.disabled}); + resetSelectInputOptionIndex(state.optionIndex); + } + + function validateOptionIndex(optionIndex) { + if (getType(optionIndex) !== 'number' + || optionIndex < 0 + || optionIndex >= selectCtx._optionList.length + ) { + console.error( + errMsgPrefix + ' Invalid select, optionIndex: ' + optionIndex + ' is out if range.' + + ' May caused by test case change.' + ); + return false; + } + return true; + } + + function setSelectInputInitValue(inputDefine) { + var initOptionIdx = 0; + var initOptionIdxOpt = retrieveValue(inputDefine.optionIndex, inputDefine.valueIndex, undefined); + if (initOptionIdxOpt != null) { + if (initOptionIdxOpt < 0 || initOptionIdxOpt >= selectCtx._optionList.length) { + throw new Error(errMsgPrefix + ' Invalid optionIndex: ' + initOptionIdxOpt); + } + selectCtx._selectEl.value = selectCtx._optionList[initOptionIdxOpt].value; + initOptionIdx = initOptionIdxOpt; + } + else if (inputDefine.hasOwnProperty('value')) { + var found = false; + for (var idx = 0; idx < selectCtx._optionList.length; idx++) { + if (!selectCtx._optionList[idx].input && selectCtx._optionList[idx].value === inputDefine.value) { + found = true; + initOptionIdx = idx; + } + } + if (!found) { + throw new Error(errMsgPrefix + ' Value not found in select options: ' + inputDefine.value); + } + } + resetSelectInputOptionIndex(initOptionIdx); + } + + function resetSelectInputOptionIndex(optionIdx) { + selectCtx._selectEl.value = optionIdx; + resetSelectInputSubInputsDisabled(); + } + + function getSelectInputOptionIndex() { + return +selectCtx._selectEl.value; + } + + function getSelectInputValueByOptionIndex(optionIdx) { + return selectCtx._optionList[optionIdx].input + ? selectCtx._optionIdxToSubInput[optionIdx].getState().value + : selectCtx._optionList[optionIdx].value; + } + + function triggerUserSelectChangedEvent() { + var optionIdx = getSelectInputOptionIndex(); + var value = getSelectInputValueByOptionIndex(optionIdx); + var target = {value: value}; + _selectListener.call(target, {target: target}); + } + + function resetSelectInputSubInputsDisabled() { + var optionIdx = getSelectInputOptionIndex(); + for (var i = 0; i < selectCtx._optionIdxToSubInput.length; i++) { + var subInput = selectCtx._optionIdxToSubInput[i]; + if (subInput) { + var disabled = selectCtx._disabled + ? true // Disable all options. + : i !== optionIdx // Disable all except current selected option. + subInput.disable({disabled: disabled}); + } + } + } + + function makeSelectInputTextByValue(optionDef) { + if (optionDef.hasOwnProperty('value')) { + return printObject(optionDef.value, { + arrayLineBreak: false, objectLineBreak: false, indent: 0, lineBreak: '' + }); + } + else if (optionDef.input) { + return 'range input'; + } + } + } // End of createSelectInput + + function createGroupSetInput(groupSetDefine) { + assert( + getType(groupSetDefine.inputsHeight) === 'number', + '`inputsHeight` is mandatory on groupSet to avoid height change' + + ' to affects visual testing when switching groups.' + ) + assert( + getType(groupSetDefine.groups) === 'array', + '.groups must be an array.' + ); + assert( + groupSetDefine.groups.length > 0, + 'groupset.group must have at least one group' + ); + + var groupSetEl = document.createElement('div'); + initInputsContainer(groupSetEl, groupSetDefine, { + ignoreInputsStyle: true, + className: 'test-inputs-groupset', + }); + var groupSetMarginBottomEl = document.createElement('div'); + groupSetMarginBottomEl.className = 'test-inputs-groupset-margin-bottom'; + + var groupSetTextEl = document.createElement('div'); + groupSetTextEl.className = 'test-inputs-groupset-text'; + groupSetEl.appendChild(groupSetTextEl); + + var groupSetCreated = { + currentGroupIndex: 0, + elList: [groupSetEl, groupSetMarginBottomEl], + children: [], + getState: getGroupSetInputState, + setState: setGroupSetInputState, + switchGroup: switchGroup + }; + + for (var groupIdx = 0; groupIdx < groupSetDefine.groups.length; groupIdx++) { + var groupDefine = groupSetDefine.groups[groupIdx]; + assert(groupDefine, 'groupset.group must not be undefined/null.'); + + var groupChildInputsContainer = document.createElement('div'); + initInputsContainer(groupChildInputsContainer, groupSetDefine, { + ignoreFixHeight: true, + className: 'test-inputs-groupset-group', + }); + groupSetEl.appendChild(groupChildInputsContainer); + + var groupChildId = retrieveId(groupDefine, 'id'); + if (groupChildId == null) { + throw new Error('In group child input, id must be specified.'); + } + + var groupCreated = { + groupParent: groupSetCreated, + inputsContainerEl: groupChildInputsContainer, + groupSetTextEl: groupSetTextEl, + groupDefine: groupDefine, + idList: null, + groupIndex: groupSetCreated.children.length + }; + groupSetCreated.children.push(groupCreated); + + storeToInputDict(groupDefine, groupCreated); + + var inputsDefineList = retrieveInputDefineList(groupDefine).slice(); + + // Cascade `disabled`. + for (var inputIdx = 0; inputIdx < inputsDefineList.length; inputIdx++) { + var inputDefine = inputsDefineList[inputIdx]; + if (!inputDefine) { + continue; + } + assert(isObject(inputDefine)); + inputsDefineList[inputIdx] = inputDefine = Object.assign({}, inputDefine); + inputDefine.disabled = retrieveValue( + inputDefine.disabled, groupDefine.disabled, groupSetDefine.disabled + ); + } + + groupCreated.idList = dealInitEachInput(inputsDefineList, groupChildInputsContainer); + + showHideGroupInGroupSet(groupCreated, false); + } + + showHideGroupInGroupSet(groupSetCreated.children[groupSetCreated.currentGroupIndex], true); + + return groupSetCreated; + + function switchGroup(groupId) { + var groupCreatedToShow = retrieveAndVerifyGroup(groupId); + if (groupCreatedToShow.groupIndex === groupCreatedToShow.groupParent.currentGroupIndex) { + return; + } + var groupCreatedToHide = groupCreatedToShow.groupParent.children[ + groupCreatedToShow.groupParent.currentGroupIndex + ]; + showHideGroupInGroupSet(groupCreatedToHide, false); + showHideGroupInGroupSet(groupCreatedToShow, true); + groupCreatedToShow.groupParent.currentGroupIndex = groupCreatedToShow.groupIndex; + } + + function getGroupSetInputState() { + var state = {currentGroupIndex: groupSetCreated.currentGroupIndex}; + return state; + } + + function setGroupSetInputState(state) { + if (!isObject(state)) { + console.error( + errMsgPrefix + ' Invalid group set state: ' + printObject(state) + + ' May caused by test case change.' + ); + return; + } + var currentGroupIndex = state.currentGroupIndex; + if (getType(currentGroupIndex) !== 'number' + || currentGroupIndex < 0 + || currentGroupIndex >= groupSetCreated.children.length + ) { + console.error( + errMsgPrefix + ' Invalid group set currentGroupIndex: ' + currentGroupIndex + + ' May caused by test case change.' + ); + return; + } + switchGroup(currentGroupIndex); + } + + } // End of createGroupSetInput + + function createButtonInput(inputDefine, inputRecorder) { + var _btnDisabled = false; + var btn = document.createElement('button'); + btn.innerHTML = getInputsTextHTML(inputDefine, 'button'); + var _btnListener = getBtnEventListener(inputDefine, NAMES_ON_CLICK); + assert(_btnListener, 'No button onclick provided.'); + btn.addEventListener('click', inputRecorder.wrapUserInputListener({ + listener: function () { + if (_btnDisabled) { return; } + return _btnListener.apply(this, arguments); + }, + op: 'click' + })); + resetButtonInputDisabled(inputDefine); + + return { + elList: [btn], + disable: resetButtonInputDisabled, + setState: setButtonInputState, + getState: getButtonInputState + }; + + function resetButtonInputDisabled(opt) { + _btnDisabled = !!opt.disabled; + btn.disabled = _btnDisabled; + } + function getButtonInputState() { + return {disabled: _btnDisabled}; + } + function setButtonInputState(state) { + if (!isObject(state)) { + console.error( + errMsgPrefix + ' Button input state must be object rather than ' + printObject(state) + + ' May caused by test case change.' + ); + return; + } + resetButtonInputDisabled(state); + } + } // End of createButtonInput + + function createBr(inputDefine) { + return {elList: [document.createElement('br')]}; + } + + function createHr(inputDefine) { + var _hrWrapperEl = document.createElement('div'); + _hrWrapperEl.className = 'test-inputs-hr' + var textEl = document.createElement('span'); + textEl.className = 'test-inputs-hr-text'; + _hrWrapperEl.appendChild(textEl); + var text = textEl.innerHTML = getInputsTextHTML(inputDefine, ''); + textEl.style.display = text ? 'block' : 'none'; + + return { + elList: [_hrWrapperEl] + }; + } + + } // End of initInputs function initRecordCanvas(opt, chart, recordCanvasContainer) { if (!opt.recordCanvas) { @@ -239,6 +1585,156 @@ } } + /** + * @param {EChartsInstance} chart + * @param {Parameter['boundingRect']} opt.boundingRect + */ + function initShowBoundingRects(chart, echarts, opt, boundingRectsContainer) { + assert(chart.__testHelper); + + var _bRectZr; + var _bRectGroup; + // @type Parameter['boundingRect'] + var _currBoundingRectOpt = false; + + chart.__testHelper.updateBoundingRects + = chart.__testHelper.updateBoundingRect + = chart.__testHelper.boundingRect + = chart.__testHelper.boundingRects + = updateBoundingRects; + + updateBoundingRects(opt.boundingRect); + + return; + + function updateBoundingRects(opt) { + if (arguments.length > 0) { + _currBoundingRectOpt = opt; + } // If no opt, keep the last one. + + _currBoundingRectOpt + ? buildBoundingRects(_currBoundingRectOpt) + : disableBoundingRects(); + } + + function ensureBoundingRectsFacilities() { + // zr requires size non-zero. + boundingRectsContainer.style.width = chart.getWidth() + 'px'; + boundingRectsContainer.style.height = chart.getHeight() + 'px'; + + if (_bRectZr) { + _bRectZr.resize(); + return; + } + + _bRectGroup = new echarts.graphic.Group(); + _bRectGroup.__testHelperBoundingRectsRoot = true; + _bRectGroup.on('click', function (event) { + var target = event.target; + if (!target || !target.__testHelperBoundingRectTarget) { + return; + } + var wrapper = { + boundingRect: target, + rawElement: target.__testHelperBoundingRectTarget + }; + console.log('boundingRect:', wrapper.boundingRect); + console.log('rawElement:', wrapper.rawElement); + window.$0 = wrapper; + }); + _bRectZr = echarts.zrender.init(boundingRectsContainer); + _bRectZr.add(_bRectGroup); + } + + function disableBoundingRects() { + chart.off('finished', updateBoundingRects); + boundingRectsContainer.style.display = 'none'; + if (_bRectGroup) { + _bRectGroup.removeAll(); + } + } + + function buildBoundingRects(boundingRectOpt) { + ensureBoundingRectsFacilities(); + boundingRectOpt = isObject(boundingRectOpt) ? boundingRectOpt : {}; + + boundingRectsContainer.style.display = 'block'; + _bRectGroup.removeAll(); + + var strokeColor = boundingRectOpt.color || 'rgba(0,0,255,0.5)'; + var silent = boundingRectOpt.silent != null ? boundingRectOpt.silent : false; + + boundingRectsContainer.style.pointerEvent = silent ? 'none' : 'auto'; + + var roots = chart.getZr().storage.getRoots(); + for (var rootIdx = 0; rootIdx < roots.length; rootIdx++) { + travelGroupAndBuildRects(roots[rootIdx], _bRectGroup); + } + + // Follow chart update and resize. + chart.on('finished', updateBoundingRects); + + return; + + function travelGroupAndBuildRects(group, visualRectGroupParent) { + var visualRectGroup = createVisualRectGroup(group, visualRectGroupParent) + group.eachChild(function (child) { + if (child.isGroup) { + travelGroupAndBuildRects(child, visualRectGroup); + return; + } + + createRectForDisplayable(child, visualRectGroup); + + var textContent = child.getTextContent(); + var textGuildLine = child.getTextGuideLine(); + if (textContent || textGuildLine) { + textContent && createRectForDisplayable(textContent, _bRectGroup, true); + textGuildLine && createRectForDisplayable(textGuildLine, _bRectGroup, true); + } + }); + + function createVisualRectGroup(fromEl, visualRectGroupParent) { + var visualRectGroup = new echarts.graphic.Group(); + copyTransformAttrs(visualRectGroup, fromEl); + visualRectGroupParent.add(visualRectGroup); + return visualRectGroup; + } + + function createRectForDisplayable(el, visualRectGroup, useInnerTransformable) { + var elRawRect = el.getBoundingRect(); + var visualRect = new echarts.graphic.Rect({ + shape: {x: elRawRect.x, y: elRawRect.y, width: elRawRect.width, height: elRawRect.height}, + style: {fill: null, stroke: strokeColor, lineWidth: 1, strokeNoScale: true}, + silent: silent, + z: Number.MAX_SAFE_INTEGER + }); + visualRect.__testHelperBoundingRectTarget = el; + var transAttrSource = el; + if (useInnerTransformable && el.innerTransformable) { + transAttrSource = el.innerTransformable; + } + copyTransformAttrs(visualRect, transAttrSource); + visualRectGroup.add(visualRect); + } + } + + function copyTransformAttrs(target, source) { + target.x = source.x; + target.y = source.y; + target.rotation = source.rotation; + target.scaleX = source.scaleX; + target.scaleY = source.scaleY; + target.originX = source.originX; + target.originY = source.originY; + target.skewX = source.skewX; + target.skewY = source.skewY; + target.anchorX = source.anchorX; + target.anchorY = source.anchorY; + } + } + } + testHelper.createRecordVideo = function (chart, recordVideoContainer) { var button = document.createElement('button'); button.innerHTML = 'Start Recording'; @@ -250,7 +1746,7 @@ button.onclick = function () { isRecording ? recorder.stop() : recorder.start(); - button.innerHTML = `${isRecording ? 'Start' : 'Stop'} Recording`; + button.innerHTML = (isRecording ? 'Start' : 'Stop') + ' Recording'; isRecording = !isRecording; } @@ -269,8 +1765,9 @@ * @param {number} opt.height * @param {boolean} opt.draggable * @param {string} opt.renderer 'canvas' or 'svg' + * @param {string} errMsgPrefix */ - testHelper.createChart = function (echarts, domOrId, option, opt) { + testHelper.createChart = function (echarts, domOrId, option, opt, errMsgPrefix) { if (typeof opt === 'number') { opt = {height: opt}; } @@ -297,7 +1794,7 @@ if (opt.draggable) { if (!window.draggable) { throw new Error( - 'Pleasse add the script in HTML: \n' + errMsgPrefix + ' Pleasse add the script in HTML: \n' + '' ); } @@ -370,9 +1867,10 @@ resultDom.style.cssText = [ 'position: absolute;', 'left: 20px;', + 'pointer-events: none;', 'font-size: ' + fontSize + 'px;', 'z-index: ' + (failErr ? 99999 : 88888) + ';', - 'color: ' + (failErr ? 'red' : 'green') + ';', + 'color: ' + (failErr ? 'rgba(150,0,0,0.8)' : 'rgba(0,150,0,0.8)') + ';', ].join(''); printAssertRecord.push(resultDom); hostDOMEl.appendChild(resultDom); @@ -493,13 +1991,16 @@ var newHeight = dom.clientHeight; if (width !== newWidth || height !== newHeight) { chart.resize(); + if (chart.__testHelper && chart.__testHelper.updateBoundingRects) { + chart.__testHelper.updateBoundingRects(); + } width = newWidth; height = newHeight; } } if (window.attachEvent) { // Use builtin resize in IE - window.attachEvent('onresize', chart.resize); + window.attachEvent('onresize', resize); } else if (window.addEventListener) { window.addEventListener('resize', resize, false); @@ -555,7 +2056,7 @@ return '/' + resolvedPath; }; - testHelper.encodeHTML = function (source) { + var encodeHTML = testHelper.encodeHTML = function (source) { return String(source) .replace(/&/g, '&') .replace(/ 3) { + // `3` is an arbitrary value, considering a path array: + // [ + // [1,2], [3,4], [5,6], + // [7,8], [9,10] + // ] + preventParentArrayPartiallyBreak = true; + } + if (!forceObjectLineBreak && maxColumnWithoutLineBreak > lineBreakMaxColumn) { + hasLineBreak = true; } var tail = hasLineBreak ? lineBreak : ''; - var delimiter = ',' + (hasLineBreak ? (lineBreak + subCodeIndent) : ' '); var subPre = hasLineBreak ? subCodeIndent : ''; var endPre = hasLineBreak ? codeIndent : ''; - str = '' - + preStr + '[' + tail - + subPre + childBuilder.join(delimiter) + tail - + endPre + ']'; + var delimiterInline = ', '; + var delimiterBreak = ',' + lineBreak + subCodeIndent; + if (!childBuilder.length) { + str = preStr + '[]'; + } + else { + var subContentStr = ''; + var subContentMaxColumn = 0; + if (canPartiallyBreak && hasLineBreak) { + for (var idx = 0; idx < childBuilder.length; idx++) { + var childStr = childBuilder[idx]; + subContentMaxColumn += childStr.length + delimiterInline.length; + if (idx === childBuilder.length - 1) { + subContentStr += childStr; + } + else if (subContentMaxColumn > lineBreakMaxColumn) { + subContentStr += childStr + delimiterBreak; + subContentMaxColumn = 0; + } + else { + subContentStr += childStr + delimiterInline; + } + } + } + else { + subContentStr = childBuilder.join(hasLineBreak ? delimiterBreak : delimiterInline); + } + str = '' + + preStr + '[' + tail + + subPre + subContentStr + tail + + endPre + ']'; + } break; case 'object': - hasLineBreak = opt.objectLineBreak != null ? opt.objectLineBreak : true; + if (forceObjectLineBreak) { + hasLineBreak = !!opt.objectLineBreak; + } var childBuilder = []; + var maxColumnWithoutLineBreak = preStr.length; + var keyCount = 0; for (var i in obj) { if (obj.hasOwnProperty(i)) { + keyCount++; var subResult = doPrint(obj[i], i, depth + 1); childBuilder.push(subResult.str); + if (subResult.hasLineBreak) { hasLineBreak = true; } + else { + maxColumnWithoutLineBreak += subResult.str.length + 2; // `2` is ', '.length + } + + if (subResult.preventParentArrayPartiallyBreak) { + preventParentArrayPartiallyBreak = true; + } } } - str = '' - + preStr + '{' + (hasLineBreak ? lineBreak : '') - + (childBuilder.length - ? (hasLineBreak ? subCodeIndent : '') + childBuilder.join(',' + (hasLineBreak ? lineBreak + subCodeIndent: ' ')) + (hasLineBreak ? lineBreak: '') - : '' - ) - + (hasLineBreak ? codeIndent : '') + '}'; + if (keyCount > 1) { + // `3` is an arbitrary value, considering case like: + // [ + // {name: 'xx'}, {name: 'yy'}, {name: 'zz'}, + // {name: 'aa'}, {name: 'bb'} + // ] + preventParentArrayPartiallyBreak = true; + } + if (!forceObjectLineBreak && maxColumnWithoutLineBreak > lineBreakMaxColumn) { + hasLineBreak = true; + } + if (!childBuilder.length) { + str = preStr + '{}'; + } + else { + str = '' + + preStr + '{' + (hasLineBreak ? lineBreak : '') + + (hasLineBreak ? subCodeIndent : '') + + childBuilder.join(',' + (hasLineBreak ? lineBreak + subCodeIndent: ' ')) + + (hasLineBreak ? lineBreak: '') + + (hasLineBreak ? codeIndent : '') + '}'; + } break; case 'boolean': case 'number': str = preStr + obj + ''; break; case 'string': - str = JSON.stringify(obj); // escapse \n\r or others. - str = preStr + quotationMark + str.slice(1, str.length - 1) + quotationMark; + str = preStr + convertStringToJSLiteral(obj, quotationMark); break; default: str = preStr + obj + ''; + preventParentArrayPartiallyBreak = true; } return { str: str, - hasLineBreak: hasLineBreak + hasLineBreak: hasLineBreak, + isMethodShorthand: isMethodShorthand, + preventParentArrayPartiallyBreak: preventParentArrayPartiallyBreak }; } + + /** + * Simple implementation for detecting method shorthand, such as, + * ({abc() { return 1; }}).abc is a method shorthand and needs to + * be serialized as `{abc() { return 1; }}` rather than `{abc: abc() { return 1; }}`. + * Those cases can be detected: + * ({abc() { console.log('=>'); return 1; }}).abc expected: IS_SHORTHAND + * ({abc(x, y = 5) { return 1; }}).abc expected: IS_SHORTHAND + * ({$ab_c() { return 1; }}).$ab_c expected: IS_SHORTHAND + * ({*abc() { return 1; }}).abc expected: IS_SHORTHAND + * ({* abc() { return 1; }}).abc expected: IS_SHORTHAND + * ({async abc() { return 1; }}).abc expected: IS_SHORTHAND + * ({*abc() { yield 1; }}).abc expected: IS_SHORTHAND + * ({abc(x, y) { return x + y; }}).abc expected: IS_SHORTHAND + * ({abc: function abc() { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: function def() { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: function() { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: function* () { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: function (aa, bb) { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: function (aa, bb = 5) { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: async () => { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: () => { return 1; }}).abc expected: NOT_SHORTHAND + * ({abc: (aa, bb = 5) => { return 1; }}).abc expected: NOT_SHORTHAND + * FIXME: fail at some rare cases, such as: + * Literal string involved, like: + * ({"ab-() ' =>c"() { return 1; }})["ab-() ' =>c"] expected: IS_SHORTHAND + * ({async "ab-c"() { return 1; }})["ab-c"] expected: IS_SHORTHAND + * Computed property name involved, like: + * ({[some]() { return 1; }})[some] expected: IS_SHORTHAND + */ + function isMethodShorthandNotAccurate(fnStr, fnName, objKey) { + // Assert fnStr, fnName, objKey is a string. + if (fnName !== objKey) { + return false; + } + var matched = fnStr.match(/^\s*(async\s+)?(function\s*)?(\*\s*)?([a-zA-Z$_][a-zA-Z0-9$_]*)?\s*\(/); + if (!matched) { + return false; + } + if (matched[2]) { // match 'function' + return false; + } + // May enhanced by /(['"])(?:(?=(\\?))\2.)*?\1/; to match literal string, + // such as "ab-c", "a\nc". But this simple impl does not cover it. + if (!matched[4] || matched[4] !== objKey) { // match "maybe function name" + return false; + } + return true; + } + }; /** @@ -733,7 +2415,7 @@ * @param {function} [opt.filter] print a subtree only if any satisfied node exists. * param: el, return: boolean */ - testHelper.stringifyElements = function (chart, opt) { + var stringifyElements = testHelper.stringifyElements = function (chart, opt) { if (!chart) { return; } @@ -823,7 +2505,7 @@ * * @see `stringifyElements`. */ - testHelper.printElements = function (chart, opt) { + var printElements = testHelper.printElements = function (chart, opt) { var elsStr = testHelper.stringifyElements(chart, opt); console.log(elsStr); }; @@ -843,7 +2525,7 @@ * param: el, return: boolean * @return {Array.} */ - testHelper.retrieveElements = function (chart, opt) { + var retrieveElements = testHelper.retrieveElements = function (chart, opt) { if (!chart) { return; } @@ -912,6 +2594,20 @@ document.body.appendChild(canvas); }; + function initDataTables(opt, dataTableContainer) { + var dataTables = opt.dataTables; + if (!dataTables && opt.dataTable) { + dataTables = [opt.dataTable]; + } + if (dataTables) { + var tableHTML = []; + for (var i = 0; i < dataTables.length; i++) { + tableHTML.push(createDataTableHTML(dataTables[i], opt)); + } + dataTableContainer.innerHTML = tableHTML.join(''); + } + } + function createDataTableHTML(data, opt) { var sourceFormat = detectSourceFormat(data); var dataTableLimit = opt.dataTableLimit || DEFAULT_DATA_TABLE_LIMIT; @@ -928,7 +2624,7 @@ var htmlLine = ['']; for (var j = 0; j < line.length; j++) { var val = i === dataTableLimit ? '...' : line[j]; - htmlLine.push('' + testHelper.encodeHTML(val) + ''); + htmlLine.push('' + encodeHTML(val) + ''); } htmlLine.push(''); html.push(htmlLine.join('')); @@ -941,9 +2637,9 @@ for (var key in line) { if (line.hasOwnProperty(key)) { var keyText = i === dataTableLimit ? '...' : key; - htmlLine.push('' + testHelper.encodeHTML(keyText) + ''); + htmlLine.push('' + encodeHTML(keyText) + ''); var val = i === dataTableLimit ? '...' : line[key]; - htmlLine.push('' + testHelper.encodeHTML(val) + ''); + htmlLine.push('' + encodeHTML(val) + ''); } } htmlLine.push(''); @@ -953,12 +2649,12 @@ else if (sourceFormat === 'keyedColumns') { for (var key in data) { var htmlLine = ['']; - htmlLine.push('' + testHelper.encodeHTML(key) + ''); + htmlLine.push('' + encodeHTML(key) + ''); if (data.hasOwnProperty(key)) { var col = data[key] || []; for (var i = 0; i < col.length && i <= dataTableLimit; i++) { var val = i === dataTableLimit ? '...' : col[i]; - htmlLine.push('' + testHelper.encodeHTML(val) + ''); + htmlLine.push('' + encodeHTML(val) + ''); } } htmlLine.push(''); @@ -994,7 +2690,7 @@ function createObjectHTML(obj, key) { var html = isObject(obj) - ? testHelper.encodeHTML(printObject(obj, key)) + ? encodeHTML(printObject(obj, key)) : obj ? obj.toString() : ''; @@ -1051,6 +2747,217 @@ return type === 'function' || (!!value && type === 'object'); } + function arrayIndexOf(arr, value) { + if (arr.indexOf) { + return arr.indexOf(value); + } + for (var i = 0; i < arr.length; i++) { + if (arr[i] === value) { + return i; + } + } + return -1; + } + + var assert = testHelper.assert = function (cond, msg) { + if (!cond) { + throw new Error(msg || 'Assertion failed.'); + } + } + + function makeFlexibleNames(dashedNames) { + var nameMap = {}; + for (var i = 0; i < dashedNames.length; i++) { + var name = dashedNames[i]; + var tmpNames = []; + tmpNames.push(name); + tmpNames.push(name.replace(/-/g, '')); + tmpNames.push(name.replace(/-/g, '_')); + tmpNames.push(name.replace(/-([a-zA-Z0-9])/g, function (_, wf) { + return wf.toUpperCase(); + })); + for (var j = 0; j < tmpNames.length; j++) { + nameMap[tmpNames[j]] = 1; + nameMap[tmpNames[j].toUpperCase()] = 1; + nameMap[tmpNames[j].toLowerCase()] = 1; + } + } + var names = []; + for (var name in nameMap) { + if (nameMap.hasOwnProperty(name)) { + names.push(name); + } + } + return names; + } + + /** + * Copied from src/util/number.ts + */ + function getPrecision(val) { + val = +val; + if (isNaN(val)) { + return 0; + } + + // It is much faster than methods converting number to string as follows + // let tmp = val.toString(); + // return tmp.length - 1 - tmp.indexOf('.'); + // especially when precision is low + // Notice: + // (1) If the loop count is over about 20, it is slower than `getPrecisionSafe`. + // (see https://jsbench.me/2vkpcekkvw/1) + // (2) If the val is less than for example 1e-15, the result may be incorrect. + // (see test/ut/spec/util/number.test.ts `getPrecision_equal_random`) + if (val > 1e-14) { + var e = 1; + for (var i = 0; i < 15; i++, e *= 10) { + if (Math.round(val * e) / e === val) { + return i; + } + } + } + + return getPrecisionSafe(val); + } + + /** + * Copied from src/util/number.ts + * Get precision with slow but safe method + */ + function getPrecisionSafe(val) { + // toLowerCase for: '3.4E-12' + var str = val.toString().toLowerCase(); + + // Consider scientific notation: '3.4e-12' '3.4e+12' + var eIndex = str.indexOf('e'); + var exp = eIndex > 0 ? +str.slice(eIndex + 1) : 0; + var significandPartLen = eIndex > 0 ? eIndex : str.length; + var dotIndex = str.indexOf('.'); + var decimalPartLen = dotIndex < 0 ? 0 : significandPartLen - 1 - dotIndex; + return Math.max(0, decimalPartLen - exp); + } + + /** + * Copied from src/util/number.ts + */ + function round(x, precision, returnStr) { + if (precision == null) { + precision = 10; + } + // Avoid range error + precision = Math.min(Math.max(0, precision), ROUND_SUPPORTED_PRECISION_MAX); + // PENDING: 1.005.toFixed(2) is '1.00' rather than '1.01' + x = (+x).toFixed(precision); + return (returnStr ? x : +x); + } + // Although chrome already enlarge this number to 100 for `toFixed`, but + // we sill follow the spec for compatibility. + var ROUND_SUPPORTED_PRECISION_MAX = 20; + + + function objectNoOtherNotNullUndefinedPropExcept(obj, exceptProps) { + if (!obj) { + return false; + } + for (var key in obj) { + if (obj.hasOwnProperty(key) && arrayIndexOf(exceptProps, key) < 0 && obj[key] != null) { + return false; + } + } + return true; + } + + var copyToClipboard = function (text) { + if (typeof navigator === 'undefined' || !navigator.clipboard || !navigator.clipboard.writeText) { + console.error('[clipboard] Can not copy to clipboard.'); + return; + } + return navigator.clipboard.writeText(text).then(function () { + console.log('[clipboard] Text copied to clipboard.'); + }).catch(function (err) { + console.error('[clipboard] Failed to copy text: ', err); // Just print for easy to use. + return err; + }); + }; + + /** + * A shortcut for both stringify and copy to clipboard. + * + * @param {any} val Any val to stringify and copy to clipboard. + * @param {Object?} printObjectOpt Optional. + */ + testHelper.clipboard = function (val, printObjectOpt) { + var literal = testHelper.printObject(val, printObjectOpt); + if (document.hasFocus()) { + copyToClipboard(literal); + } + else { + // Handle the error: + // NotAllowedError: Failed to execute 'writeText' on 'Clipboard': Document is not focused. + ensureClipboardButton(); + updateClipboardButton(literal) + console.log( + '⚠️ [clipboard] Please click the new button that appears on the top-left corner of the screen' + + ' to copy to clipboard.' + ); + } + + function updateClipboardButton(text) { + var button = __tmpClipboardButttonWrapper.button; + button.innerHTML = 'Click me to copy to clipboard'; + button.style.display = 'block'; + __tmpClipboardButttonWrapper.text = text; + } + + function ensureClipboardButton() { + var button = __tmpClipboardButttonWrapper.button; + if (button != null) { + return; + } + __tmpClipboardButttonWrapper.button = button = document.createElement('div'); + button.style.cssText = [ + 'height: 80px;', + 'line-height: 80px;', + 'padding: 10px 20px;', + 'margin: 5px;', + 'text-align: center;', + 'position: fixed;', + 'top: 10px;', + 'left: 10px;', + 'z-index: 9999;', + 'cursor: pointer;', + 'color: #fff;', + 'background-color: #333;', + 'border: 2px solid #eee;', + 'border-radius: 5px;', + 'font-size: 18px;', + 'font-weight: bold;', + 'font-family: sans-serif;', + 'box-shadow: 0 4px 10px rgba(0, 0, 0, 0.8);' + ].join(''); + document.body.appendChild(button); + button.addEventListener('click', function () { + copyToClipboard(__tmpClipboardButttonWrapper.text).then(function (err) { + if (!err) { + button.style.display = 'none'; + } + else { + button.innerHTML = 'error, see console log.'; + } + }); + }); + } + // Do not return the text, because it may be too long for a console.log. + }; + var __tmpClipboardButttonWrapper = {}; + + // It may be changed by test case changing. Do not use it as a persistent id. + var _idBase = 1; + function generateNonPersistentId(prefix) { + return (prefix || '') + '' + (_idBase++); + } + function VideoRecorder(chart) { this.start = startRecording; this.stop = stopRecording; @@ -1115,4 +3022,4 @@ context.testHelper = testHelper; -})(window); \ No newline at end of file +})(window); diff --git a/test/tmp-base.html b/test/tmp-base.html index 8c9f5eac21..f81ce9ef5e 100644 --- a/test/tmp-base.html +++ b/test/tmp-base.html @@ -28,34 +28,123 @@ - + + -
+
+ + + \ No newline at end of file From 139206be7adab50f0d79e4d8adf3d14bac15b1bb Mon Sep 17 00:00:00 2001 From: 100pah Date: Sat, 24 May 2025 18:35:27 +0800 Subject: [PATCH 22/49] test: manually sync visual test updates. --- test/runTest/cli.js | 117 +- test/runTest/client/client.css | 1 + test/runTest/client/client.js | 192 +- test/runTest/client/index.html | 54 +- test/runTest/compareScreenshot.js | 11 +- test/runTest/package-lock.json | 2369 ++++++++++++++++++------ test/runTest/package.json | 12 +- test/runTest/recorder/index.html | 2 +- test/runTest/recorder/recorder.css | 5 + test/runTest/recorder/recorder.js | 2 +- test/runTest/runtime/ActionPlayback.js | 30 +- test/runTest/runtime/main.js | 24 +- test/runTest/server.js | 106 +- test/runTest/store.js | 76 +- test/runTest/util.js | 125 +- 15 files changed, 2340 insertions(+), 786 deletions(-) diff --git a/test/runTest/cli.js b/test/runTest/cli.js index 7b7755fda0..cbc40ac5ac 100644 --- a/test/runTest/cli.js +++ b/test/runTest/cli.js @@ -39,7 +39,9 @@ program .option('--no-headless', 'Not headless') .option('-s, --speed ', 'Playback speed') .option('--expected ', 'Expected version') + .option('--expected-source ', 'Expected source') .option('--actual ', 'Actual version') + .option('--actual-source ', 'Actual source') .option('--renderer ', 'svg/canvas renderer') .option('--use-coarse-pointer ', '"auto" (by default) or "true" or "false"') .option('--threads ', 'How many threads to run concurrently') @@ -78,12 +80,12 @@ function getClientRelativePath(absPath) { return path.join('../', path.relative(__dirname, absPath)); } -function replaceEChartsVersion(interceptedRequest, version) { +function replaceEChartsVersion(interceptedRequest, source, version) { // TODO Extensions and maps if (interceptedRequest.url().endsWith('dist/echarts.js')) { - console.log('Use echarts version: ' + version); + console.log('Use echarts version: ' + source + ' ' + version); interceptedRequest.continue({ - url: `${origin}/test/runTest/${getVersionDir(version)}/${getEChartsTestFileName()}` + url: `${origin}/test/runTest/${getVersionDir(source, version)}/${getEChartsTestFileName()}` }); } else { @@ -117,9 +119,9 @@ async function takeScreenshot(page, fullPage, fileUrl, desc, isExpected, minor) if (minor) { screenshotName += '-' + minor; } - let screenshotPrefix = isExpected ? 'expected' : 'actual'; + const screenshotPrefix = isExpected ? 'expected' : 'actual'; fse.ensureDirSync(getScreenshotDir()); - let screenshotPath = path.join(getScreenshotDir(), `${screenshotName}-${screenshotPrefix}.png`); + const screenshotPath = path.join(getScreenshotDir(), `${screenshotName}-${screenshotPrefix}.png`); await page.screenshot({ path: screenshotPath, // https://github.com/puppeteer/puppeteer/issues/7043 @@ -128,11 +130,18 @@ async function takeScreenshot(page, fullPage, fileUrl, desc, isExpected, minor) fullPage }); - const webpScreenshotPath = await convertToWebP(screenshotPath); + let webpScreenshotPath; + try { + webpScreenshotPath = await convertToWebP(screenshotPath); + } catch (e) { + console.error('Failed to convert screenshot to webp', e); + } + + console.log('Screenshot: ', webpScreenshotPath || screenshotPath); return { screenshotName, - screenshotPath: webpScreenshotPath, + screenshotPath: webpScreenshotPath || screenshotPath, rawScreenshotPath: screenshotPath }; } @@ -155,18 +164,20 @@ async function waitForNetworkIdle(page) { page.off('requestfailed', ended); page.off('requestfinished', ended); }; - } - +} -async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) { +/** + * @param {puppeteer.Browser} browser + */ +async function runTestPage(browser, testOpt, source, version, runtimeCode, isExpected) { const fileUrl = testOpt.fileUrl; const screenshots = []; const logs = []; - const errors = []; + const errors = []; // string[] const page = await browser.newPage(); page.setRequestInterception(true); - page.on('request', request => replaceEChartsVersion(request, version)); + page.on('request', request => replaceEChartsVersion(request, source, version)); async function pageScreenshot() { if (!program.save) { @@ -196,14 +207,36 @@ async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) { await page.exposeFunction('__VRT_MOUSE_MOVE__', async (x, y) => { await page.mouse.move(x, y); }); - await page.exposeFunction('__VRT_MOUSE_DOWN__', async () => { - await page.mouse.down(); + await page.exposeFunction('__VRT_MOUSE_DOWN__', async (errMsgPart) => { + try { + await page.mouse.down(); + } + catch (err) { + // e.g., if double mousedown without a mouseup, error "'left' is already pressed." will be thrown. + // Report to users to re-record the test case. + if (errMsgPart) { + if ((err.message + '').indexOf('already pressed') >= 0) { + errMsgPart += ' May be caused by duplicated mousedowns without a mouseup.' + + ' Please re-record the test case.'; + } + err.message = err.message + ' ' + errMsgPart; + } + throw err; + } }); - await page.exposeFunction('__VRT_MOUSE_UP__', async () => { - await page.mouse.up(); + await page.exposeFunction('__VRT_MOUSE_UP__', async (errMsgPart) => { + try { + await page.mouse.up(); + } + catch (err) { + if (errMsgPart) { + err.message = err.message + ' ' + errMsgPart; + } + throw err; + } }); - await page.exposeFunction('__VRT_LOAD_ERROR__', async (err) => { - errors.push(err); + await page.exposeFunction('__VRT_LOAD_ERROR__', async (errStr) => { + errors.push(errStr); }); // await page.exposeFunction('__VRT_WAIT_FOR_NETWORK_IDLE__', async () => { // await waitForNetworkIdle(); @@ -223,8 +256,8 @@ async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) { }); }); - page.exposeFunction('__VRT_LOG_ERRORS__', (err) => { - errors.push(err); + page.exposeFunction('__VRT_LOG_ERRORS__', (errStr) => { + errors.push(errStr); }); let actionScreenshotCount = {}; @@ -265,11 +298,12 @@ async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) { try { await page.setViewport({ width: 800, - height: 600 + height: 600, }); await page.goto(`${origin}/test/${fileUrl}?__RENDERER__=${program.renderer}&__COARSE__POINTER__=${program.useCoarsePointer}`, { waitUntil: 'networkidle2', - timeout: 10000 + timeout: 10000, + // timeout: 0 }); if (!vstInited) { // Not using simpleRequire in the test @@ -316,20 +350,24 @@ async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) { }; } -async function writePNG(diffPNG, diffPath) { - return new Promise(resolve => { - let writer = fs.createWriteStream(diffPath); +function writePNG(diffPNG, diffPath) { + return new Promise((resolve, reject) => { + const writer = fs.createWriteStream(diffPath); diffPNG.pack().pipe(writer); - writer.on('finish', () => {resolve();}); + writer.on('finish', resolve); + writer.on('error', reject); }); }; -async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVersion) { +/** + * @param {puppeteer.Browser} browser + */ +async function runTest(browser, testOpt, runtimeCode, expectedSource, expectedVersion, actualSource, actualVersion) { if (program.save) { testOpt.status === 'running'; - const expectedResult = await runTestPage(browser, testOpt, expectedVersion, runtimeCode, true); - const actualResult = await runTestPage(browser, testOpt, actualVersion, runtimeCode, false); + const expectedResult = await runTestPage(browser, testOpt, expectedSource, expectedVersion, runtimeCode, true); + const actualResult = await runTestPage(browser, testOpt, actualSource, actualVersion, runtimeCode, false); // sortScreenshots(expectedResult.screenshots); // sortScreenshots(actualResult.screenshots); @@ -353,9 +391,15 @@ async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVer const diffPath = `${getScreenshotDir()}/${shot.screenshotName}-diff.png`; await writePNG(diffPNG, diffPath); - const diffWebpPath = await convertToWebP(diffPath); - result.diff = getClientRelativePath(diffWebpPath); + let diffWebpPath; + try { + diffWebpPath = await convertToWebP(diffPath); + } catch (e) { + console.error('Failed to convert diff png to webp', e); + } + + result.diff = getClientRelativePath(diffWebpPath || diffPath); result.diffRatio = diffRatio; // Remove png files @@ -363,7 +407,7 @@ async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVer await Promise.all([ fse.unlink(actual.rawScreenshotPath), fse.unlink(expected.rawScreenshotPath), - fse.unlink(diffPath) + diffWebpPath && fse.unlink(diffPath) ]); } catch (e) {} @@ -391,7 +435,7 @@ async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVer } else { // Only run once - await runTestPage(browser, testOpt, 'local', runtimeCode, true); + await runTestPage(browser, testOpt, expectedSource, 'local', runtimeCode, true); } } @@ -412,7 +456,7 @@ async function runTests(pendingTests) { async function eachTask(testOpt) { console.log(`Running test: ${testOpt.name}, renderer: ${program.renderer}, useCoarsePointer: ${program.useCoarsePointer}`); try { - await runTest(browser, testOpt, runtimeCode, program.expected, program.actual); + await runTest(browser, testOpt, runtimeCode, program.expectedSource, program.expected, program.actualSource, program.actual); } catch (e) { // Restore status @@ -437,7 +481,7 @@ async function runTests(pendingTests) { } } catch(e) { - console.log(e); + console.error(e); } @@ -455,4 +499,5 @@ runTests(program.tests.split(',').map(testName => { expectedErrors: [], status: 'pending' }; -})); \ No newline at end of file +})); + diff --git a/test/runTest/client/client.css b/test/runTest/client/client.css index 1cce88083b..63ec88c424 100644 --- a/test/runTest/client/client.css +++ b/test/runTest/client/client.css @@ -277,6 +277,7 @@ .test-errors, .test-logs { margin-top: 20px; padding: 0 20px; + word-wrap: break-word; } .test-logs .log-item { diff --git a/test/runTest/client/client.js b/test/runTest/client/client.js index 0c34e0ff0e..9241f39edc 100644 --- a/test/runTest/client/client.js +++ b/test/runTest/client/client.js @@ -21,6 +21,8 @@ const socket = io('/client'); // const LOCAL_SAVE_KEY = 'visual-regression-testing-config'; +let handlingSourceChange = false; + function getChangedObject(target, source) { let changedObject = {}; Object.keys(source).forEach(key => { @@ -83,9 +85,15 @@ function processTestsData(tests, oldTestsData) { // Keep select status not change. if (oldTestsData && oldTestsData[idx]) { test.selected = oldTestsData[idx].selected; + // Keep source information + test.expectedSource = oldTestsData[idx].expectedSource; + test.actualSource = oldTestsData[idx].actualSource; } else { test.selected = false; + // Initialize source information + test.expectedSource = app.runConfig.expectedSource; + test.actualSource = app.runConfig.actualSource; } }); return tests; @@ -101,6 +109,22 @@ try { } catch (e) {} +function getVersionFromSource(source, versions, nightlyVersions) { + if (source === 'PR') { + // Default PR version can be empty since it needs to be manually selected + return '#'; + } + else if (source === 'nightly') { + return nightlyVersions.length ? nightlyVersions[0] : null; + } + else if (source === 'local') { + return 'local'; + } + else { + return versions.length ? versions[0] : null; + } +} + const app = new Vue({ el: '#app', data: { @@ -112,9 +136,6 @@ const app = new Vue({ allSelected: false, lastSelectedIndex: -1, - expectedVersionsList: [], - actualVersionsList: [], - loadingVersion: false, showIframeDialog: false, @@ -128,21 +149,27 @@ const app = new Vue({ pageInvisible: false, + versions: [], + nightlyVersions: [], + prVersions: [], + branchVersions: [], + runConfig: Object.assign({ sortBy: 'name', - - isActualNightly: false, - isExpectedNightly: false, - actualVersion: 'local', + actualVersion: null, expectedVersion: null, - + expectedSource: 'release', + actualSource: 'local', renderer: 'canvas', useCoarsePointer: 'auto', threads: 4 }, urlRunConfig) }, - mounted() { + async mounted() { + // Add call to fetch branches + await this.fetchBranchVersions(); + // Sync config from server when first time open // or switching back socket.emit('syncRunConfig', { @@ -151,12 +178,38 @@ const app = new Vue({ forceSet: Object.keys(urlRunConfig).length > 0 }); socket.on('syncRunConfig_return', res => { - this.expectedVersionsList = res.expectedVersionsList; - this.actualVersionsList = res.actualVersionsList; - // Only assign on changed object to avoid unnecessary vue change. - Object.assign(this.runConfig, getChangedObject(this.runConfig, res.runConfig)); + this.versions = res.versions || []; + this.nightlyVersions = res.nightlyVersions || []; + this.prVersions = res.prVersions || []; + + // Only set versions if they haven't been manually set + handlingSourceChange = true; + this.$nextTick(() => { + if (!this.runConfig.expectedVersion) { + this.runConfig.expectedVersion = getVersionFromSource( + this.runConfig.expectedSource, + this.versions, + this.nightlyVersions + ); + } - updateUrl(); + if (!this.runConfig.actualVersion) { + this.runConfig.actualVersion = getVersionFromSource( + this.runConfig.actualSource, + this.versions, + this.nightlyVersions + ); + } + + // Only apply other config changes from server + const configWithoutVersions = { ...res.runConfig }; + delete configWithoutVersions.expectedVersion; + delete configWithoutVersions.actualVersion; + Object.assign(this.runConfig, getChangedObject(this.runConfig, configWithoutVersions)); + + handlingSourceChange = false; + updateUrl(); + }); }); setTimeout(() => { @@ -172,6 +225,16 @@ const app = new Vue({ this.pageInvisible = true; } }); + + socket.on('run_error', err => { + app.$notify({ + title: 'Error', + message: err.message, + type: 'error', + duration: 5000 + }); + app.running = false; + }); }, computed: { @@ -254,6 +317,36 @@ const app = new Vue({ }); }, set() {} + }, + + expectedVersionsList() { + switch (this.runConfig.expectedSource) { + case 'release': + return this.versions; + case 'nightly': + return this.nightlyVersions; + case 'PR': + return this.prVersions; + case 'local': + return ['local']; + default: + return []; + } + }, + + actualVersionsList() { + switch (this.runConfig.actualSource) { + case 'release': + return this.versions; + case 'nightly': + return this.nightlyVersions; + case 'PR': + return this.prVersions; + case 'local': + return ['local']; + default: + return []; + } } }, @@ -266,6 +359,44 @@ const app = new Vue({ 'currentTestName'(newVal, oldVal) { updateUrl(); + }, + + 'runConfig.expectedSource': { + handler(newVal, oldVal) { + if (newVal === oldVal) { + return; + } + + handlingSourceChange = true; + this.$nextTick(() => { + this.runConfig.expectedVersion = getVersionFromSource( + newVal, + this.versions, + this.nightlyVersions + ); + handlingSourceChange = false; + }); + }, + deep: false + }, + + 'runConfig.actualSource': { + handler(newVal, oldVal) { + if (newVal === oldVal) { + return; + } + + handlingSourceChange = true; + this.$nextTick(() => { + this.runConfig.actualVersion = getVersionFromSource( + newVal, + this.versions, + this.nightlyVersions + ); + handlingSourceChange = false; + }); + }, + deep: false } }, @@ -339,8 +470,12 @@ const app = new Vue({ let searches = []; let ecVersion = test[version + 'Version']; + let ecSource = test[version + 'Source']; if (ecVersion !== 'local') { - searches.push('__ECDIST__=' + ecVersion); + let distPath = ecSource === 'PR' + ? 'pr-' + ecVersion.replace(/^#/, '') + : ecVersion; + searches.push('__ECDIST__=' + distPath); } if (test.useSVG) { searches.push('__RENDERER__=svg'); @@ -367,8 +502,6 @@ const app = new Vue({ this.runConfig.expectedVersion = runResult.expectedVersion; this.runConfig.actualVersion = runResult.actualVersion; // TODO - this.runConfig.isExpectedNightly = runResult.expectedVersion.includes('-dev.'); - this.runConfig.isActualNightly = runResult.actualVersion.includes('-dev.'); this.runConfig.renderer = runResult.renderer; this.runConfig.useCoarsePointer = runResult.useCoarsePointer; @@ -397,6 +530,17 @@ const app = new Vue({ open(url, target) { window.open(url, target); + }, + + async fetchBranchVersions() { + try { + const response = await fetch('https://api.github.com/repos/apache/echarts/branches?per_page=100'); + const branches = await response.json(); + this.branchVersions = branches.map(branch => branch.name); + } catch (error) { + console.error('Failed to fetch branches:', error); + this.branchVersions = []; + } } } }); @@ -419,7 +563,9 @@ function runTests(tests, noHeadless) { app.running = true; socket.emit('run', { tests, + expectedSource: app.runConfig.expectedSource, expectedVersion: app.runConfig.expectedVersion, + actualSource: app.runConfig.actualSource, actualVersion: app.runConfig.actualVersion, threads: app.runConfig.threads, renderer: app.runConfig.renderer, @@ -498,11 +644,19 @@ function updateUrl() { // Only update url when version is changed. app.$watch('runConfig', (newVal, oldVal) => { - if (!app.pageInvisible) { + if (!app.pageInvisible && !handlingSourceChange) { socket.emit('syncRunConfig', { runConfig: app.runConfig, - // Override server config from URL. forceSet: true + }, err => { + if (err) { + app.$notify({ + title: 'Error', + message: err, + type: 'error', + duration: 5000 + }); + } }); } }, { deep: true }); \ No newline at end of file diff --git a/test/runTest/client/index.html b/test/runTest/client/index.html index df8cdc9a78..f5270a90a5 100644 --- a/test/runTest/client/index.html +++ b/test/runTest/client/index.html @@ -1,4 +1,3 @@ - + Actual - - - - + + + + + + +
Renderer diff --git a/test/runTest/compareScreenshot.js b/test/runTest/compareScreenshot.js index f4c1627c39..769c1b739e 100644 --- a/test/runTest/compareScreenshot.js +++ b/test/runTest/compareScreenshot.js @@ -22,7 +22,7 @@ const pixelmatch = require('pixelmatch'); const fs = require('fs'); function readPNG(path) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { fs.createReadStream(path) .pipe(new PNG()) .on('parsed', function () { @@ -31,7 +31,8 @@ function readPNG(path) { width: this.width, height: this.height }); - }); + }) + .on('error', (err) => reject(`failed to read PNG from '${path}'\n` + err)); }); } @@ -46,7 +47,11 @@ module.exports = function (expectedShotPath, actualShotPath, threshold = 0.01) { (width !== actualImg.width) || (height !== actualImg.height) ) { - throw new Error('Image size not match'); + throw new Error( + 'Image size not match. ' + + ' expect: ' + width + 'x' + height + + ' actual: ' + actualImg.width + 'x' + actualImg.height + ); } const diffPNG = new PNG({width, height}); let diffPixelsCount = pixelmatch(expectedImg.data, actualImg.data, diffPNG.data, width, height, {threshold}); diff --git a/test/runTest/package-lock.json b/test/runTest/package-lock.json index cd221ad51a..e33450bd8c 100644 --- a/test/runTest/package-lock.json +++ b/test/runTest/package-lock.json @@ -10,12 +10,73 @@ "devDependencies": { "cwebp-bin": "^6.1.1", "glob": "7.0.0", - "pixelmatch": "5.0.2", - "pngjs": "3.4.0", - "puppeteer": "^9.1.1", - "serve-handler": "6.1.1", - "slugify": "^1.3.4", - "socket.io": "2.5.0" + "pixelmatch": "<6.0.0", + "pngjs": "7.0.0", + "puppeteer": "^23.10.0", + "serve-handler": "^6.1.6", + "slugify": "^1.6.6", + "socket.io": "^2.5.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@sindresorhus/is": { @@ -27,18 +88,30 @@ "node": ">=4" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", - "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "dev": true, - "optional": true + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~6.20.0" + } }, "node_modules/@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -64,15 +137,42 @@ "dev": true }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/arch": { @@ -116,12 +216,39 @@ "node": ">=4" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -134,6 +261,57 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.4.2.tgz", + "integrity": "sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.20.0" + } + }, "node_modules/base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", @@ -172,6 +350,16 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bin-build": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", @@ -229,10 +417,11 @@ } }, "node_modules/bin-version/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -432,17 +621,6 @@ "node": ">=4" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -556,6 +734,16 @@ "node": ">=0.10.0" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", @@ -571,11 +759,35 @@ "node": ">=4" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "node_modules/chromium-bidi": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } }, "node_modules/clone-response": { "version": "1.0.2", @@ -586,6 +798,26 @@ "mimic-response": "^1.0.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -653,6 +885,33 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -684,13 +943,24 @@ "url": "https://github.com/imagemin/cwebp-bin?sponsor=1" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -913,11 +1183,27 @@ "node": ">=0.10.0" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/download": { "version": "6.2.5", @@ -965,6 +1251,13 @@ "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -975,27 +1268,29 @@ } }, "node_modules/engine.io": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.1.tgz", - "integrity": "sha512-dfs8EVg/i7QjFsXxn7cCRQ+Wai1G1TlEvHhdYEi80fxn5R1vZ2K661O6v/rezj1FP234SZ14r9CmJke99iYDGg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.2.tgz", + "integrity": "sha512-C4JjGQZLY3kWlIDx0BQNKizbrfpb7NahxDztGdN5jrPK2ghmXiNDN+E/t0JzDeNRZxPVaszxEng42Pmj27X/0w==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", "debug": "~4.1.0", "engine.io-parser": "~2.2.0", - "ws": "~7.4.2" + "ws": "~7.5.10" }, "engines": { "node": ">=8.0.0" } }, "node_modules/engine.io-client": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz", - "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.4.tgz", + "integrity": "sha512-ydc8uuMMDxC5KCKNJN3zZKYJk2sgyTuTZQ7Aj1DJSsLKAcizA/PzWivw8fZMIjJVBo2CJOYzntv4FSjY/Lr//g==", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "~1.3.0", "component-inherit": "0.0.3", @@ -1005,7 +1300,7 @@ "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~7.4.2", + "ws": "~7.5.10", "xmlhttprequest-ssl": "~1.6.2", "yeast": "0.1.2" } @@ -1048,6 +1343,36 @@ "ms": "^2.1.1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1057,6 +1382,62 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -1135,6 +1516,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -1150,14 +1532,12 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true, - "dependencies": { - "punycode": "^1.3.2" - } + "license": "MIT" }, "node_modules/fd-slicer": { "version": "1.1.0", @@ -1200,19 +1580,6 @@ "node": ">=4" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-versions": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", @@ -1277,11 +1644,15 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, "node_modules/get-proxy": { "version": "2.1.0", @@ -1300,6 +1671,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -1310,6 +1682,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.0.tgz", @@ -1408,17 +1795,32 @@ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/ieee754": { @@ -1441,6 +1843,23 @@ } ] }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", @@ -1491,6 +1910,37 @@ "node": ">=4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -1558,13 +2008,47 @@ "node": ">= 4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" }, - "node_modules/keyv": { + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", @@ -1573,17 +2057,12 @@ "json-buffer": "3.0.0" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/lowercase-keys": { "version": "1.0.1", @@ -1667,17 +2146,19 @@ "node": "*" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", @@ -1688,32 +2169,22 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", @@ -1852,33 +2323,6 @@ "node": ">=4" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-map-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", @@ -1912,15 +2356,72 @@ "node": ">=4" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseqs": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", @@ -1933,15 +2434,6 @@ "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", "dev": true }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1967,10 +2459,11 @@ } }, "node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", - "dev": true + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" }, "node_modules/pend": { "version": "1.2.0", @@ -1978,6 +2471,13 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -2009,36 +2509,36 @@ } }, "node_modules/pixelmatch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.0.2.tgz", - "integrity": "sha512-b65UpTI40rGFY8QwN6IYuCbpmwAOL6M8d6voX4F3zR99UmDqh7r2QWLxoeHOazBRgEmDUdqNVESDREqFxQS7rQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", "dev": true, + "license": "ISC", "dependencies": { - "pngjs": "^3.4.0" + "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.13.0" } }, "node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=14.19.0" } }, "node_modules/prepend-http": { @@ -2061,6 +2561,7 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2071,11 +2572,42 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pseudomap": { "version": "1.0.2", @@ -2093,34 +2625,66 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, "node_modules/puppeteer": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", - "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "version": "23.10.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.10.0.tgz", + "integrity": "sha512-vLpEvUM8POKBX4j6y/yyD4oGRhS1oBAbMn3lYpz1yeakhRsA8IhF5QmjXwAIQYGdR/INxyFiY6kQDoUtLGDP3g==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.5.0", + "chromium-bidi": "0.8.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.10.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "23.10.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.10.0.tgz", + "integrity": "sha512-7pv6kFget4Iki0RLBDowi35vaccz73XpC6/FAnfCrYa2IXVUlBHfZw+HGWzlvvTGwM9HHd32rgCrIDzil3kj+w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "@puppeteer/browsers": "2.5.0", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" }, "engines": { - "node": ">=10.18.1" + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/query-string": { @@ -2137,6 +2701,13 @@ "node": ">=0.10.0" } }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -2146,18 +2717,24 @@ "node": ">= 0.6" } }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, "node_modules/responselike": { @@ -2169,41 +2746,6 @@ "lowercase-keys": "^1.0.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2238,10 +2780,11 @@ } }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver" } @@ -2268,18 +2811,18 @@ } }, "node_modules/serve-handler": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.1.tgz", - "integrity": "sha512-LQPvxGia2TYqyMkHKH4jW9jx6jlQUMcWz6gJavZ3+4vsnB+SaWbYTncb9YsK5YBR6SlvyumREZJAzLw8VaFAUQ==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", - "minimatch": "3.0.4", + "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, @@ -2313,18 +2856,6 @@ "node": ">= 0.6" } }, - "node_modules/serve-handler/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2353,19 +2884,32 @@ "dev": true }, "node_modules/slugify": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz", - "integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=8.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, "node_modules/socket.io": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", - "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.1.tgz", + "integrity": "sha512-eaTE4tBKRD6RFoetquMbxgvcpvoDtRyIlkIMI/SMK2bsKvbENTsDeeu4GJ/z9c90yOWxB7b/eC+yKLPbHnH6bA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "~4.1.0", "engine.io": "~3.6.0", @@ -2416,9 +2960,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", @@ -2466,6 +3010,36 @@ "ms": "^2.1.1" } }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -2490,6 +3064,39 @@ "node": ">=0.10.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/streamx": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.0.tgz", + "integrity": "sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -2499,13 +3106,32 @@ "node": ">=0.10.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/strip-dirs": { @@ -2539,31 +3165,30 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, + "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/temp-dir": { @@ -2588,6 +3213,13 @@ "node": ">=4" } }, + "node_modules/text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2615,12 +3247,6 @@ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", "dev": true }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -2633,6 +3259,13 @@ "node": ">=0.10.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2645,6 +3278,13 @@ "node": "*" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -2655,6 +3295,14 @@ "through": "^2.3.8" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -2676,6 +3324,13 @@ "node": ">= 4" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true, + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2692,22 +3347,6 @@ "uuid": "bin/uuid" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -2720,6 +3359,24 @@ "which": "bin/which" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2727,10 +3384,11 @@ "dev": true }, "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -2765,12 +3423,51 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -2786,26 +3483,86 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==", "dev": true + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { + "@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true + }, + "@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", + "dev": true, + "requires": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", "dev": true }, + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "@types/node": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", - "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "dev": true, - "optional": true + "optional": true, + "requires": { + "undici-types": "~6.20.0" + } }, "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "requires": { @@ -2829,12 +3586,27 @@ "dev": true }, "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "debug": "4" + "debug": "^4.3.4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" } }, "arch": { @@ -2860,12 +3632,33 @@ } } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -2878,6 +3671,52 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "optional": true + }, + "bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "optional": true, + "requires": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "optional": true + }, + "bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "requires": { + "bare-os": "^2.1.0" + } + }, + "bare-stream": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.4.2.tgz", + "integrity": "sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.20.0" + } + }, "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", @@ -2896,6 +3735,12 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, + "basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true + }, "bin-build": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", @@ -2930,9 +3775,9 @@ }, "dependencies": { "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -3107,17 +3952,6 @@ } } }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -3207,6 +4041,12 @@ } } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", @@ -3219,11 +4059,27 @@ "url-to-options": "^1.0.1" } }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "chromium-bidi": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "dev": true, + "requires": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } }, "clone-response": { "version": "1.0.2", @@ -3234,6 +4090,21 @@ "mimic-response": "^1.0.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3295,6 +4166,18 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -3316,13 +4199,19 @@ "bin-wrapper": "^4.0.1" } }, + "data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true + }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decode-uri-component": { @@ -3506,10 +4395,21 @@ } } }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + } + }, "devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", "dev": true }, "download": { @@ -3551,6 +4451,12 @@ "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", "dev": true }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -3561,9 +4467,9 @@ } }, "engine.io": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.1.tgz", - "integrity": "sha512-dfs8EVg/i7QjFsXxn7cCRQ+Wai1G1TlEvHhdYEi80fxn5R1vZ2K661O6v/rezj1FP234SZ14r9CmJke99iYDGg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.2.tgz", + "integrity": "sha512-C4JjGQZLY3kWlIDx0BQNKizbrfpb7NahxDztGdN5jrPK2ghmXiNDN+E/t0JzDeNRZxPVaszxEng42Pmj27X/0w==", "dev": true, "requires": { "accepts": "~1.3.4", @@ -3571,7 +4477,7 @@ "cookie": "~0.4.1", "debug": "~4.1.0", "engine.io-parser": "~2.2.0", - "ws": "~7.4.2" + "ws": "~7.5.10" }, "dependencies": { "debug": { @@ -3586,9 +4492,9 @@ } }, "engine.io-client": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz", - "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.4.tgz", + "integrity": "sha512-ydc8uuMMDxC5KCKNJN3zZKYJk2sgyTuTZQ7Aj1DJSsLKAcizA/PzWivw8fZMIjJVBo2CJOYzntv4FSjY/Lr//g==", "dev": true, "requires": { "component-emitter": "~1.3.0", @@ -3599,7 +4505,7 @@ "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~7.4.2", + "ws": "~7.5.10", "xmlhttprequest-ssl": "~1.6.2", "yeast": "0.1.2" }, @@ -3634,12 +4540,63 @@ "has-binary2": "~1.0.2" } }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -3711,14 +4668,11 @@ "yauzl": "^2.10.0" } }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dev": true, - "requires": { - "punycode": "^1.3.2" - } + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true }, "fd-slicer": { "version": "1.1.0", @@ -3752,16 +4706,6 @@ "trim-repeated": "^1.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "find-versions": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", @@ -3825,10 +4769,10 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-proxy": { @@ -3849,6 +4793,17 @@ "pump": "^3.0.0" } }, + "get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + } + }, "glob": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.0.tgz", @@ -3934,13 +4889,23 @@ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "requires": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" } }, @@ -3950,6 +4915,16 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", @@ -3994,6 +4969,28 @@ "p-is-promise": "^1.1.0" } }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -4046,12 +5043,39 @@ "is-object": "^1.0.1" } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -4061,14 +5085,11 @@ "json-buffer": "3.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "lowercase-keys": { "version": "1.0.1", @@ -4133,16 +5154,16 @@ "brace-expansion": "^1.1.7" } }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "negotiator": { @@ -4151,21 +5172,18 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", @@ -4272,24 +5290,6 @@ "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", "dev": true }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "p-map-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", @@ -4314,11 +5314,52 @@ "p-finally": "^1.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dev": true, + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + } + }, + "pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "requires": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } }, "parseqs": { "version": "0.0.6", @@ -4332,12 +5373,6 @@ "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", "dev": true }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4357,9 +5392,9 @@ "dev": true }, "path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", "dev": true }, "pend": { @@ -4368,6 +5403,12 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -4390,27 +5431,26 @@ } }, "pixelmatch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.0.2.tgz", - "integrity": "sha512-b65UpTI40rGFY8QwN6IYuCbpmwAOL6M8d6voX4F3zR99UmDqh7r2QWLxoeHOazBRgEmDUdqNVESDREqFxQS7rQ==", - "dev": true, - "requires": { - "pngjs": "^3.4.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", "dev": true, "requires": { - "find-up": "^4.0.0" + "pngjs": "^6.0.0" + }, + "dependencies": { + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true + } } }, "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true }, "prepend-http": { @@ -4437,6 +5477,30 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4459,30 +5523,41 @@ "once": "^1.3.1" } }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, "puppeteer": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", - "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "version": "23.10.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.10.0.tgz", + "integrity": "sha512-vLpEvUM8POKBX4j6y/yyD4oGRhS1oBAbMn3lYpz1yeakhRsA8IhF5QmjXwAIQYGdR/INxyFiY6kQDoUtLGDP3g==", "dev": true, "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "@puppeteer/browsers": "2.5.0", + "chromium-bidi": "0.8.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.10.0", + "typed-query-selector": "^2.12.0" + } + }, + "puppeteer-core": { + "version": "23.10.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.10.0.tgz", + "integrity": "sha512-7pv6kFget4Iki0RLBDowi35vaccz73XpC6/FAnfCrYa2IXVUlBHfZw+HGWzlvvTGwM9HHd32rgCrIDzil3kj+w==", + "dev": true, + "requires": { + "@puppeteer/browsers": "2.5.0", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "dependencies": { + "ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "requires": {} + } } }, "query-string": { @@ -4496,22 +5571,29 @@ "strict-uri-encode": "^1.0.0" } }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "dev": true }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true }, "responselike": { "version": "1.0.2", @@ -4522,31 +5604,6 @@ "lowercase-keys": "^1.0.0" } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4563,9 +5620,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "semver-regex": { @@ -4584,18 +5641,17 @@ } }, "serve-handler": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.1.tgz", - "integrity": "sha512-LQPvxGia2TYqyMkHKH4jW9jx6jlQUMcWz6gJavZ3+4vsnB+SaWbYTncb9YsK5YBR6SlvyumREZJAzLw8VaFAUQ==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", "dev": true, "requires": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", - "minimatch": "3.0.4", + "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" }, "dependencies": { @@ -4619,15 +5675,6 @@ "requires": { "mime-db": "~1.33.0" } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } } } }, @@ -4653,15 +5700,21 @@ "dev": true }, "slugify": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz", - "integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true }, "socket.io": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", - "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.1.tgz", + "integrity": "sha512-eaTE4tBKRD6RFoetquMbxgvcpvoDtRyIlkIMI/SMK2bsKvbENTsDeeu4GJ/z9c90yOWxB7b/eC+yKLPbHnH6bA==", "dev": true, "requires": { "debug": "~4.1.0", @@ -4724,9 +5777,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", @@ -4764,6 +5817,27 @@ } } }, + "socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "requires": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, "sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -4782,19 +5856,55 @@ "sort-keys": "^1.0.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "streamx": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.0.tgz", + "integrity": "sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg==", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + } + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", "dev": true }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "safe-buffer": "~5.2.0" + "ansi-regex": "^5.0.1" } }, "strip-dirs": { @@ -4822,28 +5932,26 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "temp-dir": { @@ -4862,6 +5970,12 @@ "uuid": "^3.0.1" } }, + "text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4886,12 +6000,6 @@ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", "dev": true }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -4901,6 +6009,12 @@ "escape-string-regexp": "^1.0.2" } }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -4910,6 +6024,12 @@ "safe-buffer": "^5.0.1" } }, + "typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -4920,6 +6040,13 @@ "through": "^2.3.8" } }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "optional": true + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -4935,6 +6062,12 @@ "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", "dev": true }, + "urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4947,22 +6080,6 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -4972,6 +6089,17 @@ "isexe": "^2.0.0" } }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4979,9 +6107,9 @@ "dev": true }, "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "requires": {} }, @@ -4997,12 +6125,39 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -5018,6 +6173,12 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==", "dev": true + }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true } } } diff --git a/test/runTest/package.json b/test/runTest/package.json index 078ca82914..43095a6fe7 100644 --- a/test/runTest/package.json +++ b/test/runTest/package.json @@ -5,12 +5,12 @@ "devDependencies": { "cwebp-bin": "^6.1.1", "glob": "7.0.0", - "pixelmatch": "5.0.2", - "pngjs": "3.4.0", - "puppeteer": "^9.1.1", - "serve-handler": "6.1.1", - "slugify": "^1.3.4", - "socket.io": "2.5.0" + "pixelmatch": "<6.0.0", + "pngjs": "7.0.0", + "puppeteer": "^23.10.0", + "serve-handler": "^6.1.6", + "slugify": "^1.6.6", + "socket.io": "^2.5.1" }, "scripts": { "start": "npm run test:visual", diff --git a/test/runTest/recorder/index.html b/test/runTest/recorder/index.html index d344b1018d..40adf2793d 100644 --- a/test/runTest/recorder/index.html +++ b/test/runTest/recorder/index.html @@ -61,7 +61,7 @@
{{action.name}}
- +
{ scrollX: {{action.scrollX}}, scrollY: {{action.scrollY}} }
{{op}}
diff --git a/test/runTest/recorder/recorder.css b/test/runTest/recorder/recorder.css index 9e6c8cce71..3e2e81f4a0 100644 --- a/test/runTest/recorder/recorder.css +++ b/test/runTest/recorder/recorder.css @@ -189,6 +189,11 @@ iframe { margin-left: 10px; } +.action-popover { + max-height: 90%; + overflow: auto; +} + ::-webkit-scrollbar { height: 8px; width: 8px; diff --git a/test/runTest/recorder/recorder.js b/test/runTest/recorder/recorder.js index 66f18dee73..e4289563e0 100644 --- a/test/runTest/recorder/recorder.js +++ b/test/runTest/recorder/recorder.js @@ -40,7 +40,7 @@ function getUniqueSelector(el) { let selector = ''; if (el.id) { // id has highest priority. - return el.id; + return '#' + el.id; } else { selector = el.tagName.toLowerCase(); diff --git a/test/runTest/runtime/ActionPlayback.js b/test/runTest/runtime/ActionPlayback.js index aa50caa676..cad29497c5 100644 --- a/test/runTest/runtime/ActionPlayback.js +++ b/test/runTest/runtime/ActionPlayback.js @@ -37,6 +37,8 @@ export class ActionPlayback { this._currentOpIndex = 0; this._isLastOpMousewheel = false; + + this._isMouseDown = false; } getContext() { @@ -52,6 +54,7 @@ export class ActionPlayback { this._current = Date.now(); this._elapsedTime = 0; this._isLastOpMousewheel = false; + this._isMouseDown = false; } _restoreContext(ctx) { @@ -103,7 +106,7 @@ export class ActionPlayback { try { // Execute all if there are multiple ops in one frame. do { - const executed = await self._update(takeScreenshot); + const executed = await self._update(takeScreenshot, action.name); if (!executed) { break; } @@ -117,6 +120,14 @@ export class ActionPlayback { if (self._currentOpIndex >= self._ops.length) { // Finished + if (self._isMouseDown) { + reject(new Error( + action.name + ' has finished, but the page remains in mousedown state.' + + ' A mouseup is needed; otherwise the subsequent test case may be affected.' + + ' Please re-record this test case.' + )); + return; + } resolve(); } else { @@ -135,7 +146,7 @@ export class ActionPlayback { } } - async _update(takeScreenshot) { + async _update(takeScreenshot, actionName) { let op = this._ops[this._currentOpIndex]; if (!op || (op.time > this._elapsedTime)) { @@ -144,18 +155,23 @@ export class ActionPlayback { } let screenshotTaken = false; + let errMsgPart; switch (op.type) { case 'mousedown': + errMsgPart = actionName + ' ' + op.type + ' (' + op.x + ',' + op.y + ')'; // Pause timeline to avoid frame not sync. timeline.pause(); await __VRT_MOUSE_MOVE__(op.x, op.y); - await __VRT_MOUSE_DOWN__(); + await __VRT_MOUSE_DOWN__(errMsgPart); + this._isMouseDown = true; timeline.resume(); break; case 'mouseup': + errMsgPart = actionName + ' ' + op.type + ' (' + op.x + ',' + op.y + ')'; timeline.pause(); await __VRT_MOUSE_MOVE__(op.x, op.y); - await __VRT_MOUSE_UP__(); + await __VRT_MOUSE_UP__(errMsgPart); + this._isMouseDown = false; if (window.__VRT_RELOAD_TRIGGERED__) { return; } @@ -187,6 +203,12 @@ export class ActionPlayback { break; case 'valuechange': const selector = document.querySelector(op.selector); + if (!selector) { + throw new Error( + '[Test Case Error] (' + actionName + ') Selector not found: ' + op.selector + + '. It may be caused by test case changes.' + ); + } selector.value = op.value; // changing value via js won't trigger `change` event, so trigger it manually selector.dispatchEvent(new Event('change')); diff --git a/test/runTest/runtime/main.js b/test/runTest/runtime/main.js index 633911d327..c83bd66407 100644 --- a/test/runTest/runtime/main.js +++ b/test/runTest/runtime/main.js @@ -103,7 +103,16 @@ window.__VRT_RUN_ACTIONS__ = async function (actions, restoredActionIndex, resto continue; } window.scrollTo(action.scrollX, action.scrollY); - await actionPlayback.runAction(action, index === restoredActionIndex ? restoredActionContext : null); + try { + await actionPlayback.runAction(action, index === restoredActionIndex ? restoredActionContext : null); + } + catch (err) { + // Any error in this JS task must be handled; otherwise __VRT_FINISH_ACTIONS__ + // can not be called and the entire test execution will be blocked. + const errStr = errToStr(err); + // console.error(errStr); + __VRT_LOG_ERRORS__(errStr); + } } actionPlayback.stop(); @@ -111,6 +120,19 @@ window.__VRT_RUN_ACTIONS__ = async function (actions, restoredActionIndex, resto __VRT_FINISH_ACTIONS__(); } +function errToStr(err) { + if (typeof err === 'string') { + return err; + } + if (err && err.message != null) { + return err.message + (err.stack ? ' ' + err.stack : ''); + } + if (err && err.toString) { + return err.toString(); + } + return '[error] ' + err; +} + window.addEventListener('DOMContentLoaded', () => { let style = document.createElement('style'); diff --git a/test/runTest/server.js b/test/runTest/server.js index ce701f785a..a6f7e29337 100644 --- a/test/runTest/server.js +++ b/test/runTest/server.js @@ -25,6 +25,8 @@ process.on('uncaughtException', (err) => { console.log(); console.error(`Please run \`${chalk.yellow('npm install')}\` in \`${chalk.green('test/runTest')}\` folder first!`); console.log(); + } else { + console.error('Uncaught err:', err); } process.exit(1); }); @@ -46,17 +48,23 @@ const { getAllTestsRuns, delTestsRun, RESULTS_ROOT_DIR, - checkStoreVersion + checkStoreVersion, + clearStaledResults } = require('./store'); -const {prepareEChartsLib, getActionsFullPath, fetchVersions} = require('./util'); +const {prepareEChartsLib, getActionsFullPath, fetchVersions, cleanBranchDirectory, fetchRecentPRs} = require('./util'); const fse = require('fs-extra'); const fs = require('fs'); const open = require('open'); const genReport = require('./genReport'); +const useCNMirror = process.argv.includes('--useCNMirror') || !!process.env.USE_CN_MIRROR; + +console.info(chalk.green('useCNMirror:'), useCNMirror); const CLI_FIXED_THREADS_COUNT = 1; function serve() { + clearStaledResults(); + const server = http.createServer((request, response) => { return handler(request, response, { cleanUrls: false, @@ -139,7 +147,9 @@ function startTests(testsNameList, socket, { noHeadless, threadsCount, replaySpeed, + actualSource, actualVersion, + expectedSource, expectedVersion, renderer, useCoarsePointer, @@ -201,6 +211,8 @@ function startTests(testsNameList, socket, { '--speed', replaySpeed || 5, '--actual', actualVersion, '--expected', expectedVersion, + '--actual-source', actualSource, + '--expected-source', expectedSource, '--renderer', renderer || '', '--use-coarse-pointer', useCoarsePointer, '--threads', Math.min(threadsCount, CLI_FIXED_THREADS_COUNT), @@ -225,12 +237,25 @@ function checkPuppeteer() { async function start() { + // Clean PR directories before starting + const {cleanPRDirectories} = require('./util'); + cleanPRDirectories(); + if (!checkPuppeteer()) { - // TODO Check version. console.error(`Can't find puppeteer >= 9.0.0, run 'npm install' to update in the 'test/runTest' folder`); return; } + let stableVersions; + let nightlyVersions; + try { + stableVersions = await fetchVersions(false, useCNMirror); + nightlyVersions = (await fetchVersions(true, useCNMirror)).slice(0, 100); + } catch (e) { + console.error('Failed to fetch version list:', e); + console.log(`Try again later or try the CN mirror with: ${chalk.yellow('npm run test:visual -- -- --useCNMirror')}`) + return; + } let _currentTestHash; let _currentRunConfig; @@ -241,11 +266,6 @@ async function start() { // Start a static server for puppeteer open the html test cases. let {io} = serve(); - const stableVersions = await fetchVersions(false); - const nightlyVersions = (await fetchVersions(true)).slice(0, 100); - stableVersions.unshift('local'); - nightlyVersions.unshift('local'); - io.of('/client').on('connect', async socket => { let isAborted = false; @@ -271,20 +291,10 @@ async function start() { return; } - const expectedVersionsList = _currentRunConfig.isExpectedNightly ? nightlyVersions : stableVersions; - const actualVersionsList = _currentRunConfig.isActualNightly ? nightlyVersions : stableVersions; - if (!expectedVersionsList.includes(_currentRunConfig.expectedVersion)) { - // Pick first version not local - _currentRunConfig.expectedVersion = expectedVersionsList[1]; - } - if (!actualVersionsList.includes(_currentRunConfig.actualVersion)) { - _currentRunConfig.actualVersion = 'local'; - } - socket.emit('syncRunConfig_return', { runConfig: _currentRunConfig, - expectedVersionsList, - actualVersionsList + versions: stableVersions, + nightlyVersions: nightlyVersions }); if (_currentTestHash !== getRunHash(_currentRunConfig)) { @@ -308,6 +318,7 @@ async function start() { }); socket.on('genTestsRunReport', async (params) => { + console.log('genTestsRunReport', params); const absPath = await genReport( path.join(RESULTS_ROOT_DIR, getRunHash(params)) ); @@ -328,8 +339,18 @@ async function start() { let startTime = Date.now(); - await prepareEChartsLib(data.expectedVersion); // Expected version. - await prepareEChartsLib(data.actualVersion); // Version to test + try { + await prepareEChartsLib(data.expectedSource, data.expectedVersion, useCNMirror); + await prepareEChartsLib(data.actualSource, data.actualVersion, useCNMirror); + } + catch (e) { + console.error(e); + // Send error to client + socket.emit('run_error', { + message: e.toString() + }); + return; + } // If aborted in the time downloading lib. if (isAborted) { @@ -337,30 +358,27 @@ async function start() { } // TODO Should broadcast to all sockets. - try { - if (!checkStoreVersion(data)) { - throw new Error('Unmatched store version and run version.'); - } - - await startTests( - data.tests, - io.of('/client'), - { - noHeadless: data.noHeadless, - threadsCount: data.threads, - replaySpeed: data.replaySpeed, - actualVersion: data.actualVersion, - expectedVersion: data.expectedVersion, - renderer: data.renderer, - useCoarsePointer: data.useCoarsePointer, - noSave: false - } - ); - } - catch (e) { - console.error(e); + if (!checkStoreVersion(data)) { + throw new Error('Unmatched store version and run version.'); } + await startTests( + data.tests, + io.of('/client'), + { + noHeadless: data.noHeadless, + threadsCount: data.threads, + replaySpeed: data.replaySpeed, + actualSource: data.actualSource, + actualVersion: data.actualVersion, + expectedSource: data.expectedSource, + expectedVersion: data.expectedVersion, + renderer: data.renderer, + useCoarsePointer: data.useCoarsePointer, + noSave: false + } + ); + if (!isAborted) { const deltaTime = Date.now() - startTime; console.log('Finished in ', Math.round(deltaTime / 1000) + ' second'); diff --git a/test/runTest/store.js b/test/runTest/store.js index 3430bd47e8..21d1d2d49e 100644 --- a/test/runTest/store.js +++ b/test/runTest/store.js @@ -77,9 +77,19 @@ class Test { * It depends on two versions and rendering mode. */ function getRunHash(params) { + // Replace # with PR- in the hash to avoid URL issues + const expectedVersion = params.expectedSource === 'PR' + ? params.expectedVersion.replace('#', 'PR-') + : params.expectedVersion; + const actualVersion = params.actualSource === 'PR' + ? params.actualVersion.replace('#', 'PR-') + : params.actualVersion; + return [ - params.expectedVersion, - params.actualVersion, + params.expectedSource, + expectedVersion, + params.actualSource, + actualVersion, params.renderer, params.useCoarsePointer ].join(TEST_HASH_SPLITTER); @@ -90,11 +100,21 @@ function getRunHash(params) { */ function parseRunHash(str) { const parts = str.split(TEST_HASH_SPLITTER); + // Convert back PR-123 to #123 for PR versions + const expectedVersion = parts[0] === 'PR' + ? parts[1].replace('PR-', '#') + : parts[1]; + const actualVersion = parts[2] === 'PR' + ? parts[3].replace('PR-', '#') + : parts[3]; + return { - expectedVersion: parts[0], - actualVersion: parts[1], - renderer: parts[2], - useCoarsePointer: parts[3] + expectedSource: parts[0], + expectedVersion: expectedVersion, + actualSource: parts[2], + actualVersion: actualVersion, + renderer: parts[4], + useCoarsePointer: parts[5] }; } @@ -102,6 +122,22 @@ function getResultBaseDir() { return path.join(RESULTS_ROOT_DIR, _runHash); } +module.exports.clearStaledResults = async function () { + // If split by __ and there is no 6 parts, it is staled. + try { + const dirs = await globby('*', { cwd: RESULTS_ROOT_DIR, onlyDirectories: true }); + for (let dir of dirs) { + const parts = dir.split(TEST_HASH_SPLITTER); + if (parts.length !== 6) { + await module.exports.delTestsRun(dir); + } + } + } + catch(e) { + console.error('Failed to clear staled results', e); + } +} + module.exports.getResultBaseDir = getResultBaseDir; module.exports.getRunHash = getRunHash; @@ -111,7 +147,9 @@ module.exports.getRunHash = getRunHash; module.exports.checkStoreVersion = function (runParams) { const storeParams = parseRunHash(_runHash); console.log('Store ', _runHash); - return storeParams.expectedVersion === runParams.expectedVersion + return storeParams.expectedSource === runParams.expectedSource + && storeParams.expectedVersion === runParams.expectedVersion + && storeParams.actualSource === runParams.actualSource && storeParams.actualVersion === runParams.actualVersion && storeParams.renderer === runParams.renderer && storeParams.useCoarsePointer === runParams.useCoarsePointer; @@ -301,14 +339,22 @@ module.exports.getAllTestsRuns = async function () { continue; } - params.lastRunTime = lastRunTime > 0 ? formatDate(lastRunTime) : 'N/A'; - params.total = total; - params.passed = passedCount; - params.finished = finishedCount; - params.id = dir; - params.diskSize = convertBytes(await getFolderSize(path.join(RESULTS_ROOT_DIR, dir))); - - results.push(params); + const runData = { + expectedSource: params.expectedSource, + expectedVersion: params.expectedVersion, + actualSource: params.actualSource, + actualVersion: params.actualVersion, + renderer: params.renderer, + useCoarsePointer: params.useCoarsePointer, + lastRunTime: lastRunTime > 0 ? formatDate(lastRunTime) : 'N/A', + total: total, + passed: passedCount, + finished: finishedCount, + id: dir, + diskSize: convertBytes(await getFolderSize(path.join(RESULTS_ROOT_DIR, dir))) + }; + + results.push(runData); }; return results; } diff --git a/test/runTest/util.js b/test/runTest/util.js index 4d892b3443..751b6a1bb3 100644 --- a/test/runTest/util.js +++ b/test/runTest/util.js @@ -42,10 +42,15 @@ module.exports.fileNameFromTest = function (testName) { return testName + '.html'; }; -function getVersionDir(version) { +function getVersionDir(source, version) { version = version || 'local'; + if (source === 'PR') { + // For PR preview artifacts + const prNumber = version.replace(/^#/, ''); + return `tmp/__version__/pr-${prNumber}`; + } return `tmp/__version__/${version}`; -}; +} module.exports.getVersionDir = getVersionDir; module.exports.getActionsFullPath = function (testName) { @@ -56,58 +61,97 @@ module.exports.getEChartsTestFileName = function () { return `echarts.test-${config.testVersion}.js`; }; -module.exports.prepareEChartsLib = function (version) { +// Clean PR directories at the start of initing because PR code may change +module.exports.cleanPRDirectories = function () { + const baseDir = path.join(__dirname, 'tmp/__version__'); + if (fs.existsSync(baseDir)) { + const dirs = fs.readdirSync(baseDir); + dirs.forEach(dir => { + if (dir.startsWith('pr-')) { + fse.removeSync(path.join(baseDir, dir)); + } + }); + } +} + +module.exports.prepareEChartsLib = function (source, version, useCNMirror) { + console.log(`Preparing ECharts lib: ${source} ${version}`); - const versionFolder = path.join(__dirname, getVersionDir(version)); + const versionFolder = path.join(__dirname, getVersionDir(source, version)); const ecDownloadPath = `${versionFolder}/echarts.js`; + const testLibPath = `${versionFolder}/${module.exports.getEChartsTestFileName()}`; + fse.ensureDirSync(versionFolder); + if (!version || version === 'local') { // Developing version, make sure it's new build - fse.copySync(path.join(__dirname, '../../dist/echarts.js'), `${versionFolder}/echarts.js`); + fse.copySync(path.join(__dirname, '../../dist/echarts.js'), ecDownloadPath); let code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8')); - fs.writeFileSync(`${versionFolder}/${module.exports.getEChartsTestFileName()}`, code, 'utf-8'); - + fs.writeFileSync(testLibPath, code, 'utf-8'); return Promise.resolve(); } - return new Promise(resolve => { - const testLibPath = `${versionFolder}/${module.exports.getEChartsTestFileName()}`; - if (!fs.existsSync(ecDownloadPath)) { - const file = fs.createWriteStream(ecDownloadPath); - const isNightly = version.includes('-dev'); - const packageName = isNightly ? 'echarts-nightly' : 'echarts' - - const url = `https://unpkg.com/${packageName}@${version}/dist/echarts.js`; - console.log(`Downloading ${packageName}@${version} from ${url}`, ); - https.get(url, response => { - response.pipe(file); - - file.on('finish', () => { - let code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8')); - fs.writeFileSync(testLibPath, code, 'utf-8'); - resolve(); - }); - }); + else if (fs.existsSync(ecDownloadPath) && fs.existsSync(testLibPath) && + fs.statSync(ecDownloadPath).size > 0 && fs.statSync(testLibPath).size > 0) { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + let url; + + if (source === 'PR') { + const prNumber = version.replace(/^#/, ''); + if (!/^\d+$/.test(prNumber)) { + reject('Invalid PR number format. Should be #123'); + return; + } + url = `https://echarts-pr-${prNumber}.surge.sh/dist/echarts.js`; } else { - // Always do code modifaction. - // In case we need to do replacement on old downloads. - let code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8')); - fs.writeFileSync(testLibPath, code, 'utf-8'); - resolve(); + const isNightly = source === 'nightly'; + const packageName = isNightly ? 'echarts-nightly' : 'echarts'; + url = useCNMirror + ? `https://registry.npmmirror.com/${packageName}/${version}/files/dist/echarts.js` + : `https://unpkg.com/${packageName}@${version}/dist/echarts.js`; } + + console.log(`Downloading ECharts from ${url}`); + https.get(url, response => { + if (response.statusCode === 404) { + reject(`PR artifact doesn't exist at ${url}. Make sure the PR build is complete.`); + return; + } + + let data = ''; + response.on('data', chunk => { + data += chunk; + }); + + response.on('end', () => { + if (!data) { + reject(`Downloaded file is empty from ${url}`); + return; + } + fs.writeFileSync(ecDownloadPath, data, 'utf-8'); + const code = modifyEChartsCode(data); + fs.writeFileSync(testLibPath, code, 'utf-8'); + resolve(); + }); + }).on('error', (e) => { + reject(`Failed to download from ${url}: ${e}`); + }); }); }; -module.exports.fetchVersions = function (isNighlty) { +module.exports.fetchVersions = function (isNightly, useCNMirror) { return new Promise((resolve, reject) => { - https.get( - isNighlty - ? `https://registry.npmjs.org/echarts-nightly` - : `https://registry.npmjs.org/echarts` - , res => { + const npmRegistry = useCNMirror + ? 'https://registry.npmmirror.com' + : 'https://registry.npmjs.org'; + const endpoint = `${npmRegistry}/echarts${isNightly ? '-nightly' : ''}`; + https.get(endpoint, res => { if (res.statusCode !== 200) { res.destroy(); - reject('Failed fetch versions from https://registry.npmjs.org/echarts'); + reject('status code: ' + res.statusCode); return; } var buffers = []; @@ -118,10 +162,11 @@ module.exports.fetchVersions = function (isNighlty) { resolve(Object.keys(JSON.parse(data).versions).reverse()); } catch (e) { - reject(e.toString()); + reject(e); } }); - }); + }) + .on('error', reject); }); }; @@ -156,4 +201,4 @@ module.exports.waitTime = function (time) { resolve(); }, time); }); -}; \ No newline at end of file +}; From 8ddd15d320bbab70e22b0dd87d725bb5160fa1c8 Mon Sep 17 00:00:00 2001 From: 100pah Date: Tue, 27 May 2025 04:31:04 +0800 Subject: [PATCH 23/49] feat(visualMap): support specify `seriesId` on visualMap option; previously only seriesIndex can be specified, not convenient and not consistent with other components. --- src/component/visualMap/VisualMapModel.ts | 45 ++++++++++++++--------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/component/visualMap/VisualMapModel.ts b/src/component/visualMap/VisualMapModel.ts index 85b3162e51..74c791694c 100644 --- a/src/component/visualMap/VisualMapModel.ts +++ b/src/component/visualMap/VisualMapModel.ts @@ -32,7 +32,8 @@ import { BorderOptionMixin, OptionDataValue, BuiltinVisualProperty, - DimensionIndex + DimensionIndex, + OptionId } from '../../util/types'; import ComponentModel from '../../model/Component'; import Model from '../../model/Model'; @@ -67,12 +68,13 @@ export interface VisualMapOption /** * 'all' or null/undefined: all series. * A number or an array of number: the specified series. - * set min: 0, max: 200, only for campatible with ec2. - * In fact min max should not have default value. */ seriesIndex?: 'all' | number[] | number + seriesId?: OptionId | OptionId[] /** + * set min: 0, max: 200, only for campatible with ec2. + * In fact min max should not have default value. * min value, must specified if pieces is not specified. */ min?: number @@ -237,23 +239,30 @@ class VisualMapModel extends Com } /** - * @protected - * @return {Array.} An array of series indices. + * @return An array of series indices. */ - getTargetSeriesIndices() { - const optionSeriesIndex = this.option.seriesIndex; - let seriesIndices: number[] = []; - - if (optionSeriesIndex == null || optionSeriesIndex === 'all') { - this.ecModel.eachSeries(function (seriesModel, index) { - seriesIndices.push(index); - }); - } - else { - seriesIndices = modelUtil.normalizeToArray(optionSeriesIndex); + protected getTargetSeriesIndices(): number[] { + const optionSeriesId = this.option.seriesId; + let optionSeriesIndex = this.option.seriesIndex; + if (optionSeriesIndex == null && optionSeriesId == null) { + optionSeriesIndex = 'all'; } - return seriesIndices; + const seriesModels = modelUtil.queryReferringComponents( + this.ecModel, + 'series', + { + index: optionSeriesIndex, + id: optionSeriesId, + }, + { + useDefault: false, + enableAll: true, + enableNone: false, + } + ).models; + + return zrUtil.map(seriesModels, seriesModel => seriesModel.componentIndex); } /** @@ -605,7 +614,7 @@ class VisualMapModel extends Com // zlevel: 0, z: 4, - seriesIndex: 'all', + // seriesIndex: 'all', min: 0, max: 200, From 6beeef2f0ef91d6e51e0d8ab3248bd3f4b35f74a Mon Sep 17 00:00:00 2001 From: 100pah Date: Sat, 31 May 2025 02:12:15 +0800 Subject: [PATCH 24/49] feat(matrix & calendar): (1). Support matrix col/row size option. (2). Support corner content options in matrix. (3) Support body content options. (4). Support matrix `mergeCells`. (5). Fix and refactor the matrix style options, conserning their inheritance and z-order and flexibility. (6). Support declaratively layout other components (including grid/polar/geo/graph coord sys, and dataZoom/visualMap/legend/... components) based on `matrix`/`calendar`. And and enhance the echarts API `convertToLayout`/`convertToPixel`, custom series API `api.coord`/`api.coord` to support this feature comprehansively. Considering the previous "pie layout on coord sys", introduce the `boxCooridnateSystem` and `createBoxLayoutReference` to all of the components and echarts for the overall coord sys infrastructure. (7). Add some test cases to matrix and calendar. --- src/chart/bar/BarSeries.ts | 10 +- src/chart/bar/PictorialBarSeries.ts | 5 +- src/chart/custom/CustomSeries.ts | 25 +- src/chart/custom/CustomView.ts | 2 + src/chart/funnel/FunnelSeries.ts | 1 + src/chart/funnel/funnelLayout.ts | 13 +- src/chart/graph/createView.ts | 23 +- src/chart/heatmap/HeatmapSeries.ts | 8 +- src/chart/heatmap/HeatmapView.ts | 28 +- src/chart/helper/createGraphFromNodeEdge.ts | 2 +- src/chart/helper/createSeriesData.ts | 10 + src/chart/map/MapSeries.ts | 14 +- src/chart/pie/PieSeries.ts | 17 +- src/chart/pie/PieView.ts | 6 +- src/chart/pie/labelLayout.ts | 6 +- src/chart/pie/pieLayout.ts | 84 +- src/chart/scatter/ScatterSeries.ts | 2 +- src/component/axisPointer/modelHelper.ts | 1 + src/component/calendar/CalendarView.ts | 8 +- src/component/dataZoom/SliderZoomView.ts | 9 +- src/component/helper/listComponent.ts | 54 +- src/component/legend/LegendView.ts | 15 +- src/component/matrix/MatrixView.ts | 494 +++-- src/component/timeline/SliderTimelineView.ts | 5 +- src/component/title/install.ts | 10 +- src/component/toolbox/ToolboxView.ts | 24 +- src/component/visualMap/VisualMapView.ts | 3 +- src/coord/CoordinateSystem.ts | 70 +- src/coord/View.ts | 15 +- src/coord/calendar/Calendar.ts | 107 +- src/coord/calendar/CalendarModel.ts | 9 +- src/coord/calendar/prepareCustom.ts | 13 +- src/coord/cartesian/Cartesian2D.ts | 4 +- src/coord/cartesian/Grid.ts | 76 +- src/coord/cartesian/GridModel.ts | 8 +- src/coord/cartesian/cartesianAxisHelper.ts | 13 +- .../cartesian/defaultAxisExtentFromData.ts | 13 +- src/coord/geo/Geo.ts | 18 +- src/coord/geo/geoCreator.ts | 40 +- src/coord/matrix/Matrix.ts | 626 +++++- src/coord/matrix/MatrixBodyCorner.ts | 291 +++ src/coord/matrix/MatrixDim.ts | 578 ++++-- src/coord/matrix/MatrixModel.ts | 320 ++- src/coord/matrix/matrixCoordHelper.ts | 363 ++++ src/coord/matrix/prepareCustom.ts | 17 +- src/coord/parallel/Parallel.ts | 9 +- src/coord/polar/Polar.ts | 31 +- src/coord/polar/polarCreator.ts | 11 +- src/coord/radar/Radar.ts | 11 +- src/coord/single/Single.ts | 44 +- src/core/CoordinateSystem.ts | 313 ++- src/core/echarts.ts | 115 +- src/data/OrdinalMeta.ts | 35 +- src/label/labelStyle.ts | 5 +- src/model/Component.ts | 10 +- src/model/Series.ts | 3 +- src/model/referHelper.ts | 36 +- src/util/graphic.ts | 102 +- src/util/layout.ts | 115 +- src/util/model.ts | 86 +- src/util/number.ts | 46 +- src/util/types.ts | 93 +- test/calendar-other-coord-sys.html | 748 +++++++ test/matrix.html | 240 ++- test/matrix2.html | 1802 +++++++++++++++++ test/matrix3.html | 858 ++++++++ test/matrix_application.html | 384 +++- test/matrix_application2.html | 801 ++++++++ test/pie-coordinate-system.html | 16 +- 69 files changed, 8405 insertions(+), 969 deletions(-) create mode 100644 src/coord/matrix/MatrixBodyCorner.ts create mode 100644 src/coord/matrix/matrixCoordHelper.ts create mode 100644 test/calendar-other-coord-sys.html create mode 100644 test/matrix2.html create mode 100644 test/matrix3.html create mode 100644 test/matrix_application2.html diff --git a/src/chart/bar/BarSeries.ts b/src/chart/bar/BarSeries.ts index de03a19eaf..395c2f14a2 100644 --- a/src/chart/bar/BarSeries.ts +++ b/src/chart/bar/BarSeries.ts @@ -37,11 +37,13 @@ import { inheritDefaultOption } from '../../util/component'; import SeriesData from '../../data/SeriesData'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; -export type PolarBarLabelPosition = SeriesLabelOption['position'] - | 'start' | 'insideStart' | 'middle' | 'end' | 'insideEnd'; +type PolarBarLabelPositionExtra = 'start' | 'insideStart' | 'middle' | 'end' | 'insideEnd'; +export type PolarBarLabelPosition = SeriesLabelOption['position'] | PolarBarLabelPositionExtra; -export type BarSeriesLabelOption = Omit - & {position?: PolarBarLabelPosition | 'outside'}; +export type BarSeriesLabelOption = SeriesLabelOption< + CallbackDataParams, + {positionExtra: PolarBarLabelPositionExtra | 'outside'} +>; export interface BarStateOption { itemStyle?: BarItemStyleOption diff --git a/src/chart/bar/PictorialBarSeries.ts b/src/chart/bar/PictorialBarSeries.ts index 95fbdd230f..5292a1a62f 100644 --- a/src/chart/bar/PictorialBarSeries.ts +++ b/src/chart/bar/PictorialBarSeries.ts @@ -27,14 +27,15 @@ import { StatesOptionMixin, OptionDataItemObject, DefaultEmphasisFocus, - SeriesEncodeOptionMixin + SeriesEncodeOptionMixin, + CallbackDataParams } from '../../util/types'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import { inheritDefaultOption } from '../../util/component'; export interface PictorialBarStateOption { itemStyle?: ItemStyleOption - label?: SeriesLabelOption + label?: SeriesLabelOption } interface PictorialBarSeriesSymbolOption { diff --git a/src/chart/custom/CustomSeries.ts b/src/chart/custom/CustomSeries.ts index 29014e1a6a..606ccb3f15 100644 --- a/src/chart/custom/CustomSeries.ts +++ b/src/chart/custom/CustomSeries.ts @@ -26,10 +26,12 @@ import { AnimationOption, BlurScope, CallbackDataParams, + CoordinateSystemDataLayout, Dictionary, DimensionLoose, ItemStyleOption, LabelOption, + NullUndefined, OptionDataValue, OrdinalRawValue, ParsedValue, @@ -286,13 +288,30 @@ export interface CustomSeriesRenderItemParamsCoordSys { } export interface CustomSeriesRenderItemCoordinateSystemAPI { coord( - data: OptionDataValue | OptionDataValue[], - clamp?: boolean + // @see `CoordinateSystemDataCoord` + data: (OptionDataValue | NullUndefined) + | (OptionDataValue | NullUndefined)[] + | (OptionDataValue | OptionDataValue[] | NullUndefined)[], + // Some coord sys may support `clamp?: boolean` there. + // Can also be an `{xxx?: ...}` here. + opt?: unknown ): number[]; size?( + // Represents a range, rather than a absolute value. + // e.g., `dataSize: [5, 100]` represents + // data range `5` in x and data range `100` in y. dataSize: OptionDataValue | OptionDataValue[], + // Represents a data point, based on which to calculate size. + // Some axis, such as logarithm, size varies in different points. dataItem?: OptionDataValue | OptionDataValue[] ): number | number[]; + layout?( + // @see `CoordinateSystemDataCoord` + data: (OptionDataValue | NullUndefined) + | (OptionDataValue | NullUndefined)[] + | (OptionDataValue | OptionDataValue[] | NullUndefined)[], + opt?: unknown + ): CoordinateSystemDataLayout; } export type WrapEncodeDefRet = Dictionary; @@ -377,7 +396,7 @@ export default class CustomSeriesModel extends SeriesModel { static type = 'series.custom'; readonly type = CustomSeriesModel.type; - static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; + static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar', 'matrix']; // preventAutoZ = true; diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts index 603b5ebb96..5ce6cceacb 100644 --- a/src/chart/custom/CustomView.ts +++ b/src/chart/custom/CustomView.ts @@ -152,6 +152,8 @@ const attachedTxInfoTmp = { /** + * FIXME: register rather than import directly, for size. + * * To reduce total package size of each coordinate systems, the modules `prepareCustom` * of each coordinate systems are not required by each coordinate systems directly, but * required by the module `custom`. diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts index 152cdb37da..307b15a2ec 100644 --- a/src/chart/funnel/FunnelSeries.ts +++ b/src/chart/funnel/FunnelSeries.ts @@ -147,6 +147,7 @@ class FunnelSeriesModel extends SeriesModel { } static defaultOption: FunnelSeriesOption = { + coordinateSystemUsage: 'box', // zlevel: 0, // 一级层叠 z: 2, // 二级层叠 legendHoverLink: true, diff --git a/src/chart/funnel/funnelLayout.ts b/src/chart/funnel/funnelLayout.ts index 4d433c1b1e..952904117a 100644 --- a/src/chart/funnel/funnelLayout.ts +++ b/src/chart/funnel/funnelLayout.ts @@ -25,14 +25,6 @@ import SeriesData from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import { isFunction } from 'zrender/src/core/util'; -function getViewRect(seriesModel: FunnelSeriesModel, api: ExtensionAPI) { - return layout.getLayoutRect( - seriesModel.getBoxLayoutParams(), { - width: api.getWidth(), - height: api.getHeight() - } - ); -} function getSortedIndices(data: SeriesData, sort: FunnelSeriesOption['sort']) { const valueDim = data.mapDimension('value'); @@ -251,7 +243,10 @@ export default function funnelLayout(ecModel: GlobalModel, api: ExtensionAPI) { const data = seriesModel.getData(); const valueDim = data.mapDimension('value'); const sort = seriesModel.get('sort'); - const viewRect = getViewRect(seriesModel, api); + + const layoutRef = layout.createBoxLayoutReference(seriesModel, api); + const viewRect = layout.getLayoutRect(seriesModel.getBoxLayoutParams(), layoutRef.refContainer); + const orient = seriesModel.get('orient'); const viewWidth = viewRect.width; const viewHeight = viewRect.height; diff --git a/src/chart/graph/createView.ts b/src/chart/graph/createView.ts index 238c4b6184..90b7b2b530 100644 --- a/src/chart/graph/createView.ts +++ b/src/chart/graph/createView.ts @@ -19,29 +19,34 @@ // FIXME Where to create the simple view coordinate system import View from '../../coord/View'; -import {getLayoutRect} from '../../util/layout'; +import {createBoxLayoutReference, getLayoutRect} from '../../util/layout'; import * as bbox from 'zrender/src/core/bbox'; import GraphSeriesModel, { GraphNodeItemOption } from './GraphSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; import GlobalModel from '../../model/Global'; import { extend } from 'zrender/src/core/util'; +import { injectCoordSysByOption } from '../../core/CoordinateSystem'; function getViewRect(seriesModel: GraphSeriesModel, api: ExtensionAPI, aspect: number) { + const layoutRef = createBoxLayoutReference(seriesModel, api); const option = extend(seriesModel.getBoxLayoutParams(), { aspect: aspect }); - return getLayoutRect(option, { - width: api.getWidth(), - height: api.getHeight() - }); + return getLayoutRect(option, layoutRef.refContainer); } export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionAPI) { const viewList: View[] = []; ecModel.eachSeriesByType('graph', function (seriesModel: GraphSeriesModel) { - const coordSysType = seriesModel.get('coordinateSystem'); - if (!coordSysType || coordSysType === 'view') { + injectCoordSysByOption({ + targetModel: seriesModel, + coordSysType: 'view', + coordSysProvider: createViewCoordSys, + isDefaultDataCoordSys: true, + }); + + function createViewCoordSys() { const data = seriesModel.getData(); const positions = data.mapArray(function (idx) { const itemModel = data.getItemModel(idx); @@ -77,7 +82,7 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA const viewWidth = viewRect.width; const viewHeight = viewRect.height; - const viewCoordSys = seriesModel.coordinateSystem = new View(); + const viewCoordSys = new View(); viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); viewCoordSys.setBoundingRect( @@ -92,6 +97,8 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA viewCoordSys.setZoom(seriesModel.get('zoom')); viewList.push(viewCoordSys); + + return viewCoordSys; } }); diff --git a/src/chart/heatmap/HeatmapSeries.ts b/src/chart/heatmap/HeatmapSeries.ts index df68efdeb6..7647f1837e 100644 --- a/src/chart/heatmap/HeatmapSeries.ts +++ b/src/chart/heatmap/HeatmapSeries.ts @@ -38,6 +38,7 @@ import SeriesData from '../../data/SeriesData'; import type Geo from '../../coord/geo/Geo'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Calendar from '../../coord/calendar/Calendar'; +import Matrix from '../../coord/matrix/Matrix'; type HeatmapDataValue = OptionDataValue[]; @@ -67,7 +68,7 @@ export interface HeatmapSeriesOption type?: 'heatmap' - coordinateSystem?: 'cartesian2d' | 'geo' | 'calendar' + coordinateSystem?: 'cartesian2d' | 'geo' | 'calendar' | 'matrix' // Available on geo coordinate system blurSize?: number @@ -84,9 +85,8 @@ class HeatmapSeriesModel extends SeriesModel { static readonly type = 'series.heatmap'; readonly type = HeatmapSeriesModel.type; - static readonly dependencies = ['grid', 'geo', 'calendar']; - // @ts-ignore - coordinateSystem: Cartesian2D | Geo | Calendar; + static readonly dependencies = ['grid', 'geo', 'calendar', 'matrix']; + coordinateSystem: Cartesian2D | Geo | Calendar | Matrix; getInitialData(option: HeatmapSeriesOption, ecModel: GlobalModel): SeriesData { return createSeriesData(null, this, { diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts index a181685fad..cdab43f2e6 100644 --- a/src/chart/heatmap/HeatmapView.ts +++ b/src/chart/heatmap/HeatmapView.ts @@ -265,27 +265,33 @@ class HeatmapView extends ChartView { }); } else if (isMatrix) { + const shape = coordSys.dataToLayout([ + data.get(dataDims[0], idx), + data.get(dataDims[1], idx) + ]).rect; + if (zrUtil.eqNaN(shape.x)) { + continue; + } rect = new graphic.Rect({ z2: 1, - shape: coordSys.dataToRect( - [ - data.get(dataDims[0], idx) as string, - data.get(dataDims[1], idx) as string - ] - ), - style + shape, + style, }); } - else { + else { // Calendar // Ignore empty data if (isNaN(data.get(dataDims[1], idx) as number)) { continue; } - + const layout = coordSys.dataToLayout([data.get(dataDims[0], idx)]); + const shape = layout.contentRect || layout.rect; + if (zrUtil.eqNaN(shape.x)) { + continue; + } rect = new graphic.Rect({ z2: 1, - shape: coordSys.dataToRect([data.get(dataDims[0], idx)]).contentShape, - style + shape, + style, }); } diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts index 14eea23fd3..a6fcaaed95 100644 --- a/src/chart/helper/createGraphFromNodeEdge.ts +++ b/src/chart/helper/createGraphFromNodeEdge.ts @@ -69,7 +69,7 @@ export default function createGraphFromNodeEdge( const coordSys = seriesModel.get('coordinateSystem'); let nodeData; - if (coordSys === 'cartesian2d' || coordSys === 'polar') { + if (coordSys === 'cartesian2d' || coordSys === 'polar' || coordSys === 'matrix') { nodeData = createSeriesData(nodes, seriesModel); } else { diff --git a/src/chart/helper/createSeriesData.ts b/src/chart/helper/createSeriesData.ts index 4e1bfd714c..a154906435 100644 --- a/src/chart/helper/createSeriesData.ts +++ b/src/chart/helper/createSeriesData.ts @@ -161,6 +161,16 @@ function createSeriesData( const dimValueGetter = firstCategoryDimIndex != null && isNeedCompleteOrdinalData(source) + /** + * This serves this case: + * var echarts_option = { + * xAxis: { data: ['a', 'b', 'c'] }, + * yAxis: {} + * series: { data: [555, 666, 777] } + * }; + * The `series.data` is completed to: + * [[0, 555], [1, 666], [2, 777]] + */ ? function (this: DataStore, itemOpt: any, dimName: string, dataIndex: number, dimIndex: number) { // Use dataIndex as ordinal value in categoryAxis return dimIndex === firstCategoryDimIndex diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts index 157678f412..7e177e8868 100644 --- a/src/chart/map/MapSeries.ts +++ b/src/chart/map/MapSeries.ts @@ -45,6 +45,7 @@ import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; import {createSymbol, ECSymbol} from '../../util/symbol'; import {LegendIconParams} from '../../component/legend/LegendModel'; import {Group} from '../../util/graphic'; +import { CoordinateSystemUsageKind, decideCoordSysUsageKind } from '../../core/CoordinateSystem'; export interface MapStateOption { itemStyle?: GeoItemStyleOption @@ -146,10 +147,15 @@ class MapSeries extends SeriesModel { * inner exclusive geo model. */ getHostGeoModel(): GeoModel { - const geoIndex = this.option.geoIndex; - return geoIndex != null - ? this.ecModel.getComponent('geo', geoIndex) as GeoModel - : null; + if (decideCoordSysUsageKind(this).kind === CoordinateSystemUsageKind.boxCoordSys) { + // Always use an internal geo if specify a boxCoordSys. + // Notice that currently we do not support laying out a geo based on + // another geo, but preserve the possibility. + return; + } + return this.getReferringComponents( + 'geo', {useDefault: false, enableAll: false, enableNone: false} + ).models[0] as GeoModel; } getMapType(): string { diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts index c50da04054..a57b3f4e19 100644 --- a/src/chart/pie/PieSeries.ts +++ b/src/chart/pie/PieSeries.ts @@ -39,6 +39,7 @@ import { DefaultEmphasisFocus } from '../../util/types'; import type SeriesData from '../../data/SeriesData'; +import { registerLayOutOnCoordSysUsage } from '../../core/CoordinateSystem'; interface PieItemStyleOption extends ItemStyleOption { // can be 10 @@ -168,9 +169,8 @@ class PieSeriesModel extends SeriesModel { * @overwrite */ getInitialData(this: PieSeriesModel): SeriesData { - const isMatrix = this.option.coordinateSystem === 'matrix'; return createSeriesDataSimply(this, { - coordDimensions: isMatrix ? ['x', 'y', 'value'] : ['value'], + coordDimensions: ['value'], encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) }); } @@ -244,6 +244,7 @@ class PieSeriesModel extends SeriesModel { stillShowZeroSum: true, // cursor: null, + coordinateSystemUsage: 'box', left: 0, top: 0, @@ -266,7 +267,8 @@ class PieSeriesModel extends SeriesModel { // Works only position is 'outer' and alignTo is 'edge'. edgeDistance: '25%', // Works only position is 'outer' and alignTo is not 'edge'. - bleedMargin: 10, + // The default `bleedMargin` is auto determined according to view rect size. + // bleedMargin: 10, // Distance between text and label line. distanceToLabelLine: 5 // formatter: 标签文本格式器,同 tooltip.formatter,不支持异步回调 @@ -328,4 +330,13 @@ class PieSeriesModel extends SeriesModel { } +registerLayOutOnCoordSysUsage({ + fullType: PieSeriesModel.type, + getCoord2(model: PieSeriesModel) { + // Not able to validate `center` type here. + // But percentage center, such as '12%', is not allowed in this case. + return model.getShallow('center'); + } +}); + export default PieSeriesModel; diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts index 04a81f1e6b..cd3621102f 100644 --- a/src/chart/pie/PieView.ts +++ b/src/chart/pie/PieView.ts @@ -19,7 +19,7 @@ */ -import { extend, retrieve3 } from 'zrender/src/core/util'; +import { clone, extend, retrieve3 } from 'zrender/src/core/util'; import * as graphic from '../../util/graphic'; import { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states'; import ChartView from '../../view/Chart'; @@ -33,7 +33,7 @@ import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGu import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import { getSectorCornerRadius } from '../helper/sectorHelper'; import { saveOldStyle } from '../../animation/basicTransition'; -import { getBasicPieLayout, getSeriesLayoutData } from './pieLayout'; +import { getSeriesLayoutData } from './pieLayout'; /** * Piece of pie including Sector, Label, LabelLine @@ -262,7 +262,7 @@ class PieView extends ChartView { if (data.count() === 0 && seriesModel.get('showEmptyCircle')) { const layoutData = getSeriesLayoutData(seriesModel); const sector = new graphic.Sector({ - shape: extend(getBasicPieLayout(seriesModel, api), layoutData) + shape: clone(layoutData) }); sector.useStyle(seriesModel.getModel('emptyCircleStyle').getItemStyle()); this._emptyCircleSector = sector; diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts index 934b49cadc..419079478b 100644 --- a/src/chart/pie/labelLayout.ts +++ b/src/chart/pie/labelLayout.ts @@ -384,7 +384,11 @@ export default function pieLabelLayout( const labelDistance = labelModel.get('distanceToLabelLine'); const labelAlignTo = labelModel.get('alignTo'); const edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth); - const bleedMargin = labelModel.get('bleedMargin'); + let bleedMargin = labelModel.get('bleedMargin'); + if (bleedMargin == null) { + // An arbitrary strategy for small viewRect - especial pie is layout in calendar or matrix coord sys. + bleedMargin = Math.min(viewWidth, viewHeight) > 200 ? 10 : 2; + } const labelLineModel = itemModel.getModel('labelLine'); let labelLineLen = labelLineModel.get('length'); diff --git a/src/chart/pie/pieLayout.ts b/src/chart/pie/pieLayout.ts index 4ec992ec4b..05915b96ba 100644 --- a/src/chart/pie/pieLayout.ts +++ b/src/chart/pie/pieLayout.ts @@ -26,25 +26,51 @@ import PieSeriesModel from './PieSeries'; import { SectorShape } from 'zrender/src/graphic/shape/Sector'; import { normalizeArcAngles } from 'zrender/src/core/PathProxy'; import { makeInner } from '../../util/model'; +import { BoxCoordinateSystemCoordFrom } from '../../core/CoordinateSystem'; const PI2 = Math.PI * 2; const RADIAN = Math.PI / 180; -function getViewRect(seriesModel: PieSeriesModel, api: ExtensionAPI) { - return layout.getLayoutRect( - seriesModel.getBoxLayoutParams(), { - width: api.getWidth(), - height: api.getHeight() - } - ); +function getViewRectAndCenter(seriesModel: PieSeriesModel, api: ExtensionAPI) { + + const layoutRef = layout.createBoxLayoutReference(seriesModel, api, { + enableLayoutOnlyByCenter: true, + }); + const boxLayoutParams = seriesModel.getBoxLayoutParams(); + + let viewRect: layout.LayoutRect; + let center: number[]; + if (layoutRef.type === layout.BoxLayoutReferenceType.point) { + center = layoutRef.refPoint; + // `viewRect` is required in `pie/labelLayout.ts`. + viewRect = layout.getLayoutRect( + boxLayoutParams, {width: api.getWidth(), height: api.getHeight()} + ); + } + else { // layoutRef.type === layout.BoxLayoutReferenceType.rect + const centerOption = seriesModel.get('center'); + const centerOptionArr = zrUtil.isArray(centerOption) + ? centerOption : [centerOption, centerOption]; + viewRect = layout.getLayoutRect( + boxLayoutParams, layoutRef.refContainer + ); + center = layoutRef.boxCoordFrom === BoxCoordinateSystemCoordFrom.coord2 + ? layoutRef.refPoint // option `series.center` has been used as coord. + : [ + parsePercent(centerOptionArr[0], viewRect.width) + viewRect.x, + parsePercent(centerOptionArr[1], viewRect.height) + viewRect.y, + ]; + } + + return {viewRect, center}; } -export function getBasicPieLayout(seriesModel: PieSeriesModel, api: ExtensionAPI): - Pick { - const viewRect = getViewRect(seriesModel, api); +function getBasicPieLayout(seriesModel: PieSeriesModel, api: ExtensionAPI): + Pick & {viewRect: layout.LayoutRect} { // center can be string or number when coordinateSystem is specified - let center = seriesModel.get('center'); + const {viewRect, center} = getViewRectAndCenter(seriesModel, api); + let radius = seriesModel.get('radius'); if (!zrUtil.isArray(radius)) { @@ -56,28 +82,12 @@ export function getBasicPieLayout(seriesModel: PieSeriesModel, api: ExtensionAPI const r0 = parsePercent(radius[0], size / 2); const r = parsePercent(radius[1], size / 2); - let cx: number; - let cy: number; - const coordSys = seriesModel.coordinateSystem; - if (coordSys) { - // percentage is not allowed when coordinate system is specified - const point = coordSys.dataToPoint(center); - cx = point[0] || 0; - cy = point[1] || 0; - } - else { - if (!zrUtil.isArray(center)) { - center = [center, center]; - } - cx = parsePercent(center[0], width) + viewRect.x; - cy = parsePercent(center[1], height) + viewRect.y; - } - return { - cx, - cy, + cx: center[0], + cy: center[1], r0, - r + r, + viewRect, }; } @@ -89,9 +99,8 @@ export default function pieLayout( ecModel.eachSeriesByType(seriesType, function (seriesModel: PieSeriesModel) { const data = seriesModel.getData(); const valueDim = data.mapDimension('value'); - const viewRect = getViewRect(seriesModel, api); - const { cx, cy, r, r0 } = getBasicPieLayout(seriesModel, api); + const { cx, cy, r, r0, viewRect } = getBasicPieLayout(seriesModel, api); let startAngle = -seriesModel.get('startAngle') * RADIAN; let endAngle = seriesModel.get('endAngle'); @@ -132,6 +141,10 @@ export default function pieLayout( layoutData.startAngle = startAngle; layoutData.endAngle = endAngle; layoutData.clockwise = clockwise; + layoutData.cx = cx; + layoutData.cy = cy; + layoutData.r = r; + layoutData.r0 = r0; const angleRange = Math.abs(endAngle - startAngle); @@ -141,6 +154,7 @@ export default function pieLayout( let currentAngle = startAngle; + // Requird by `pieLabelLayout`. data.setLayout({ viewRect, r }); data.each(valueDim, function (value: number, idx: number) { @@ -274,4 +288,8 @@ export const getSeriesLayoutData = makeInner<{ startAngle: number endAngle: number clockwise: boolean + cx: number + cy: number + r: number + r0: number }, PieSeriesModel>(); diff --git a/src/chart/scatter/ScatterSeries.ts b/src/chart/scatter/ScatterSeries.ts index 5e2995ecad..505937b039 100644 --- a/src/chart/scatter/ScatterSeries.ts +++ b/src/chart/scatter/ScatterSeries.ts @@ -82,7 +82,7 @@ class ScatterSeriesModel extends SeriesModel { static readonly type = 'series.scatter'; type = ScatterSeriesModel.type; - static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; + static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar', 'matrix']; hasSymbolVisual = true; diff --git a/src/component/axisPointer/modelHelper.ts b/src/component/axisPointer/modelHelper.ts index 2b03d20104..a0635b9c68 100644 --- a/src/component/axisPointer/modelHelper.ts +++ b/src/component/axisPointer/modelHelper.ts @@ -293,6 +293,7 @@ function collectSeriesInfo(result: CollectionResult, ecModel: GlobalModel) { const seriesTooltipTrigger = seriesModel.get(['tooltip', 'trigger'], true); const seriesTooltipShow = seriesModel.get(['tooltip', 'show'], true); if (!coordSys + || !coordSys.model // PENDING: radar do not have a model. || seriesTooltipTrigger === 'none' || seriesTooltipTrigger === false || seriesTooltipTrigger === 'item' diff --git a/src/component/calendar/CalendarView.ts b/src/component/calendar/CalendarView.ts index 6cc7274758..50c1d556da 100644 --- a/src/component/calendar/CalendarView.ts +++ b/src/component/calendar/CalendarView.ts @@ -97,7 +97,7 @@ class CalendarView extends ComponentView { i = coordSys.getNextNDay(i, 1).time ) { - const point = coordSys.dataToRect([i], false).tl; + const point = coordSys.dataToCalendarLayout([i], false).tl; // every rect const rect = new graphic.Rect({ @@ -158,7 +158,7 @@ class CalendarView extends ComponentView { function addPoints(date: OptionDataValueDate) { self._firstDayOfMonth.push(coordSys.getDateInfo(date)); - self._firstDayPoints.push(coordSys.dataToRect([date], false).tl); + self._firstDayPoints.push(coordSys.dataToCalendarLayout([date], false).tl); const points = self._getLinePointsOfOneWeek(calendarModel, date, orient); @@ -214,7 +214,7 @@ class CalendarView extends ComponentView { for (let i = 0; i < 7; i++) { const tmpD = coordSys.getNextNDay(parsedDate.time, i); - const point = coordSys.dataToRect([tmpD.time], false); + const point = coordSys.dataToCalendarLayout([tmpD.time], false); points[2 * tmpD.day] = point.tl; points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr']; @@ -536,7 +536,7 @@ class CalendarView extends ComponentView { for (let i = 0; i < 7; i++) { const tmpD = coordSys.getNextNDay(start, i); - const point = coordSys.dataToRect([tmpD.time], false).center; + const point = coordSys.dataToCalendarLayout([tmpD.time], false).center; let day = i; day = Math.abs((i + firstDayOfWeek) % 7); const weekText = new graphic.Text({ diff --git a/src/component/dataZoom/SliderZoomView.ts b/src/component/dataZoom/SliderZoomView.ts index a6545fdb3e..bf4573a154 100644 --- a/src/component/dataZoom/SliderZoomView.ts +++ b/src/component/dataZoom/SliderZoomView.ts @@ -224,17 +224,18 @@ class SliderZoomView extends DataZoomView { const showMoveHandle = dataZoomModel.get('brushSelect'); const moveHandleSize = showMoveHandle ? DEFAULT_MOVE_HANDLE_SIZE : 0; + const refContainer = layout.createBoxLayoutReference(dataZoomModel, api).refContainer; + // If some of x/y/width/height are not specified, // auto-adapt according to target grid. const coordRect = this._findCoordRect(); - const ecSize = {width: api.getWidth(), height: api.getHeight()}; // Default align by coordinate system rect. const positionInfo = this._orient === HORIZONTAL ? { // Why using 'right', because right should be used in vertical, // and it is better to be consistent for dealing with position param merge. - right: ecSize.width - coordRect.x - coordRect.width, - top: (ecSize.height - DEFAULT_FILLER_SIZE - DEFAULT_LOCATION_EDGE_GAP - moveHandleSize), + right: refContainer.width - coordRect.x - coordRect.width, + top: (refContainer.height - DEFAULT_FILLER_SIZE - DEFAULT_LOCATION_EDGE_GAP - moveHandleSize), width: coordRect.width, height: DEFAULT_FILLER_SIZE } @@ -258,7 +259,7 @@ class SliderZoomView extends DataZoomView { const layoutRect = layout.getLayoutRect( layoutParams, - ecSize + refContainer ); this._location = {x: layoutRect.x, y: layoutRect.y}; diff --git a/src/component/helper/listComponent.ts b/src/component/helper/listComponent.ts index 39d063bc48..9faabbb619 100644 --- a/src/component/helper/listComponent.ts +++ b/src/component/helper/listComponent.ts @@ -17,57 +17,27 @@ * under the License. */ -// @ts-nocheck - -import { - getLayoutRect, - box as layoutBox, - positionElement -} from '../../util/layout'; import * as formatUtil from '../../util/format'; import * as graphic from '../../util/graphic'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { ItemStyleOption, ZRColor } from '../../util/types'; +import Model from '../../model/Model'; -/** - * Layout list like component. - * It will box layout each items in group of component and then position the whole group in the viewport - * @param {module:zrender/group/Group} group - * @param {module:echarts/model/Component} componentModel - * @param {module:echarts/ExtensionAPI} - */ -export function layout(group, componentModel, api) { - const boxLayoutParams = componentModel.getBoxLayoutParams(); - const padding = componentModel.get('padding'); - const viewportSize = {width: api.getWidth(), height: api.getHeight()}; - - const rect = getLayoutRect( - boxLayoutParams, - viewportSize, - padding - ); - layoutBox( - componentModel.get('orient'), - group, - componentModel.get('itemGap'), - rect.width, - rect.height - ); - - positionElement( - group, - boxLayoutParams, - viewportSize, - padding - ); -} +interface BackgroundRelatedOption { + backgroundColor?: ZRColor + borderRadius?: number | number[] + padding?: number | number[] + itemStyle?: Omit +}; -export function makeBackground(rect, componentModel) { +export function makeBackground(rect: RectLike, componentModel: Model): graphic.Rect { const padding = formatUtil.normalizeCssArray( componentModel.get('padding') ); const style = componentModel.getItemStyle(['color', 'opacity']); style.fill = componentModel.get('backgroundColor'); - rect = new graphic.Rect({ + const bgRect = new graphic.Rect({ shape: { x: rect.x - padding[3], y: rect.y - padding[0], @@ -84,5 +54,5 @@ export function makeBackground(rect, componentModel) { // and background rect when setting like `left: 0`, `top: 0`. // graphic.subPixelOptimizeRect(rect); - return rect; + return bgRect; } diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 35b0779f2b..fb34487c74 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -43,7 +43,8 @@ import { CommonTooltipOption, ColorString, SeriesOption, - SymbolOptionMixin + SymbolOptionMixin, + ItemStyleOption } from '../../util/types'; import Model from '../../model/Model'; import {LineStyleProps} from '../../model/mixin/lineStyle'; @@ -132,11 +133,11 @@ class LegendView extends ComponentView { this.renderInner(itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition); // Perform layout. + const refContainer = layoutUtil.createBoxLayoutReference(legendModel, api).refContainer; const positionInfo = legendModel.getBoxLayoutParams(); - const viewportSize = {width: api.getWidth(), height: api.getHeight()}; const padding = legendModel.get('padding'); - const maxSize = layoutUtil.getLayoutRect(positionInfo, viewportSize, padding); + const maxSize = layoutUtil.getLayoutRect(positionInfo, refContainer, padding); const mainRect = this.layoutInner(legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition); @@ -146,7 +147,7 @@ class LegendView extends ComponentView { width: mainRect.width, height: mainRect.height }, positionInfo), - viewportSize, + refContainer, padding ); this.group.x = layoutRect.x - mainRect.x; @@ -155,7 +156,11 @@ class LegendView extends ComponentView { // Render background after group is layout. this.group.add( - this._backgroundEl = makeBackground(mainRect, legendModel) + this._backgroundEl = makeBackground( + mainRect, + // FXIME: most itemStyle options does not work in background because inherit is not handled yet. + legendModel as Model & {itemStyle: ItemStyleOption}> + ) ); } diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index fbcbafba5a..6aa65dc90b 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -17,10 +17,35 @@ * under the License. */ -import MatrixModel from '../../coord/matrix/MatrixModel'; +import MatrixModel, { MatrixBaseCellOption, MatrixCellStyleOption, MatrixOption } from '../../coord/matrix/MatrixModel'; import ComponentView from '../../view/Component'; import { createTextStyle } from '../../label/labelStyle'; -import * as graphic from '../../util/graphic'; +import { MatrixCellLayoutInfo, MatrixDim, MatrixXYLocator } from '../../coord/matrix/MatrixDim'; +import Model from '../../model/Model'; +import { NullUndefined, OrdinalRawValue } from '../../util/types'; +import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; +import * as vectorUtil from 'zrender/src/core/vector'; +import { RectShape } from 'zrender/src/graphic/shape/Rect'; +import { ItemStyleProps } from '../../model/mixin/itemStyle'; +import { LineStyleProps } from '../../model/mixin/lineStyle'; +import { LineShape } from 'zrender/src/graphic/shape/Line'; +import { subPixelOptimize } from 'zrender/src/graphic/helper/subPixelOptimize'; +import { Group, Text, Rect, Line, XY, WH, setTooltipConfig, expandOrShrinkRect } from '../../util/graphic'; +import { ListIterator } from '../../util/model'; +import { isNumber, normalizeCssArray, retrieve2 } from 'zrender/src/core/util'; +import { createNaNRectLike } from '../../coord/matrix/matrixCoordHelper'; +import { MatrixBodyCorner, MatrixBodyOrCornerKind } from '../../coord/matrix/MatrixBodyCorner'; +import { ElementTextConfig } from 'zrender/src/Element'; + +const round = Math.round; + +// When special border style is defined on cell, it +// should be over all of the other borders. +type Z2CellDefault = {normal: number, special: number}; +const Z2_BACKGROUND = 0; +const Z2_OUTER_BORDER = 99; +const Z2_BODY_CORNER_CELL_DEFAULT: Z2CellDefault = {normal: 25, special: 100}; +const Z2_DIMENSION_CELL_DEFAULT: Z2CellDefault = {normal: 50, special: 125}; class MatrixView extends ComponentView { @@ -29,152 +54,359 @@ class MatrixView extends ComponentView { render(matrixModel: MatrixModel) { + this.group.removeAll(); + const group = this.group; + const coordSys = matrixModel.coordinateSystem; + const rect = coordSys.getRect(); + const xDimModel = matrixModel.getDimensionModel('x'); + const yDimModel = matrixModel.getDimensionModel('y'); + const xDim = xDimModel.dim; + const yDim = yDimModel.dim; - group.removeAll(); + renderDimensionCells( + group, + matrixModel + ); - this._renderTable(matrixModel); - } + createBodyAndCorner( + group, + matrixModel, + xDim, + yDim + ); - protected _renderTable(matrixModel: MatrixModel) { - const coordSys = matrixModel.coordinateSystem; - const xDim = coordSys.getDim('x'); - const yDim = coordSys.getDim('y'); - const xModel = matrixModel.getModel('x'); - const yModel = matrixModel.getModel('y'); - const xLabelModel = xModel.getModel('label'); - const yLabelModel = yModel.getModel('label'); - const xItemStyle = xModel.getModel('itemStyle').getItemStyle(); - const yItemStyle = yModel.getModel('itemStyle').getItemStyle(); + const borderZ2Option = matrixModel.getShallow('borderZ2', true); + const outerBorderZ2 = retrieve2(borderZ2Option, Z2_OUTER_BORDER); + const dividerLineZ2 = outerBorderZ2 - 1; - const rect = coordSys.getRect(); - const xLeavesCnt = xDim.getLeavesCount(); - const yLeavesCnt = yDim.getLeavesCount(); - const xCells = xDim.getCells(); - const xHeight = xDim.getHeight(); - const yCells = yDim.getCells(); - const yHeight = yDim.getHeight(); - const cellWidth = rect.width / (xLeavesCnt + yHeight); - const cellHeight = rect.height / (yLeavesCnt + xHeight); - - const xLeft = rect.x + cellWidth * yHeight; - if (xModel.get('show')) { - for (let i = 0; i < xCells.length; i++) { - const cell = xCells[i]; - const width = cellWidth * cell.colSpan; - const height = cellHeight * cell.rowSpan; - const left = xLeft + cellWidth * cell.colId; - const top = rect.y + cellHeight * cell.rowId; - - const cellRect = new graphic.Rect({ - shape: { - x: left, - y: top, - width: width, - height: height + // Outer border and overall background. Use separate elements because of z-order: + // The overall background should appear below any other elements. + // But in most cases, the outer border and the divider line should be above the normal cell borders - + // especially when cell borders have different colors. But users may highlight some specific cells by + // overstirking their border, in which case it should be above the outer border. + const bgStyle = matrixModel.getModel('backgroundStyle').getItemStyle( + ['borderWidth'] + ); + bgStyle.lineWidth = 0; + const borderStyle = matrixModel.getModel('backgroundStyle').getItemStyle( + ['color', 'decal', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'] + ); + borderStyle.fill = 'none'; + const bgRect = createMatrixRect(rect.clone(), bgStyle, Z2_BACKGROUND); + const borderRect = createMatrixRect(rect.clone(), borderStyle, outerBorderZ2); + bgRect.silent = true; + borderRect.silent = true; + group.add(bgRect); + group.add(borderRect); + + // Header split line. + const xDimCell0 = xDim.getUnitLayoutInfo(0, 0); + const yDimCell0 = yDim.getUnitLayoutInfo(1, 0); + if (xDimCell0 && yDimCell0) { + if (xDim.shouldShow()) { + group.add(createMatrixLine( + { + x1: rect.x, + y1: yDimCell0.xy, + x2: rect.x + rect.width, + y2: yDimCell0.xy, }, - style: xItemStyle - }); - this.group.add(cellRect); - - if (xLabelModel.get('show')) { - cellRect.setTextConfig({ - position: 'inside' - }); - cellRect.setTextContent( - new graphic.Text({ - style: createTextStyle(xLabelModel, { - text: cell.value, - verticalAlign: 'middle', - align: 'center' - }), - silent: xLabelModel.get('silent') - }) - ); - } + xDimModel.getModel('dividerLineStyle').getLineStyle(), + dividerLineZ2, + )); + } + if (yDim.shouldShow()) { + group.add(createMatrixLine( + { + x1: xDimCell0.xy, + y1: rect.y, + x2: xDimCell0.xy, + y2: rect.y + rect.height, + }, + yDimModel.getModel('dividerLineStyle').getLineStyle(), + dividerLineZ2, + )); } } + } +} - const yTop = rect.y + cellHeight * xHeight; - if (yModel.get('show')) { - for (let i = 0; i < yCells.length; i++) { - const cell = yCells[i]; - const width = cellWidth * cell.rowSpan; - const height = cellHeight * cell.colSpan; - const left = rect.x + cellWidth * cell.rowId; - const top = yTop + cellHeight * cell.colId; - - this.group.add(new graphic.Rect({ - shape: { - x: left, - y: top, - width: width, - height: height - }, - style: yItemStyle - })); - if (yLabelModel.get('show')) { - this.group.add(new graphic.Text({ - style: createTextStyle(yLabelModel, { - text: cell.value, - x: left + width / 2, - y: top + height / 2, - verticalAlign: 'middle', - align: 'center' - }) - })); +function renderDimensionCells(group: Group, matrixModel: MatrixModel) { + + renderOnDimension(0); + renderOnDimension(1); + + function renderOnDimension(dimIdx: 0 | 1) { + const thisDimModel = matrixModel.getDimensionModel(XY[dimIdx]); + const thisDim = thisDimModel.dim; + + if (!thisDim.shouldShow()) { + return; + } + + const thisDimBgStyleModel = thisDimModel.getModel('itemStyle'); + const thisDimLabelModel = thisDimModel.getModel('label'); + const tooltipOption = matrixModel.getShallow('tooltip', true); + const xyLocator: MatrixXYLocator[] = []; + + for (const it = thisDim.resetCellIterator(); it.next();) { + const dimCell = it.item; + const shape = {} as RectLike; + BoundingRect.copy(shape, dimCell.rect); + + vectorUtil.set(xyLocator, dimCell.id.x, dimCell.id.y); + + createMatrixCell( + xyLocator, + matrixModel, + group, + dimCell.option, + thisDimBgStyleModel, + thisDimLabelModel, + thisDimModel, + shape, + dimCell.option.value, + Z2_DIMENSION_CELL_DEFAULT, + tooltipOption + ); + } + } +} + +function createBodyAndCorner( + group: Group, + matrixModel: MatrixModel, + xDim: MatrixDim, + yDim: MatrixDim +): void { + + createBodyOrCornerCells('body', matrixModel.getBody(), xDim, yDim); + if (xDim.shouldShow() && yDim.shouldShow()) { + createBodyOrCornerCells('corner', matrixModel.getCorner(), yDim, xDim); + } + + function createBodyOrCornerCells( + bodyCornerOptionRoot: TBodyOrCornerKind, + bodyOrCorner: MatrixBodyCorner, + dimForCoordX: MatrixDim, // Can be `matrix.y` (transposed) for corners. + dimForCoordY: MatrixDim, // Can be `matrix.x` (trnasposed) for corners. + ): void { + // Prevent inheriting from ancestor. + const parentCellModel = new Model(matrixModel.getShallow(bodyCornerOptionRoot, true)); + const parentItemStyleModel = parentCellModel.getModel('itemStyle'); + const parentLabelModel = parentCellModel.getModel('label'); + + const itx = new ListIterator(); + const ity = new ListIterator(); + const xyLocator: number[] = []; + const tooltipOption = matrixModel.getShallow('tooltip', true); + + for (dimForCoordY.resetLayoutIterator(ity, 1); ity.next();) { + for (dimForCoordX.resetLayoutIterator(itx, 0); itx.next();) { + const xLayout = itx.item; + const yLayout = ity.item; + + vectorUtil.set(xyLocator, xLayout.id.x, yLayout.id.y); + const bodyCornerCell = bodyOrCorner.getCell(xyLocator); + + // If in span of an other body or corner cell, never render it. + if (bodyCornerCell && bodyCornerCell.inSpanOf && bodyCornerCell.inSpanOf !== bodyCornerCell) { + continue; } + + const shape = {} as RectLike; + if (bodyCornerCell && bodyCornerCell.span) { + BoundingRect.copy(shape, bodyCornerCell.spanRect); + } + else { + xLayout.dim.getLayout(shape, 0, xyLocator[0]); + yLayout.dim.getLayout(shape, 1, xyLocator[1]); + } + + const bodyCornerCellOption = bodyCornerCell ? bodyCornerCell.option : null; + + createMatrixCell( + xyLocator, + matrixModel, + group, + bodyCornerCellOption, + parentItemStyleModel, + parentLabelModel, + parentCellModel, + shape, + bodyCornerCellOption ? bodyCornerCellOption.value : null, + Z2_BODY_CORNER_CELL_DEFAULT, + tooltipOption + ); } } + } // End of createBodyOrCornerCells +} - // Inner cells - const innerBackgroundStyle = matrixModel - .getModel('innerBackgroundStyle') - .getItemStyle(); - for (let i = 0; i < xLeavesCnt; i++) { - for (let j = 0; j < yLeavesCnt; j++) { - const left = xLeft + cellWidth * i; - const top = yTop + cellHeight * j; - this.group.add(new graphic.Rect({ - shape: { - x: left, - y: top, - width: cellWidth, - height: cellHeight - }, - style: innerBackgroundStyle - })); +function createMatrixCell( + xyLocator: MatrixXYLocator[], + matrixModel: MatrixModel, + group: Group, + cellOption: MatrixBaseCellOption | NullUndefined, + parentItemStyleModel: Model, + parentLabelModel: Model, + parentCellModel: Model, + shape: RectLike, + textValue: unknown, + zrCellDefault: Z2CellDefault, + tooltipOption: MatrixOption['tooltip'], +): void { + // Do not use getModel for handy performance optimization. + _tmpCellItemStyleModel.option = cellOption ? cellOption.itemStyle : null; + _tmpCellItemStyleModel.parentModel = parentItemStyleModel; + _tmpCellModel.option = cellOption; + _tmpCellModel.parentModel = parentCellModel; + + // Use different z2 becuase special border may be defined in itemStyle. + const z2 = retrieve2( + _tmpCellModel.getShallow('z2'), + (cellOption && cellOption.itemStyle) ? zrCellDefault.special : zrCellDefault.normal + ); + const tooltipOptionShow = tooltipOption && tooltipOption.show; + let labelShown = false; + + const cellRect = createMatrixRect(shape, _tmpCellItemStyleModel.getItemStyle(), z2); + group.add(cellRect); + + const cursorOption = _tmpCellModel.get('cursor'); + if (cursorOption != null) { + cellRect.attr('cursor', cursorOption); + } + let cellText: Text; + + if (textValue != null) { + const text = textValue + ''; + _tmpCellLabelModel.option = cellOption ? cellOption.label : null; + _tmpCellLabelModel.parentModel = parentLabelModel; + if (_tmpCellLabelModel.getShallow('show')) { + labelShown = true; + + const padding = decideCellPadding(_tmpCellModel); + const lineWidth = cellRect && cellRect.style && cellRect.style.lineWidth; + + BoundingRect.copy(_tmpContentRect, shape); + expandOrShrinkRect(_tmpContentRect, lineWidth, true, true); + expandOrShrinkRect(_tmpContentRect, padding, true, true); + + // Be conservative, currently do not use `labelStyle.ts#createTextConfig` + // to support all `LabelOption`. + const textConfig: ElementTextConfig = {}; + textConfig.position = _tmpCellLabelModel.getShallow('position') || 'inside'; + textConfig.distance = retrieve2(_tmpCellLabelModel.getShallow('distance'), 5); + cellRect.setTextConfig(textConfig); + + cellRect.setTextContent( + cellText = new Text({ + style: createTextStyle(_tmpCellLabelModel, { + text, + width: makeCellLabelWH(_tmpContentRect, _tmpCellLabelModel, 0), + height: makeCellLabelWH(_tmpContentRect, _tmpCellLabelModel, 1), + }), + z2: z2 + 1, + }) + ); + } + setTooltipConfig({ // At least for text overflow. + el: cellRect, + componentModel: matrixModel, + itemName: text, + itemTooltipOption: tooltipOption, + formatterParamsExtra: { + xyLocator: xyLocator.slice() } + }); + } + + let rectSilent = _tmpCellModel.get('silent'); + let labelSilent = _tmpCellLabelModel.get('silent'); + // auto, tooltip of text cells need silient: false, but non-text cells + // do not need a special cursor in most cases. + if (labelSilent == null) { + labelSilent = !(tooltipOptionShow && labelShown); + } + if (rectSilent == null) { + rectSilent = labelSilent || ( + !cellRect.style || cellRect.style.fill === 'none' || !cellRect.style.fill + ); + } + cellRect.silent = rectSilent; + if (cellText) { + // FIXME: but currently label is impl as `textContent`, where it's not + // supported that it's owner is silent but text is not silent. + cellText.silent = labelSilent; + } +} +const _tmpCellModel = new Model(); +const _tmpCellItemStyleModel = new Model(); +const _tmpCellLabelModel = new Model(); +const _tmpContentRect = createNaNRectLike(); + + +function createMatrixRect( + shape: RectShape, style: ItemStyleProps, z2: number +): Rect { + // Currently `subPixelOptimizeRect` can not be used here because it will break rect alignment. + // Optimize line and rect with the same direction. + const lineWidth = style.lineWidth; + if (lineWidth) { + const x2Original = shape.x + shape.width; + const y2Original = shape.y + shape.height; + shape.x = subPixelOptimize(shape.x, lineWidth, true); + shape.y = subPixelOptimize(shape.y, lineWidth, true); + shape.width = subPixelOptimize(x2Original, lineWidth, true) - shape.x; + shape.height = subPixelOptimize(y2Original, lineWidth, true) - shape.y; + } + return new Rect({ + shape, + style: style, + z2, + }); +} + +function createMatrixLine(shape: Omit, style: LineStyleProps, z2: number): Line { + const lineWidth = style.lineWidth; + if (lineWidth) { + if (round(shape.x1 * 2) === round(shape.x2 * 2)) { + shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true); + } + if (round(shape.y1 * 2) === round(shape.y2 * 2)) { + shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true); } + } + return new Line({ + shape, + style, + silent: true, + z2, + }); +} - // Outer border - const backgroundStyle = matrixModel - .getModel('backgroundStyle') - .getItemStyle(); - this.group.add(new graphic.Rect({ - shape: rect, - style: backgroundStyle - })); - // Header border - this.group.add(new graphic.Line({ - shape: { - x1: rect.x, - y1: yTop, - x2: rect.x + rect.width, - y2: yTop - }, - style: backgroundStyle - })); - this.group.add(new graphic.Line({ - shape: { - x1: xLeft, - y1: rect.y, - x2: xLeft, - y2: rect.y + rect.height - }, - style: backgroundStyle - })); +function makeCellLabelWH( + contentRect: RectLike, cellLabelModel: Model, dimIdx: number +): number { + // - To add some spacing between the text and cell border, using percentage value for `label.width/height` + // is not ideal, becuase the padding would vary with the cell size, making the layout inconsistent. + // - `lineWith` is not considered deliberatedly, since it may be modified with user interaction. + let size = cellLabelModel.getShallow(WH[dimIdx]); + if (!isNumber(size)) { + size = contentRect[WH[dimIdx]]; } + return size; +} + +export function decideCellPadding(cellModel: Model): number[] { + const paddingOption = retrieve2(cellModel.getShallow('padding'), DEFAULT_CELL_PADDING_IF_TEXT); + // If cell is used to fill other series, such as heatmap, it's preferable no padding, + // but it's irrelevant since currently `dataToLayout` does not consider padding. + return normalizeCssArray(paddingOption); } +// Consider text overflow truncate +const DEFAULT_CELL_PADDING_IF_TEXT = 3; export default MatrixView; diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts index f993e57138..0a547abd75 100644 --- a/src/component/timeline/SliderTimelineView.ts +++ b/src/component/timeline/SliderTimelineView.ts @@ -756,10 +756,7 @@ function createScaleByModel(model: SliderTimelineModel, axisType?: string): Scal function getViewRect(model: SliderTimelineModel, api: ExtensionAPI) { return layout.getLayoutRect( model.getBoxLayoutParams(), - { - width: api.getWidth(), - height: api.getHeight() - }, + layout.createBoxLayoutReference(model, api).refContainer, model.get('padding') ); } diff --git a/src/component/title/install.ts b/src/component/title/install.ts index 54fbb0e6eb..fc73162902 100644 --- a/src/component/title/install.ts +++ b/src/component/title/install.ts @@ -21,7 +21,7 @@ import * as zrUtil from 'zrender/src/core/util'; import * as graphic from '../../util/graphic'; import {getECData} from '../../util/innerStore'; import {createTextStyle} from '../../label/labelStyle'; -import {getLayoutRect} from '../../util/layout'; +import {createBoxLayoutReference, getLayoutRect} from '../../util/layout'; import ComponentModel from '../../model/Component'; import { ComponentOption, @@ -130,7 +130,6 @@ class TitleModel extends ComponentModel { }; } - // View class TitleView extends ComponentView { @@ -209,11 +208,10 @@ class TitleView extends ComponentView { const layoutOption = titleModel.getBoxLayoutParams(); layoutOption.width = groupRect.width; layoutOption.height = groupRect.height; + + const layoutRef = createBoxLayoutReference(titleModel, api); const layoutRect = getLayoutRect( - layoutOption, { - width: api.getWidth(), - height: api.getHeight() - }, titleModel.get('padding') + layoutOption, layoutRef.refContainer, titleModel.get('padding') ); // Adjust text align based on position if (!textAlign) { diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts index 071c009bd5..b9214323bc 100644 --- a/src/component/toolbox/ToolboxView.ts +++ b/src/component/toolbox/ToolboxView.ts @@ -40,6 +40,7 @@ import { getUID } from '../../util/component'; import Displayable from 'zrender/src/graphic/Displayable'; import ZRText from 'zrender/src/graphic/Text'; import { getFont } from '../../label/labelStyle'; +import { box, createBoxLayoutReference, getLayoutRect, positionElement } from '../../util/layout'; type IconPath = ToolboxFeatureModel['iconPaths'][string]; @@ -292,7 +293,28 @@ class ToolboxView extends ComponentView { }); } - listComponentHelper.layout(group, toolboxModel, api); + const refContainer = createBoxLayoutReference(toolboxModel, api).refContainer; + const boxLayoutParams = toolboxModel.getBoxLayoutParams(); + const padding = toolboxModel.get('padding'); + const viewRect = getLayoutRect( + boxLayoutParams, + refContainer, + padding + ); + box( + toolboxModel.get('orient'), + group, + toolboxModel.get('itemGap'), + viewRect.width, + viewRect.height + ); + positionElement( + group, + boxLayoutParams, + refContainer, + padding + ); + // Render background after group is layout // FIXME group.add(listComponentHelper.makeBackground(group.getBoundingRect(), toolboxModel)); diff --git a/src/component/visualMap/VisualMapView.ts b/src/component/visualMap/VisualMapView.ts index 1c24317071..76cf41f0fe 100644 --- a/src/component/visualMap/VisualMapView.ts +++ b/src/component/visualMap/VisualMapView.ts @@ -154,10 +154,11 @@ class VisualMapView extends ComponentView { const model = this.visualMapModel; const api = this.api; + const refContainer = layout.createBoxLayoutReference(model, api).refContainer; layout.positionElement( group, model.getBoxLayoutParams(), - {width: api.getWidth(), height: api.getHeight()} + refContainer ); } diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts index 8c778ab837..8a933cc772 100644 --- a/src/coord/CoordinateSystem.ts +++ b/src/coord/CoordinateSystem.ts @@ -20,7 +20,10 @@ import GlobalModel from '../model/Global'; import {ParsedModelFinder} from '../util/model'; import ExtensionAPI from '../core/ExtensionAPI'; -import { DimensionDefinitionLoose, ScaleDataValue, DimensionName } from '../util/types'; +import { + DimensionDefinitionLoose, ScaleDataValue, DimensionName, NullUndefined, CoordinateSystemDataLayout, + CoordinateSystemDataCoord +} from '../util/types'; import Axis from './Axis'; import { BoundingRect } from '../util/graphic'; import { MatrixArray } from 'zrender/src/core/matrix'; @@ -45,6 +48,8 @@ export interface CoordinateSystemCreator { /** * The instance get from `CoordinateSystemManger` is `CoordinateSystemMaster`. + * Consider a typical case: `grid` is a `CoordinateSystemMaster`, and it contains + * one or multiple `cartesian2d`s, which are `CoordinateSystem`s. */ export interface CoordinateSystemMaster { @@ -55,23 +60,45 @@ export interface CoordinateSystemMaster { model?: ComponentModel; + // Injected if required. + boxCoordinateSystem?: CoordinateSystem; + update?: (ecModel: GlobalModel, api: ExtensionAPI) => void; // This methods is also responsible for determining whether this // coordinate system is applicable to the given `finder`. // Each coordinate system will be tried, until one returns non- // null/undefined value. + // Aslo support + // const resultNumber = convertToPixel({someAxis: 0}, number); convertToPixel?( - ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue | ScaleDataValue[] - ): number | number[]; + ecModel: GlobalModel, + finder: ParsedModelFinder, + value: Parameters[0], + opt?: unknown + ): ReturnType | number | NullUndefined; + + // This methods is also responsible for determining whether this + // coordinate system is applicable to the given `finder`. + // Each coordinate system will be tried, until one returns non- + // null/undefined value. + convertToLayout?( + ecModel: GlobalModel, + finder: ParsedModelFinder, + value: Parameters[0], + opt?: unknown + ): ReturnType | NullUndefined; // This methods is also responsible for determining whether this // coordinate system is applicable to the given `finder`. // Each coordinate system will be tried, until one returns non- // null/undefined value. convertFromPixel?( - ecModel: GlobalModel, finder: ParsedModelFinder, pixelValue: number | number[] - ): number | number[]; + ecModel: GlobalModel, + finder: ParsedModelFinder, + pixelValue: Parameters[0], + opt?: unknown + ): ReturnType | NullUndefined; // @param point Point in global pixel coordinate system. // The signature of this method should be the same as `CoordinateSystemExecutive` @@ -113,25 +140,46 @@ export interface CoordinateSystem { /** * @param data * @param reserved Defined by the coordinate system itself - * @param out - * @return {Array.} point Point in global pixel coordinate system. + * @param out Fill it if passing, and return. For performance optimization. + * @return Point in global pixel coordinate system. + * An invalid returned point should be represented by `[NaN, NaN]`, + * rather than `null/undefined`. */ dataToPoint( - data: ScaleDataValue | ScaleDataValue[], - reserved?: any, + data: CoordinateSystemDataCoord, + opt?: unknown, out?: number[] ): number[]; + /** + * @param data See the meaning in `dataToPoint`. + * @param reserved Defined by the coordinate system itself + * @param out Fill it if passing, and return. For performance optimization. Vary by different coord sys. + * @return Layout in global pixel coordinate system. + * An invalid returned rect should be represented by `{x: NaN, y: NaN, width: NaN, height: NaN}`, + * Never return `null/undefined`. + */ + dataToLayout?( + data: CoordinateSystemDataCoord, + opt?: unknown, + out?: CoordinateSystemDataLayout + ): CoordinateSystemDataLayout; + /** * Some coord sys (like Parallel) might do not have `pointToData`, * or the meaning of this kind of features is not clear yet. * @param point point Point in global pixel coordinate system. - * @param clamp Clamp range + * @param out Fill it if passing, and return. For performance optimization. * @return data + * An invalid returned data should be represented by `[NaN, NaN]` or `NaN`, + * rather than `null/undefined`, which represents not-applicable in `convertFromPixel`. + * Return `OrdinalNumber` in ordianal (category axis) case. + * Return timestamp in time axis. */ pointToData?( point: number[], - clamp?: boolean + opt?: unknown, + out?: number | number[] ): number | number[]; // @param point Point in global pixel coordinate system. diff --git a/src/coord/View.ts b/src/coord/View.ts index 60fbb0318f..43d02ee8d3 100644 --- a/src/coord/View.ts +++ b/src/coord/View.ts @@ -282,19 +282,24 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy /** * Convert a (x, y) point to (lon, lat) data */ - pointToData(point: number[]): number[] { + pointToData(point: number[], reserved?: unknown, out?: number[]): number[] { + out = out || []; const invTransform = this.invTransform; return invTransform - ? v2ApplyTransform([], point, invTransform) - : [point[0], point[1]]; + ? v2ApplyTransform(out, point, invTransform) + : (out[0] = point[0], out[1] = point[1], out); } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: number[]): number[] { + convertToPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, value: number[] + ): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.dataToPoint(value) : null; } - convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]): number[] { + convertFromPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[] + ): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.pointToData(pixel) : null; } diff --git a/src/coord/calendar/Calendar.ts b/src/coord/calendar/Calendar.ts index 3a0909a002..381a9400e6 100644 --- a/src/coord/calendar/Calendar.ts +++ b/src/coord/calendar/Calendar.ts @@ -28,12 +28,14 @@ import { LayoutOrient, ScaleDataValue, OptionDataValueDate, - SeriesOption, - SeriesOnCalendarOptionMixin + CoordinateSystemDataLayout, } from '../../util/types'; import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; -import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; -import SeriesModel from '../../model/Series'; +import { + CoordinateSystem, CoordinateSystemMaster, +} from '../CoordinateSystem'; +import { expandOrShrinkRect } from '../../util/graphic'; +import { injectCoordSysByOption, simpleCoordSysInjectionProvider } from '../../core/CoordinateSystem'; // (24*60*60*1000) const PROXIMATE_ONE_DAY = 86400000; @@ -81,8 +83,7 @@ export interface CalendarParsedDateInfo { date: Date } -export interface CalendarCellRect { - contentShape: RectLike +interface CalendarCellRect { center: number[] tl: number[] tr: number[] @@ -119,6 +120,7 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { constructor(calendarModel: CalendarModel, ecModel: GlobalModel, api: ExtensionAPI) { this._model = calendarModel; + this._update(ecModel, api); } // Required in createListFromData getDimensionsInfo = Calendar.getDimensionsInfo; @@ -203,7 +205,7 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { return this.getDateInfo(date); } - update(ecModel: GlobalModel, api: ExtensionAPI) { + private _update(ecModel: GlobalModel, api: ExtensionAPI) { this._firstDayOfWeek = +this._model.getModel('dayLabel').get('firstDay'); this._orient = this._model.get('orient'); @@ -250,7 +252,12 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { */ // TODO Clamp of calendar is not same with cartesian coordinate systems. // It will return NaN if data exceeds. - dataToPoint(data: OptionDataValueDate | OptionDataValueDate[], clamp?: boolean) { + dataToPoint( + data: OptionDataValueDate | OptionDataValueDate[], + clamp?: boolean, + out?: number[] + ): number[] { + out = out || []; zrUtil.isArray(data) && (data = data[0]); clamp == null && (clamp = true); @@ -263,24 +270,22 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { dayInfo.time >= range.start.time && dayInfo.time < range.end.time + PROXIMATE_ONE_DAY )) { - return [NaN, NaN]; + out[0] = out[1] = NaN; + return out; } const week = dayInfo.day; const nthWeek = this._getRangeInfo([range.start.time, date]).nthWeek; if (this._orient === 'vertical') { - return [ - this._rect.x + week * this._sw + this._sw / 2, - this._rect.y + nthWeek * this._sh + this._sh / 2 - ]; - + out[0] = this._rect.x + week * this._sw + this._sw / 2; + out[1] = this._rect.y + nthWeek * this._sh + this._sh / 2; } - - return [ - this._rect.x + nthWeek * this._sw + this._sw / 2, - this._rect.y + week * this._sh + this._sh / 2 - ]; + else { + out[0] = this._rect.x + nthWeek * this._sw + this._sw / 2; + out[1] = this._rect.y + week * this._sh + this._sh / 2; + } + return out; } @@ -294,42 +299,53 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { return date && date.time; } + dataToLayout( + data: OptionDataValueDate | OptionDataValueDate[], + clamp?: boolean, + out?: CoordinateSystemDataLayout + ): CoordinateSystemDataLayout { + out = out || {} as CoordinateSystemDataLayout; + const rect = out.rect = out.rect || {} as RectLike; + const contentRect = out.contentRect = out.contentRect || {} as RectLike; + const point = this.dataToPoint(data, clamp); + + rect.x = point[0] - (this._sw) / 2; + rect.y = point[1] - (this._sh) / 2; + rect.width = this._sw; + rect.height = this._sh; + + BoundingRect.copy(contentRect, rect); + expandOrShrinkRect(contentRect, this._lineWidth / 2, true, true); + + return out; + } + /** * Convert a time date item to (x, y) four point. */ - dataToRect(data: OptionDataValueDate | OptionDataValueDate[], clamp?: boolean): CalendarCellRect { + dataToCalendarLayout( + data: OptionDataValueDate | OptionDataValueDate[], + clamp?: boolean, + ): CalendarCellRect { const point = this.dataToPoint(data, clamp); - return { - contentShape: { - x: point[0] - (this._sw - this._lineWidth) / 2, - y: point[1] - (this._sh - this._lineWidth) / 2, - width: this._sw - this._lineWidth, - height: this._sh - this._lineWidth - }, - center: point, - tl: [ point[0] - this._sw / 2, point[1] - this._sh / 2 ], - tr: [ point[0] + this._sw / 2, point[1] - this._sh / 2 ], - br: [ point[0] + this._sw / 2, point[1] + this._sh / 2 ], - bl: [ point[0] - this._sw / 2, point[1] + this._sh / 2 - ] - + ], }; } @@ -351,11 +367,20 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { return this._getDateByWeeksAndDay(nthX, nthY - 1, range); } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue | ScaleDataValue[]) { + convertToPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue | ScaleDataValue[] + ) { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.dataToPoint(value) : null; } + convertToLayout( + ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue | ScaleDataValue[] + ) { + const coordSys = getCoordSys(finder); + return coordSys === this ? coordSys.dataToLayout(value) : null; + } + convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.pointToData(pixel) : null; @@ -525,11 +550,13 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { calendarModel.coordinateSystem = calendar; }); - ecModel.eachSeries(function (calendarSeries: SeriesModel) { - if (calendarSeries.get('coordinateSystem') === 'calendar') { - // Inject coordinate system - calendarSeries.coordinateSystem = calendarList[calendarSeries.get('calendarIndex') || 0]; - } + // Inject coordinate system + ecModel.eachComponent((mainType, componentModel) => { + injectCoordSysByOption({ + targetModel: componentModel, + coordSysType: 'calendar', + coordSysProvider: simpleCoordSysInjectionProvider, + }); }); return calendarList; } diff --git a/src/coord/calendar/CalendarModel.ts b/src/coord/calendar/CalendarModel.ts index 98e9049346..1962600a43 100644 --- a/src/coord/calendar/CalendarModel.ts +++ b/src/coord/calendar/CalendarModel.ts @@ -36,6 +36,7 @@ import { } from '../../util/types'; import GlobalModel from '../../model/Global'; import Model from '../../model/Model'; +import { CoordinateSystemHostModel } from '../CoordinateSystem'; export interface CalendarMonthLabelFormatterCallbackParams { nameMap: string @@ -152,12 +153,14 @@ export interface CalendarOption extends ComponentOption, BoxLayoutOptionMixin { } } -class CalendarModel extends ComponentModel { +class CalendarModel extends ComponentModel implements CoordinateSystemHostModel { static type = 'calendar'; type = CalendarModel.type; coordinateSystem: Calendar; + static layoutMode = 'box' as const; + /** * @override */ @@ -185,7 +188,9 @@ class CalendarModel extends ComponentModel { static defaultOption: CalendarOption = { // zlevel: 0, - z: 2, + // As a most basic coord sys, `z` should be lower than + // other series and coord sys, such as, grid. + z: -50, left: 80, top: 60, diff --git a/src/coord/calendar/prepareCustom.ts b/src/coord/calendar/prepareCustom.ts index abd1436a9d..b17903dc48 100644 --- a/src/coord/calendar/prepareCustom.ts +++ b/src/coord/calendar/prepareCustom.ts @@ -17,7 +17,7 @@ * under the License. */ -import Calendar from './Calendar'; +import type Calendar from './Calendar'; import { OptionDataValueDate } from '../../util/types'; export default function calendarPrepareCustom(coordSys: Calendar) { @@ -41,8 +41,17 @@ export default function calendarPrepareCustom(coordSys: Calendar) { } }, api: { - coord: function (data: OptionDataValueDate, clamp?: boolean) { + coord: function ( + data: Parameters[0], + clamp?: Parameters[1] + ): ReturnType { return coordSys.dataToPoint(data, clamp); + }, + layout: function ( + data: Parameters[0], + clamp?: Parameters[1] + ): ReturnType { + return coordSys.dataToLayout(data, clamp); } } }; diff --git a/src/coord/cartesian/Cartesian2D.ts b/src/coord/cartesian/Cartesian2D.ts index 4072684d14..2528c7695d 100644 --- a/src/coord/cartesian/Cartesian2D.ts +++ b/src/coord/cartesian/Cartesian2D.ts @@ -158,8 +158,8 @@ class Cartesian2D extends Cartesian implements CoordinateSystem { return out; } - pointToData(point: number[], clamp?: boolean): number[] { - const out: number[] = []; + pointToData(point: number[], clamp?: boolean, out?: number[]): number[] { + out = out || []; if (this._invTransform) { return applyTransform(out, point, this._invTransform); } diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index d22ddef556..1b62f5a4ea 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -24,7 +24,7 @@ */ import {isObject, each, indexOf, retrieve3, keys} from 'zrender/src/core/util'; -import {getLayoutRect, LayoutRect} from '../../util/layout'; +import {createBoxLayoutReference, getLayoutRect, LayoutRect} from '../../util/layout'; import { createScaleByModel, ifAxisCrossZero, @@ -46,13 +46,14 @@ import {CoordinateSystemMaster} from '../CoordinateSystem'; import { ScaleDataValue } from '../../util/types'; import SeriesData from '../../data/SeriesData'; import OrdinalScale from '../../scale/Ordinal'; -import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; +import { findAxisModels, isCartesian2DInjectedAsDataCoordSys } from './cartesianAxisHelper'; import { CategoryAxisBaseOption, NumericAxisBaseOptionCommon } from '../axisCommonTypes'; import { AxisBaseModel } from '../AxisBaseModel'; import { isIntervalOrLogScale } from '../../scale/helper'; import { alignScaleTicks } from '../axisAlignTicks'; import IntervalScale from '../../scale/Interval'; import LogScale from '../../scale/Log'; +import { injectCoordSysByOption } from '../../core/CoordinateSystem'; type Cartesian2DDimensionName = 'x' | 'y'; @@ -169,16 +170,12 @@ class Grid implements CoordinateSystemMaster { */ resize(gridModel: GridModel, api: ExtensionAPI, ignoreContainLabel?: boolean): void { - const boxLayoutParams = gridModel.getBoxLayoutParams(); const isContainLabel = !ignoreContainLabel && gridModel.get('containLabel'); - const gridRect = getLayoutRect( - boxLayoutParams, { - width: api.getWidth(), - height: api.getHeight() - }); - - this._rect = gridRect; + const layoutRef = createBoxLayoutReference(gridModel, api); + const gridRect = this._rect = getLayoutRect(gridModel.getBoxLayoutParams(), layoutRef.refContainer); + // PENDING: whether to support that if the input `coord` is out of the base coord sys, + // do not render anything. At present, the behavior is undefined. const axesList = this._axesList; @@ -468,7 +465,10 @@ class Grid implements CoordinateSystemMaster { }); ecModel.eachSeries(function (seriesModel) { - if (isCartesian2DSeries(seriesModel)) { + // If pie (or other similar series) use cartesian2d, the unionExtent logic below is + // wrong, therefore skip it temporarily. See also in `defaultAxisExtentFromData.ts`. + // TODO: support union extent in this case. + if (isCartesian2DInjectedAsDataCoordSys(seriesModel)) { const axesModelMap = findAxisModels(seriesModel); const xAxisModel = axesModelMap.xAxisModel; const yAxisModel = axesModelMap.yAxisModel; @@ -535,36 +535,40 @@ class Grid implements CoordinateSystemMaster { // Inject the coordinateSystems into seriesModel ecModel.eachSeries(function (seriesModel) { - if (!isCartesian2DSeries(seriesModel)) { - return; - } - - const axesModelMap = findAxisModels(seriesModel); - const xAxisModel = axesModelMap.xAxisModel; - const yAxisModel = axesModelMap.yAxisModel; + injectCoordSysByOption({ + targetModel: seriesModel, + coordSysType: 'cartesian2d', + coordSysProvider: coordSysProvider + }); - const gridModel = xAxisModel.getCoordSysModel(); + function coordSysProvider() { + const axesModelMap = findAxisModels(seriesModel); + const xAxisModel = axesModelMap.xAxisModel; + const yAxisModel = axesModelMap.yAxisModel; - if (__DEV__) { - if (!gridModel) { - throw new Error( - 'Grid "' + retrieve3( - xAxisModel.get('gridIndex'), - xAxisModel.get('gridId'), - 0 - ) + '" not found' - ); - } - if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) { - throw new Error('xAxis and yAxis must use the same grid'); + const gridModel = xAxisModel.getCoordSysModel(); + + if (__DEV__) { + if (!gridModel) { + throw new Error( + 'Grid "' + retrieve3( + xAxisModel.get('gridIndex'), + xAxisModel.get('gridId'), + 0 + ) + '" not found' + ); + } + if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) { + throw new Error('xAxis and yAxis must use the same grid'); + } } - } - const grid = gridModel.coordinateSystem as Grid; + const grid = gridModel.coordinateSystem as Grid; - seriesModel.coordinateSystem = grid.getCartesian( - xAxisModel.componentIndex, yAxisModel.componentIndex - ); + return grid.getCartesian( + xAxisModel.componentIndex, yAxisModel.componentIndex + ); + } }); return grids; diff --git a/src/coord/cartesian/GridModel.ts b/src/coord/cartesian/GridModel.ts index 5e2f438608..7318a49498 100644 --- a/src/coord/cartesian/GridModel.ts +++ b/src/coord/cartesian/GridModel.ts @@ -19,11 +19,15 @@ import ComponentModel from '../../model/Component'; -import { ComponentOption, BoxLayoutOptionMixin, ZRColor, ShadowOptionMixin } from '../../util/types'; +import { + ComponentOption, BoxLayoutOptionMixin, ZRColor, ShadowOptionMixin, +} from '../../util/types'; import Grid from './Grid'; import { CoordinateSystemHostModel } from '../CoordinateSystem'; -export interface GridOption extends ComponentOption, BoxLayoutOptionMixin, ShadowOptionMixin { +export interface GridOption + extends ComponentOption, BoxLayoutOptionMixin, ShadowOptionMixin { + mainType?: 'grid'; show?: boolean; diff --git a/src/coord/cartesian/cartesianAxisHelper.ts b/src/coord/cartesian/cartesianAxisHelper.ts index 6f977bf2a9..9c746aecb1 100644 --- a/src/coord/cartesian/cartesianAxisHelper.ts +++ b/src/coord/cartesian/cartesianAxisHelper.ts @@ -98,10 +98,21 @@ export function layout( return layout; } -export function isCartesian2DSeries(seriesModel: SeriesModel): boolean { +export function isCartesian2DDeclaredSeries(seriesModel: SeriesModel): boolean { return seriesModel.get('coordinateSystem') === 'cartesian2d'; } +/** + * Note: If pie (or other similar series) use cartesian2d, here + * option `seriesModel.get('coordinateSystem') === 'cartesian2d'` + * and `seriesModel.coordinateSystem !== cartesian2dCoordSysInstance` + * and `seriesModel.boxCoordinateSystem === cartesian2dCoordSysInstance`, + * the logic below is probably wrong, therefore skip it temporarily. + */ +export function isCartesian2DInjectedAsDataCoordSys(seriesModel: SeriesModel): boolean { + return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d'; +} + export function findAxisModels(seriesModel: SeriesModel): { xAxisModel: CartesianAxisModel; yAxisModel: CartesianAxisModel; diff --git a/src/coord/cartesian/defaultAxisExtentFromData.ts b/src/coord/cartesian/defaultAxisExtentFromData.ts index 736eafee91..ca4bdcbacf 100644 --- a/src/coord/cartesian/defaultAxisExtentFromData.ts +++ b/src/coord/cartesian/defaultAxisExtentFromData.ts @@ -20,7 +20,9 @@ import * as echarts from '../../core/echarts'; import { createHashMap, each, HashMap, hasOwn, keys, map } from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; -import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; +import { + isCartesian2DDeclaredSeries, findAxisModels, isCartesian2DInjectedAsDataCoordSys +} from './cartesianAxisHelper'; import { getDataDimensionsOnAxis, unionAxisExtentFromData } from '../axisHelper'; import { AxisBaseModel } from '../AxisBaseModel'; import Axis from '../Axis'; @@ -50,7 +52,7 @@ echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER + 10, { getTargetSeries: function (ecModel) { const seriesModelMap = createHashMap(); ecModel.eachSeries(function (seriesModel: SeriesModel) { - isCartesian2DSeries(seriesModel) && seriesModelMap.set(seriesModel.uid, seriesModel); + isCartesian2DDeclaredSeries(seriesModel) && seriesModelMap.set(seriesModel.uid, seriesModel); }); return seriesModelMap; }, @@ -71,7 +73,12 @@ function prepareDataExtentOnAxis( seriesRecords: SeriesRecord[] ): void { ecModel.eachSeries(function (seriesModel: SeriesModel) { - if (!isCartesian2DSeries(seriesModel)) { + // If pie (or other similar series) use cartesian2d, the logic below is + // probably wrong, therefore skip it temporarily. + // TODO: support union extent in this case. + // e.g. make a fake seriesData by series.coord/series.center, and it can be + // performed by data processing (such as, filter), and applied here. + if (!isCartesian2DInjectedAsDataCoordSys(seriesModel)) { return; } diff --git a/src/coord/geo/Geo.ts b/src/coord/geo/Geo.ts index 130d959163..e4ff951f86 100644 --- a/src/coord/geo/Geo.ts +++ b/src/coord/geo/Geo.ts @@ -215,32 +215,38 @@ class Geo extends View { } } - pointToData(point: number[]) { + pointToData(point: number[], reserved?: unknown, out?: number[]) { const projection = this.projection; if (projection) { // projection may return null point. point = projection.unproject(point); } - return point && this.pointToProjected(point); + // FIXME: if no `point`, should return [NaN, NaN], rather than undefined. + // null/undefined has special meaning in `convertFromPixel`. + return point && this.pointToProjected(point, out); } /** * Point to projected data. Same with pointToData when projection is used. */ - pointToProjected(point: number[]) { - return super.pointToData(point); + pointToProjected(point: number[], out?: number[]) { + return super.pointToData(point, 0, out); } projectedToPoint(projected: number[], noRoam?: boolean, out?: number[]) { return super.dataToPoint(projected, noRoam, out); } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: number[]): number[] { + convertToPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, value: number[] + ): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.dataToPoint(value) : null; } - convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]): number[] { + convertFromPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[] + ): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.pointToData(pixel) : null; } diff --git a/src/coord/geo/geoCreator.ts b/src/coord/geo/geoCreator.ts index f51700ae0b..ee5f886190 100644 --- a/src/coord/geo/geoCreator.ts +++ b/src/coord/geo/geoCreator.ts @@ -27,13 +27,13 @@ import MapSeries, { MapSeriesOption } from '../../chart/map/MapSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; import { CoordinateSystemCreator } from '../CoordinateSystem'; import { NameMap } from './geoTypes'; -import { SeriesOption, SeriesOnGeoOptionMixin } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; import type Model from '../../model/Model'; import type GlobalModel from '../../model/Global'; -import type SeriesModel from '../../model/Series'; import type ComponentModel from '../../model/Component'; import * as vector from 'zrender/src/core/vector'; +import { injectCoordSysByOption } from '../../core/CoordinateSystem'; +import { SINGLE_REFERRING } from '../../util/model'; export type resizeGeoType = typeof resizeGeo; @@ -95,8 +95,9 @@ function resizeGeo(this: Geo, geoModel: ComponentModel[0]; - boxLayoutOption.aspect = aspect; - viewRect = layout.getLayoutRect(boxLayoutOption, { - width: viewWidth, - height: viewHeight - }); + viewRect = layout.getLayoutRect(boxLayoutOption, refContainer); } this.setViewRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height); @@ -201,13 +198,18 @@ class GeoCreator implements CoordinateSystemCreator { }); ecModel.eachSeries(function (seriesModel) { - const coordSys = seriesModel.get('coordinateSystem'); - if (coordSys === 'geo') { - const geoIndex = ( - seriesModel as SeriesModel - ).get('geoIndex') || 0; - seriesModel.coordinateSystem = geoList[geoIndex]; - } + injectCoordSysByOption({ + targetModel: seriesModel, + coordSysType: 'geo', + coordSysProvider() { + const geoModel = seriesModel.subType === 'map' + ? (seriesModel as MapSeries).getHostGeoModel() + : seriesModel.getReferringComponents( + 'geo', SINGLE_REFERRING + ).models[0] as GeoModel; + return geoModel && geoModel.coordinateSystem; + } + }); }); // If has map series diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 37dd9a60e4..451e4e04fb 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -18,31 +18,67 @@ */ import { RectLike } from 'zrender/src/core/BoundingRect'; -import type SeriesModel from '../../model/Series'; -import type { SeriesOnMatrixOptionMixin, SeriesOption } from '../../util/types'; -import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; +import type { CoordinateSystemDataLayout, NullUndefined, OrdinalNumber } from '../../util/types'; +import { + CoordinateSystem, CoordinateSystemMaster +} from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; -import MatrixModel from './MatrixModel'; +import MatrixModel, { + MatrixCoordRangeOption, + MatrixDimensionCellOption, MatrixDimensionLevelOption, MatrixDimensionModel +} from './MatrixModel'; import { LayoutRect, getLayoutRect } from '../../util/layout'; -import { MatrixDim } from './MatrixDim'; -import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; +import { ListIterator, ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; +import { eqNaN, isArray, retrieve2 } from 'zrender/src/core/util'; +import Point from 'zrender/src/core/Point'; +import { WH, XY } from '../../util/graphic'; +import Model from '../../model/Model'; +import { + MatrixCellLayoutInfo, MatrixCellLayoutInfoType, + MatrixDimensionCell, MatrixDimPair, MatrixXYLocator +} from './MatrixDim'; +import { mathMax, mathMin, parsePositionSizeOption } from '../../util/number'; +import { + createNaNRectLike, + MatrixClampOption, + parseCoordRangeOption, + resetXYLocatorRange, + xyLocatorRangeToRectOneDim +} from './matrixCoordHelper'; +import { MatrixBodyCorner, MatrixBodyOrCornerKind } from './MatrixBodyCorner'; +import { error } from '../../util/log'; +import { injectCoordSysByOption, simpleCoordSysInjectionProvider } from '../../core/CoordinateSystem'; + class Matrix implements CoordinateSystem, CoordinateSystemMaster { static readonly dimensions = ['x', 'y', 'value']; + /** + * @see fetchers in `model/referHelper.ts`, + * which is used to parse data in ordinal way. + * In most series only 'x' and 'y' is required, + * but some series, such as heatmap, can specify value. + */ static getDimensionsInfo() { - return ['x', 'y', 'value']; + return [ + {name: 'x', type: 'ordinal' as const}, + {name: 'y', type: 'ordinal' as const}, + {name: 'value'}, + ]; } readonly dimensions = Matrix.dimensions; readonly type = 'matrix'; private _model: MatrixModel; + private _dimModels: { + x: MatrixDimensionModel; + y: MatrixDimensionModel; + }; + private _dims: MatrixDimPair; + private _rect: LayoutRect; - private _xDim: MatrixDim; - private _yDim: MatrixDim; - private _lineWidth: number; static create(ecModel: GlobalModel, api: ExtensionAPI) { const matrixList: Matrix[] = []; @@ -53,119 +89,387 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { matrixModel.coordinateSystem = matrix; }); - ecModel.eachSeries(function (matrixSeries: SeriesModel) { - if (matrixSeries.get('coordinateSystem') === 'matrix') { - // Inject coordinate system - matrixSeries.coordinateSystem = matrixList[matrixSeries.get('matrixIndex') || 0]; - } + // Inject coordinate system + // PENDING: optimize to not to travel all components? + // (collect relevant components in ecModel only when model update?) + ecModel.eachComponent((mainType, componentModel) => { + injectCoordSysByOption({ + targetModel: componentModel, + coordSysType: 'matrix', + coordSysProvider: simpleCoordSysInjectionProvider, + }); }); + return matrixList; } constructor(matrixModel: MatrixModel, ecModel: GlobalModel, api: ExtensionAPI) { this._model = matrixModel; - this._xDim = new MatrixDim(matrixModel.get('x')); - this._yDim = new MatrixDim(matrixModel.get('y')); + const models = this._dimModels = { + x: matrixModel.getDimensionModel('x'), + y: matrixModel.getDimensionModel('y'), + }; + this._dims = { + x: models.x.dim, + y: models.y.dim, + }; + + this._resize(matrixModel, api); } getRect(): LayoutRect { return this._rect; } - getDim(dim: 'x' | 'y'): MatrixDim { - return dim === 'x' ? this._xDim : this._yDim; - } + private _resize(matrixModel: MatrixModel, api: ExtensionAPI) { + const dims = this._dims; + const dimModels = this._dimModels; + const viewportSize = new Point(api.getWidth(), api.getHeight()); + + const rect = this._rect = getLayoutRect(matrixModel.getBoxLayoutParams(), { + width: viewportSize.x, + height: viewportSize.y, + }); + + layOutUnitsOnDimension(dimModels, dims, rect, viewportSize, 0); + layOutUnitsOnDimension(dimModels, dims, rect, viewportSize, 1); - update(ecModel: GlobalModel, api: ExtensionAPI) { - this.resize(this._model, api); + layOutDimCellsRestInfoByUnit(0, dims); + layOutDimCellsRestInfoByUnit(1, dims); + + layOutBodyCornerCellMerge(this._model.getBody(), dims); + layOutBodyCornerCellMerge(this._model.getCorner(), dims); } - resize(matrixModel: MatrixModel, api: ExtensionAPI) { - const boxLayoutParams = matrixModel.getBoxLayoutParams(); - const gridRect = getLayoutRect( - boxLayoutParams, { - width: api.getWidth(), - height: api.getHeight() - }); - this._rect = gridRect; - this._lineWidth = matrixModel.getModel('backgroundStyle') - .getItemStyle().lineWidth || 0; + /** + * @implement + * - The input is allowed to be `[NaN/null/undefined, xxx]`/`[xxx, NaN/null/undefined]`; + * the return is `[NaN, xxxresult]`/`[xxxresult, NaN]` or clamped boundary value if + * `clamp` passed. This is for the usage that only get coord on single x or y. + * - Alwasy return an numeric array, but never be null/undefined. + * If it can not be located or invalid, return `[NaN, NaN]`. + */ + dataToPoint( + data: MatrixCoordRangeOption[], + opt?: Parameters[1], + out?: number[] + ): number[] { + out = out || []; + + this.dataToLayout(data, opt, _dtpOutDataToLayout); + + out[0] = _dtpOutDataToLayout.rect.x + _dtpOutDataToLayout.rect.width / 2; + out[1] = _dtpOutDataToLayout.rect.y + _dtpOutDataToLayout.rect.height / 2; + return out; } - dataToPoint(data: [string, string]): number[] { - const xCell = this._xDim.getCell(data[0]); - const yCell = this._yDim.getCell(data[1]); - if (!xCell || !yCell) { - // Point not found - return [NaN, NaN]; + /** + * @implement + * - The input is allowed to be `[NaN/null/undefined, xxx]`/`[xxx, NaN/null/undefined]`; + * the return is `{x: NaN, width: NaN, y: xxxresulty, height: xxxresulth}`/ + * `{y: NaN, height: NaN, x: xxxresultx, width: xxxresultw}` or clamped boundary value + * if `clamp` passed. This is for the usage that only get coord on single x or y. + * - The returned `out.rect` and `out.matrixXYLocatorRange` is always an object or an 2d-array, + * but never be null/undefined. If it cannot be located or invalid, `NaN` is in their + * corresponding number props. + * - Do not provide `out.contentRect` currently until real requirements come, because it's + * allowed to input non-leaf dimension x/y, which determines a rect covering multiple cells + * (even not merged), in which case the padding and borderWidth can not be determined to + * make a contentRect. Therefore only return `out.rect` in any case for consistency. + */ + dataToLayout( + data: MatrixCoordRangeOption[], + opt?: { + // No clamp by default, considering the possibility of supporting dataZoom (overflow/scroll). + clamp?: MatrixClampOption | NullUndefined; + // Expand if cell merging is encountered. + // - `false`: If intersecting with a rect of merged cells, expand the result to cover it. + // This is the default option, becuase `series.data` do not support the format + // `MatrixCoordRangeOption` (e.g., `[[3,5], [5,8]]`), thus merged cells can only + // be located by single cell locators (e.g., `[3, 5]`). + // - `true`: regardless of cell merging, even if the resulting rect spans accorss the merged cells. + ignoreMergeCells?: boolean; + }, + out?: CoordinateSystemDataLayout + ): CoordinateSystemDataLayout { + const dims = this._dims; + + out = out || {} as CoordinateSystemDataLayout; + const outRect = out.rect = out.rect || {} as RectLike; + outRect.x = outRect.y = outRect.width = outRect.height = NaN; + const outLocRange = out.matrixXYLocatorRange = resetXYLocatorRange(out.matrixXYLocatorRange); + + if (!isArray(data)) { + if (__DEV__) { + error('Input data must be an array in `convertToLayout`, `convertToPixel`'); + } + return out; } - const xLeavesCnt = this._xDim.getLeavesCount(); - const yLeavesCnt = this._yDim.getLeavesCount(); - const xHeight = this._xDim.getHeight(); - const yHeight = this._yDim.getHeight(); - const cellWidth = this._rect.width / (xLeavesCnt + yHeight) * xCell.colSpan; - const cellHeight = this._rect.height / (yLeavesCnt + xHeight) * yCell.rowSpan; - return [ - this._rect.x + this._rect.width / (xLeavesCnt + yHeight) - * (xCell.colId + yHeight) + cellWidth / 2, - this._rect.y + this._rect.height / (yLeavesCnt + xHeight) - * (yCell.colId + xHeight) + cellHeight / 2 - ]; - } + parseCoordRangeOption( + outLocRange, + null, + data, + dims, + retrieve2(opt && opt.clamp, MatrixClampOption.none) + ); - dataToRect(data: [string, string]): RectLike { - const xCell = this._xDim.getCell(data[0]); - const yCell = this._yDim.getCell(data[1]); - const xLeavesCnt = this._xDim.getLeavesCount(); - const yLeavesCnt = this._yDim.getLeavesCount(); - const xHeight = this._xDim.getHeight(); - const yHeight = this._yDim.getHeight(); - const cellWidth = this._rect.width / (xLeavesCnt + yHeight) * xCell.colSpan; - const cellHeight = this._rect.height / (yLeavesCnt + xHeight) * yCell.rowSpan; - const halfLineWidth = this._lineWidth / 2; - return { - x: this._rect.x + this._rect.width / (xLeavesCnt + yHeight) - * (xCell.colId + yHeight) + halfLineWidth, - y: this._rect.y + this._rect.height / (yLeavesCnt + xHeight) - * (yCell.colId + xHeight) + halfLineWidth, - width: cellWidth - halfLineWidth * 2, - height: cellHeight - halfLineWidth * 2 - }; + if (!opt || !opt.ignoreMergeCells) { + if (!opt || opt.clamp !== MatrixClampOption.corner) { + this._model.getBody().expandRangeByCellMerge(outLocRange); + } + if (!opt || opt.clamp !== MatrixClampOption.body) { + this._model.getCorner().expandRangeByCellMerge(outLocRange); + } + } + + xyLocatorRangeToRectOneDim(outRect, outLocRange, dims, 0); + xyLocatorRangeToRectOneDim(outRect, outLocRange, dims, 1); + + return out; } - pointToData(point: number[]): number[] { - const xLeavesCnt = this._xDim.getLeavesCount(); - const yLeavesCnt = this._yDim.getLeavesCount(); - const xHeight = this._xDim.getHeight(); - const yHeight = this._yDim.getHeight(); - const cellWidth = this._rect.width / (xLeavesCnt + yHeight); - const cellHeight = this._rect.height / (yLeavesCnt + xHeight); - const xIdx = Math.floor((point[0] - this._rect.x) / cellWidth); - const yIdx = Math.floor((point[1] - this._rect.y) / cellHeight); + /** + * The returned locator pair can be the input of `dataToPoint` or `dataToLayout`. + * + * If point[0] is out of the matrix rect, + * the out[0] is NaN; + * else if it is on the right of top-left corner of body, + * the out[0] is the oridinal number (>= 0). + * else + * out[0] is the locator for corner or header (<= 0). + * + * The same rule goes for point[1] and out[1]. + * + * But point[0] and point[1] are calculated separately, i.e., + * the reuslt can be `[1, NaN]` or `[NaN, 1]` if only one dimension is out of boundary. + * + * @implement + */ + pointToData( + point: number[], + opt?: { + clamp?: MatrixClampOption | NullUndefined + }, + out?: MatrixXYLocator[] + ): MatrixXYLocator[] { + const dims = this._dims; + pointToDataOneDimPrepareCtx(_tmpCtxPointToData, 0, dims, point, opt && opt.clamp); + pointToDataOneDimPrepareCtx(_tmpCtxPointToData, 1, dims, point, opt && opt.clamp); + + out = out || []; + out[0] = out[1] = NaN; + + if (_tmpCtxPointToData.y === CtxPointToDataAreaType.inCorner + && _tmpCtxPointToData.x === CtxPointToDataAreaType.inBody + ) { + pointToDataOnlyHeaderFillOut(_tmpCtxPointToData, out, 0, dims); + } + else if (_tmpCtxPointToData.x === CtxPointToDataAreaType.inCorner + && _tmpCtxPointToData.y === CtxPointToDataAreaType.inBody + ) { + pointToDataOnlyHeaderFillOut(_tmpCtxPointToData, out, 1, dims); + } + else { + pointToDataBodyCornerFillOut(_tmpCtxPointToData, out, 0, dims); + pointToDataBodyCornerFillOut(_tmpCtxPointToData, out, 1, dims); + } - const xCell = this._xDim.getCellByColId(xIdx - yHeight); - const yCell = this._yDim.getCellByColId(yIdx - xHeight); + return out; + } - return [xCell.colId, yCell.rowId, xCell.colSpan, yCell.rowSpan]; + convertToPixel( + ecModel: GlobalModel, + finder: ParsedModelFinder, + value: Parameters[0], + opt?: Parameters[1], + ): ReturnType | NullUndefined { + const coordSys = getCoordSys(finder); + return coordSys === this ? coordSys.dataToPoint(value, opt) : undefined; } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: [string, string]) { + convertToLayout( + ecModel: GlobalModel, + finder: ParsedModelFinder, + value: Parameters[0], + opt?: Parameters[1], + ): ReturnType | NullUndefined { const coordSys = getCoordSys(finder); - return coordSys === this ? coordSys.dataToPoint(value) : null; + return coordSys === this ? coordSys.dataToLayout(value, opt) : undefined; } - convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { + convertFromPixel( + ecModel: GlobalModel, + finder: ParsedModelFinder, + pixel: Parameters[0], + opt?: Parameters[1], + ): ReturnType | NullUndefined { const coordSys = getCoordSys(finder); - return coordSys === this ? coordSys.pointToData(pixel) : null; + return coordSys === this ? coordSys.pointToData(pixel, opt) : undefined; + } + + containPoint( + point: number[] + ): boolean { + return this._rect.contain(point[0], point[1]); + } + +} + +const _dtpOutDataToLayout = {rect: createNaNRectLike()}; +const _ptdLevelIt = new ListIterator(); +const _ptdDimCellIt = new ListIterator(); + + +function layOutUnitsOnDimension( + dimModels: Matrix['_dimModels'], + dims: MatrixDimPair, + matrixRect: RectLike, + viewportSize: Point, + dimIdx: number +): void { + const otherDimIdx = 1 - dimIdx; + const thisDim = dims[XY[dimIdx]]; + const otherDim = dims[XY[otherDimIdx]]; + // Notice: If matrix.x/y.show is false, still lay out, to ensure the + // consistent return of `dataToLayout`. + const otherDimShow = otherDim.shouldShow(); + + // Reset + for (const it = thisDim.resetCellIterator(); it.next();) { + it.item.wh = it.item.xy = NaN; + } + for (const it = otherDim.resetLayoutIterator(null, dimIdx); it.next();) { + it.item.wh = it.item.xy = NaN; + } + + // Set specified size from option. + let restSize = matrixRect[WH[dimIdx]]; + let restCellsCount = thisDim.getLocatorCount(dimIdx) + otherDim.getLocatorCount(dimIdx); + const tmpLevelModel = new Model(); + for (const it = otherDim.resetLevelIterator(); it.next();) { + // Consider `matrix.x.levelSize` and `matrix.x.levels[i].levelSize`. + tmpLevelModel.option = it.item.option; + tmpLevelModel.parentModel = dimModels[XY[otherDimIdx]]; + layOutSpecified(it.item, otherDimShow ? tmpLevelModel.get('levelSize') : 0); + } + const tmpCellModel = new Model(); + for (const it = thisDim.resetCellIterator(); it.next();) { + // Only leaf support size specification, to avoid unnecessary complexity. + if (it.item.type === MatrixCellLayoutInfoType.leaf) { + tmpCellModel.option = it.item.option; + tmpCellModel.parentModel = undefined; + layOutSpecified(it.item, tmpCellModel.get('size')); + } + } + function layOutSpecified(item: MatrixCellLayoutInfo, sizeOption: unknown): void { + const size = parseSizeOption(sizeOption, dimIdx, matrixRect, viewportSize); + if (!eqNaN(size)) { + item.wh = confineSize(size, restSize); + restSize = confineSize(restSize - item.wh); + restCellsCount--; + } + } + + // Set all sizes and positions to levels and leaf cells of which size is unspecified. + // Contents lay out based on matrix, rather than inverse; therefore do not support + // calculating size based on content, but allocate equally. + const computedCellWH = restCellsCount ? (restSize / restCellsCount) : 0; + // If all size specified, but some space remain (may also caused by matrix.x/y.show: false) + // do not align to the big most edge. + const notAlignToBigmost = !restCellsCount && restSize >= 1; // `1` for cumulative precision error. + let currXY = matrixRect[XY[dimIdx]]; + const maxLocator = thisDim.getLocatorCount(dimIdx) - 1; + const it = new ListIterator(); + + // Lay out levels of the perpendicular dim. + for (otherDim.resetLayoutIterator(it, dimIdx); it.next();) { + layOutUnspecified(it.item); } + for (thisDim.resetLayoutIterator(it, dimIdx); it.next();) { + layOutUnspecified(it.item); + } + function layOutUnspecified(item: MatrixCellLayoutInfo) { + if (eqNaN(item.wh)) { + item.wh = computedCellWH; + } + item.xy = currXY; + if (item.id[XY[dimIdx]] === maxLocator && !notAlignToBigmost) { + // Align to the rightmost border, consider cumulative precision error. + item.wh = matrixRect[XY[dimIdx]] + matrixRect[WH[dimIdx]] - item.xy; + } + currXY += item.wh; + } +} + +function layOutDimCellsRestInfoByUnit(dimIdx: number, dims: MatrixDimPair): void { + // Finally save layout info based on the unit leaves and levels. + for (const it = dims[XY[dimIdx]].resetCellIterator(); it.next();) { + const dimCell = it.item; + layOutRectOneDimBasedOnUnit(dimCell.rect, dimIdx, dimCell.id, dimCell.span, dims); + // Consider level varitation on tree leaves, should extend the size to touch matrix body + // to avoid weird appearance. + layOutRectOneDimBasedOnUnit(dimCell.rect, 1 - dimIdx, dimCell.id, dimCell.span, dims); + + if (dimCell.type === MatrixCellLayoutInfoType.nonLeaf) { + // `xy` and `wh` need to be saved in non-leaf since it supports locating by non-leaf + // in `dataToPoint` or `dataToLayout`. + dimCell.xy = dimCell.rect[XY[dimIdx]]; + dimCell.wh = dimCell.rect[WH[dimIdx]]; + } + } +} + +function layOutBodyCornerCellMerge(bodyOrCorner: MatrixBodyCorner, dims: MatrixDimPair) { + bodyOrCorner.travelExistingCells(cell => { + const computedSpan = cell.span; + if (computedSpan) { + const layoutRect = cell.spanRect; + const id = cell.id; + layOutRectOneDimBasedOnUnit(layoutRect, 0, id, computedSpan, dims); + layOutRectOneDimBasedOnUnit(layoutRect, 1, id, computedSpan, dims); + } + }); +} + +// Save to rect for rendering. +function layOutRectOneDimBasedOnUnit( + outRect: RectLike, dimIdx: number, id: Point, span: Point, dims: MatrixDimPair +): void { + outRect[WH[dimIdx]] = 0; + const locator = id[XY[dimIdx]]; + const dim = locator < 0 ? dims[XY[1 - dimIdx]] : dims[XY[dimIdx]]; + const layoutUnit = dim.getUnitLayoutInfo(dimIdx, id[XY[dimIdx]]); + outRect[XY[dimIdx]] = layoutUnit.xy; + outRect[WH[dimIdx]] = layoutUnit.wh; - containPoint(point: number[]): boolean { - console.warn('Not implemented.'); - return false; + if (span[XY[dimIdx]] > 1) { + const layoutUnit2 = dim.getUnitLayoutInfo(dimIdx, id[XY[dimIdx]] + span[XY[dimIdx]] - 1); + // Be careful the cumulative error - cell must be aligned. + outRect[WH[dimIdx]] = layoutUnit2.xy + layoutUnit2.wh - layoutUnit.xy; } } +/** + * Return NaN if not defined or invalid. + */ +function parseSizeOption( + sizeOption: unknown, + dimIdx: number, + matrixRect: RectLike, + viewportSize: Point +): number { + const sizeNum = parsePositionSizeOption(sizeOption, viewportSize[XY[dimIdx]]); + return confineSize(sizeNum, matrixRect[WH[dimIdx]]); +} + +function confineSize( + sizeNum: number, + sizeLimit?: number, +): number { + return Math.max(Math.min(sizeNum, retrieve2(sizeLimit, Infinity)), 0); +} + function getCoordSys(finder: ParsedModelFinderKnown): Matrix { const matrixModel = finder.matrixModel as MatrixModel; const seriesModel = finder.seriesModel; @@ -179,4 +483,150 @@ function getCoordSys(finder: ParsedModelFinderKnown): Matrix { return coordSys as Matrix; } +const CtxPointToDataAreaType = {inBody: 1, inCorner: 2, outside: 3}; +type CtxPointToDataAreaType = (typeof CtxPointToDataAreaType)[keyof typeof CtxPointToDataAreaType]; +type CtxPointToData = { + x: CtxPointToDataAreaType | NullUndefined; + y: CtxPointToDataAreaType | NullUndefined; + point: number[]; // If clamp required, this point is clamped after prepared. +}; +// For handy performance optimization in pointToData. +const _tmpCtxPointToData: CtxPointToData = {x: null, y: null, point: []}; + +function pointToDataOneDimPrepareCtx( + ctx: CtxPointToData, + dimIdx: number, + dims: MatrixDimPair, + point: number[], + clamp: MatrixClampOption | NullUndefined +) { + const thisDim = dims[XY[dimIdx]]; + const otherDim = dims[XY[1 - dimIdx]]; + + // Notice: considered cases: `matrix.x/y.show: false`, `matrix.x/y.data` is empty. + // In this cases the `layout.xy` is on the edge and `layout.wh` is `0`; they still can be + // use to calculate clampping. + + const bodyMaxUnit = thisDim.getUnitLayoutInfo(dimIdx, thisDim.getLocatorCount(dimIdx) - 1); + const body0Unit = thisDim.getUnitLayoutInfo(dimIdx, 0); + const cornerMinUnit = otherDim.getUnitLayoutInfo(dimIdx, -otherDim.getLocatorCount(dimIdx)); + const cornerMinus1Unit = otherDim.shouldShow() ? otherDim.getUnitLayoutInfo(dimIdx, -1) : null; + + let coord = ctx.point[dimIdx] = point[dimIdx]; // Transfer the oridinal coord. + + if (!body0Unit && !cornerMinus1Unit) { + ctx[XY[dimIdx]] = CtxPointToDataAreaType.outside; + return; + } + + if (clamp === MatrixClampOption.body) { + if (body0Unit) { + ctx[XY[dimIdx]] = CtxPointToDataAreaType.inBody; + coord = mathMin(bodyMaxUnit.xy + bodyMaxUnit.wh, mathMax(body0Unit.xy, coord)); + ctx.point[dimIdx] = coord; + } + else { + // If clamp to body, the result must not be in header. + ctx[XY[dimIdx]] = CtxPointToDataAreaType.outside; + } + return; + } + else if (clamp === MatrixClampOption.corner) { + if (cornerMinus1Unit) { + ctx[XY[dimIdx]] = CtxPointToDataAreaType.inCorner; + coord = mathMin(cornerMinus1Unit.xy + cornerMinus1Unit.wh, mathMax(cornerMinUnit.xy, coord)); + ctx.point[dimIdx] = coord; + } + else { + // If clamp to corner, the result must not be in body. + ctx[XY[dimIdx]] = CtxPointToDataAreaType.outside; + } + return; + } + + const pxLoc0 = body0Unit ? body0Unit.xy : cornerMinus1Unit ? cornerMinus1Unit.xy + cornerMinus1Unit.wh : NaN; + const pxMin = cornerMinUnit ? cornerMinUnit.xy : pxLoc0; + const pxMax = bodyMaxUnit ? bodyMaxUnit.xy + bodyMaxUnit.wh : pxLoc0; + + if (coord < pxMin) { + if (!clamp) { + // Quick pass for later calc, since mouse event on any place will enter this method if use `pointToData`. + ctx[XY[dimIdx]] = CtxPointToDataAreaType.outside; + return; + } + coord = pxMin; + } + else if (coord > pxMax) { + if (!clamp) { + ctx[XY[dimIdx]] = CtxPointToDataAreaType.outside; + return; + } + coord = pxMax; + } + ctx.point[dimIdx] = coord; // Save the updated coord. + + ctx[XY[dimIdx]] = pxLoc0 <= coord && coord <= pxMax ? CtxPointToDataAreaType.inBody + : pxMin <= coord && coord <= pxLoc0 ? CtxPointToDataAreaType.inCorner + : CtxPointToDataAreaType.outside; + + // Every props in ctx must be set in every branch of this method. +} + +// Assume partialOut has been set to NaN outside. +// This method may fill out[0] and out[1] in one call. +function pointToDataOnlyHeaderFillOut( + ctx: CtxPointToData, + partialOut: (OrdinalNumber | MatrixXYLocator)[], + dimIdx: number, + dims: MatrixDimPair, +): void { + const otherDimIdx = 1 - dimIdx; + + if (ctx[XY[dimIdx]] === CtxPointToDataAreaType.outside) { + return; + } + for (dims[XY[dimIdx]].resetCellIterator(_ptdDimCellIt); _ptdDimCellIt.next();) { + const cell = _ptdDimCellIt.item; + if (isCoordInRect(ctx.point[dimIdx], cell.rect, dimIdx) + && isCoordInRect(ctx.point[otherDimIdx], cell.rect, otherDimIdx) + ) { + // non-leaves are also allowed to be located. + // If the point is in x or y dimension cell area, should check both x and y coord to + // determine a cell; in this way a non-leaf cell can be determined. + partialOut[dimIdx] = cell.ordinal; + partialOut[otherDimIdx] = cell.id[XY[otherDimIdx]]; + return; + } + } +} + +// Assume partialOut has been set to NaN outside. +// This method may fill out[0] and out[1] in one call. +function pointToDataBodyCornerFillOut( + ctx: CtxPointToData, + partialOut: (OrdinalNumber | MatrixXYLocator)[], + dimIdx: number, + dims: MatrixDimPair, +): void { + if (ctx[XY[dimIdx]] === CtxPointToDataAreaType.outside) { + return; + } + const dim = ctx[XY[dimIdx]] === CtxPointToDataAreaType.inCorner + ? dims[XY[1 - dimIdx]] : dims[XY[dimIdx]]; + for (dim.resetLayoutIterator(_ptdLevelIt, dimIdx); _ptdLevelIt.next();) { + if (isCoordInLayoutInfo(ctx.point[dimIdx], _ptdLevelIt.item)) { + partialOut[dimIdx] = _ptdLevelIt.item.id[XY[dimIdx]]; + return; + } + } +} + +function isCoordInLayoutInfo(coord: number, cell: MatrixCellLayoutInfo): boolean { + return cell.xy <= coord && coord <= cell.xy + cell.wh; +} +function isCoordInRect(coord: number, rect: RectLike, dimIdx: number): boolean { + return rect[XY[dimIdx]] <= coord && coord <= rect[XY[dimIdx]] + rect[WH[dimIdx]]; +} + + export default Matrix; diff --git a/src/coord/matrix/MatrixBodyCorner.ts b/src/coord/matrix/MatrixBodyCorner.ts new file mode 100644 index 0000000000..e0e3be8ac6 --- /dev/null +++ b/src/coord/matrix/MatrixBodyCorner.ts @@ -0,0 +1,291 @@ +/* +* 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 { HashMap, createHashMap, each, extend, isArray, isObject } from 'zrender/src/core/util'; +import { NullUndefined } from '../../util/types'; +import type { MatrixXYLocator, MatrixDimPair, MatrixXYLocatorRange } from './MatrixDim'; +import { error } from '../../util/log'; +import Point from 'zrender/src/core/Point'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { MatrixBodyCornerCellOption, MatrixBodyOption, MatrixCornerOption } from './MatrixModel'; +import { + resolveXYLocatorRangeByCellMerge, + MatrixClampOption, + parseCoordRangeOption, + fillIdSpanFromLocatorRange, + createNaNRectLike, + isXYLocatorRangeInvalidOnDim, + resetXYLocatorRange, + cloneXYLocatorRange, +} from './matrixCoordHelper'; +import Model from '../../model/Model'; + + +/** + * Key: @see `makeCellMapKey` + */ +type MatrixModelBodyCornerCellMap = HashMap; + +export type MatrixBodyOrCornerKind = 'body' | 'corner'; +type MatrixBodyOrCornerOption = + ('body' extends TKind ? MatrixBodyOption : MatrixCornerOption); + +export interface MatrixBodyCornerCell { + // Represents col/row, serves as both id and locator. + // Actually its `x` is `xDimCell.id.x`; its `y` is `yDimCell.id.y` + id: Point; + // raw option in `matrix.body/corner.data[i]`. + option: MatrixBodyCornerCellOption | NullUndefined; + // `matrix.body/corner.data[i].coord` can locate a rect of cells (say, area). + // `inSpanOf` refers to the top-left cell, which represents that area. + // The top-left cell has `inSpanOf` refering to itself. + inSpanOf: MatrixBodyCornerCell | NullUndefined; + // If existing, it indicates cell merging, and this cell is the top-left cell + // of the merging area. + cellMergeOwner: boolean; + // Exist only if `cellMergeOwner: true`. + // In this case, it enusres that x > 1 and y > 1 and never out of boundary; + // othewise it is null/undefined. + span: Point | NullUndefined; + // Exist only if `cellMergeOwner: true`. + // Convey the same info with `id`+`span`, but be used in different calculation. + locatorRange: MatrixXYLocatorRange | NullUndefined; + // Exist only if `cellMergeOwner: true`. + spanRect: RectLike | NullUndefined; +} + +/** + * Lifetime: the same with `MatrixModel`, but different from `coord/Matrix`. + */ +export class MatrixBodyCorner { + + /** + * Be sparse, item exists only if needed. + */ + private _cellMap: MatrixModelBodyCornerCellMap | NullUndefined; + private _cellMergeOwnerList: MatrixBodyCornerCell[]; + + private _model: Model>; + private _dims: MatrixDimPair; + private _kind: TKind; + + constructor( + kind: TKind, + bodyOrCornerModel: Model>, + dims: MatrixDimPair + ) { + this._model = bodyOrCornerModel; + this._dims = dims; + this._kind = kind; + this._cellMergeOwnerList = []; + } + + /** + * Can not be called before series models initialization finished, since the ordinalMeta may + * use collect the values from `series.data` in series initialization. + */ + private _ensureCellMap(): MatrixModelBodyCornerCellMap { + const self = this; + + let _cellMap = self._cellMap; + if (!_cellMap) { + _cellMap = self._cellMap = createHashMap(); + fillCellMap(); + } + return _cellMap; + + function fillCellMap(): void { + type TmpParsed = { + id: Point; + span: Point; + locatorRange: MatrixXYLocatorRange; + option: MatrixBodyCornerCellOption; + cellMergeOwner: boolean; + }; + const parsedList: TmpParsed[] = []; + + let cellOptionList = self._model.getShallow('data'); + if (cellOptionList && !isArray(cellOptionList)) { + if (__DEV__) { + error(`matrix.${cellOptionList}.data must be an array if specified.`); + } + cellOptionList = null; + } + each(cellOptionList, (option, idx) => { + if (!isObject(option) || !isArray(option.coord)) { + if (__DEV__) { + error(`Illegal matrix.${self._kind}.data[${idx}], must be a {coord: [...], ...}`); + } + return; + } + + const locatorRange = resetXYLocatorRange([]); + let reasonArr: string[] | NullUndefined = null; + if (__DEV__) { + reasonArr = []; + } + parseCoordRangeOption( + locatorRange, reasonArr, option.coord, self._dims, MatrixClampOption[self._kind] + ); + if (isXYLocatorRangeInvalidOnDim(locatorRange, 0) || isXYLocatorRangeInvalidOnDim(locatorRange, 1)) { + if (__DEV__) { + error(`Can not determine cells by option matrix.${self._kind}.data[${idx}]: ` + + `${reasonArr.join(' ')}` + ); + } + return; + } + + const cellMergeOwner = option && option.mergeCells; + const parsed: TmpParsed = {id: new Point(), span: new Point(), locatorRange, option, cellMergeOwner}; + fillIdSpanFromLocatorRange(parsed, locatorRange); + + // The order of the `parsedList` determines the precedence of the styles, if there + // are overlaps between ranges specified in different items. Preserve the original + // order of `matrix.body/corner/data` to make it predictable for users. + parsedList.push(parsed); + }); + + // Resolve cell merging intersection - union to a larger rect. + const mergedMarkList: boolean[] = []; + for (let parsedIdx = 0; parsedIdx < parsedList.length; parsedIdx++) { + const parsed = parsedList[parsedIdx]; + if (!parsed.cellMergeOwner) { + continue; + } + const locatorRange = parsed.locatorRange; + resolveXYLocatorRangeByCellMerge(locatorRange, mergedMarkList, parsedList, parsedIdx); + for (let idx = 0; idx < parsedIdx; idx++) { + if (mergedMarkList[idx]) { + parsedList[idx].cellMergeOwner = false; + } + } + if (locatorRange[0][0] !== parsed.id.x || locatorRange[1][0] !== parsed.id.y) { + // The top-left cell of the unioned locatorRange is not this cell any more. + parsed.cellMergeOwner = false; + // Reconcile: simply use the last style and value option if multiple styles involved + // in a merged area, since there might be no commonly used merge strategy. + const newOption = extend({} as MatrixBodyCornerCellOption, parsed.option); + newOption.coord = null; + const newParsed: TmpParsed = { + id: new Point(), + span: new Point(), + locatorRange, + option: newOption, + cellMergeOwner: true + }; + fillIdSpanFromLocatorRange(newParsed, locatorRange); + parsedList.push(newParsed); + } + } + + // Assign options to cells. + each(parsedList, parsed => { + const topLeftCell = ensureBodyOrCornerCell(parsed.id.x, parsed.id.y); + if (parsed.cellMergeOwner) { + topLeftCell.cellMergeOwner = true; + topLeftCell.span = parsed.span; + topLeftCell.locatorRange = parsed.locatorRange; + topLeftCell.spanRect = createNaNRectLike(); + self._cellMergeOwnerList.push(topLeftCell); + } + if (!parsed.cellMergeOwner && !parsed.option) { + return; + } + for (let yidx = 0; yidx < parsed.span.y; yidx++) { + for (let xidx = 0; xidx < parsed.span.x; xidx++) { + const cell = ensureBodyOrCornerCell(parsed.id.x + xidx, parsed.id.y + yidx); + // If multiple style options are defined on a cell, the later ones takes precedence. + cell.option = parsed.option; + if (parsed.cellMergeOwner) { + cell.inSpanOf = topLeftCell; + } + } + } + }); + } // End of fillCellMap + + function ensureBodyOrCornerCell(x: MatrixXYLocator, y: MatrixXYLocator): MatrixBodyCornerCell { + const key = makeCellMapKey(x, y); + let cell = _cellMap.get(key); + if (!cell) { + cell = _cellMap.set(key, { + id: new Point(x, y), + option: null, + inSpanOf: null, + span: null, + spanRect: null, + locatorRange: null, + cellMergeOwner: false, + }); + } + return cell; + } + } + + /** + * Body cells or corner cell are not commonly defined specifically, especially in a large + * table, thus his is a sparse data structure - bodys or corner cells exist only if there + * are options specified to it (in `matrix.body.data` or `matrix.corner.data`); + * otherwise, return `NullUndefined`. + */ + getCell(xy: MatrixXYLocator[]): MatrixBodyCornerCell | NullUndefined { + // Assert xy do not contain NaN + return this._ensureCellMap().get(makeCellMapKey(xy[0], xy[1])); + } + + /** + * Only cell existing (has specific definition or props) will be travelled. + */ + travelExistingCells(cb: (cell: MatrixBodyCornerCell) => void): void { + this._ensureCellMap().each(cb); + } + + /** + * @param locatorRange Must be the return of `parseCoordRangeOption`. + */ + expandRangeByCellMerge(locatorRange: MatrixXYLocatorRange): void { + if ( + isXYLocatorRangeInvalidOnDim(locatorRange, 0) + && isXYLocatorRangeInvalidOnDim(locatorRange, 1) + && locatorRange[0][0] === locatorRange[0][1] + && locatorRange[1][0] === locatorRange[1][1] + ) { + // If it locates to a single cell, use this quick path to avoid travelling. + // It is based on the fact that any cell is not contained by more than one cell merging rect. + _tmpERBCMLocator[0] = locatorRange[0][0]; + _tmpERBCMLocator[1] = locatorRange[1][0]; + const cell = this.getCell(_tmpERBCMLocator); + const inSpanOf = cell.inSpanOf; + if (inSpanOf) { + cloneXYLocatorRange(locatorRange, inSpanOf.locatorRange); + return; + } + } + + const list = this._cellMergeOwnerList; + resolveXYLocatorRangeByCellMerge(locatorRange, null, list, list.length); + } + +} +const _tmpERBCMLocator: MatrixXYLocator[] = []; + +function makeCellMapKey(x: MatrixXYLocator, y: MatrixXYLocator): string { + return `${x}|${y}`; +} diff --git a/src/coord/matrix/MatrixDim.ts b/src/coord/matrix/MatrixDim.ts index 9261500dc7..a13b108271 100644 --- a/src/coord/matrix/MatrixDim.ts +++ b/src/coord/matrix/MatrixDim.ts @@ -1,206 +1,460 @@ -/* -* 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 { reduce, isString } from 'zrender/src/core/util'; -import { ParsedValue } from '../../util/types'; - -export type MatrixNodeOption = { - value?: string; - children?: MatrixNodeOption[]; -}; +import { + createHashMap, + defaults, + each, eqNaN, isArray, isObject, isString, + retrieve2, +} from 'zrender/src/core/util'; +import Point from 'zrender/src/core/Point'; +import OrdinalMeta from '../../data/OrdinalMeta'; +import { NullUndefined, OrdinalNumber } from '../../util/types'; +import Ordinal from '../../scale/Ordinal'; +import { + MatrixDimensionModel, MatrixDimensionCellOption, MatrixDimensionCellLooseOption, + MatrixDimensionLevelOption, + MatrixCoordValueOption, +} from './MatrixModel'; +import { WH, XY } from '../../util/graphic'; +import { ListIterator } from '../../util/model'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { + createNaNRectLike, setDimXYValue +} from './matrixCoordHelper'; +import { error } from '../../util/log'; +import { mathMax } from '../../util/number'; -export type MatrixNodeRawOption = string | MatrixNodeOption; -export interface MatrixDimOption { - show?: boolean; - data?: MatrixNodeOption[]; +export interface MatrixCellLayoutInfo { + type: MatrixCellLayoutInfoType; + // Represents col/row, serves as both id and locator. + // For a `MatrixDimensionCell`: + // it is `setDimXYValue(new Point(), dimIdx, firstLeafLocator, level - _levels.length)`. + // e.g., `id[dimIdx]` is `0` or `1` or `2` or `3` if there are at most `4` leaves in the tree. + // For a `MatrixDimensionLevelInfo`: + // it is `setDimXYValue(new Point(), dimIdx, 0, level - _levels.length)` + // e.g., `id[1 - dimIdx]` is `-3` or `-2` or `-1` if the tree has `3` levels. + // If negative, locate to corner cells; otherwise, locate to body cells. + id: Point; + // By pixel. Computed left-top x (for x dimension) or y (for y dimension). + // Used to locate. + xy: number; + // By pixel. Computed height (for x dimension) or width (for y dimension). + // Used to locate. + wh: number; + dim: MatrixDim; } -export interface MatrixDimRawOption { - data?: MatrixNodeRawOption[]; + +export const MatrixCellLayoutInfoType = { + level: 1, + leaf: 2, + nonLeaf: 3, +} as const; +export type MatrixCellLayoutInfoType = (typeof MatrixCellLayoutInfoType)[keyof typeof MatrixCellLayoutInfoType]; + +export type MatrixXYLocator = MatrixCellLayoutInfo['id']['x'] | MatrixCellLayoutInfo['id']['y']; +/** + * [[xmin, xmax], [ymin, ymax]] + * For each internal value, be NaN if invalid or out of boundary (never be null/undefined), + * otherwise must be valid locators. + * @see parseCoordRangeOption + * @see resetXYLocatorRange + */ +export type MatrixXYLocatorRange = MatrixXYLocator[][] & {__brand: 'MatrixXYLocatorRange'}; +/** + * [[xmin, xmax], [ymin, ymax]], be `NullUndefined` if illegal. + */ +export type MatrixXYCellLayoutInfoRange = (MatrixCellLayoutInfo | NullUndefined)[][]; + +export interface MatrixDimensionCell extends MatrixCellLayoutInfo { + // Computed col/row span. Always exists and >= 1. + // `span[XY[dimIdx]]` is actually the leaves count of this subtree. + span: Point; + // Start from 0, tree depth. + level: number; + // It is both `MatrixXYLocator` and `OrdinalNumber` and _cell[index]. + firstLeafLocator: MatrixXYLocator; + // Used to fatch its raw value by `matrixDim.getOrdinalMeta().category[ordinal]`, + // or feach cell by `_cells[ordinal]`. + // The ordinal of the leaf nodes is the same as `id.getOnDim(0)` for quick query, + // but not the same for non-leaf nodes. + ordinal: OrdinalNumber; + // Normalized raw option of `matrix.x/y.data[i]`, not include any parents option. + // Never be NullUndefined + option: MatrixDimensionCellOption; + // The layout rect for rendering. Available after matrix coordinate system resizing. + rect: RectLike; } -export interface MatrixCell { - value: string; - rowId: number; - rowSpan: number; - colId: number; - colSpan: number; +/** + * Computed properties of a certain tree level. + * In most cases this is used to describe level size or locate corner cells. + */ +export interface MatrixDimensionLevelInfo extends MatrixCellLayoutInfo { + // The raw option of `matrix.levels[i]` + option: MatrixDimensionLevelOption | NullUndefined; } +export type MatrixDimPair = { + x: MatrixDim; + y: MatrixDim; +}; + +/** + * Lifetime: the same with `MatrixModel`, but different from `coord/Matrix`. + */ export class MatrixDim { + // Use it to visit `cell.id` and `cell.span` + readonly dim: 'x' | 'y'; + // Must be `0 | 1`, corresponding to 'x' | 'y' + readonly dimIdx: number; + + // Under the current definition, every leave corresponds a unit cell, + // and leaves can serve as the locator of cells. + // Therefore make sure: + // - The first `_leavesCount` elements in `_cells` are leaves. + // - `_cells[leaf.id[XY[this.dimIdx]]]` is the leaf itself. + // - Leaves of each subtree are placed together, that is, the leaves of a dimCell are: + // `this._cells.slice(dimCell.firstLeafLocator, dimCell.span[XY[this.dimIdx]])` + private _cells: MatrixDimensionCell[] = []; + + // Can be visited by `_levels[cell.level]` or `_levels[cell.id[1 - dimIdx] + _levels.length]`. + // Items are never be null/undefined after initialized. + private _levels: MatrixDimensionLevelInfo[] = []; - private _option: MatrixDimOption; - private _cells: MatrixCell[]; - private _height: number; private _leavesCount: number; - constructor(option: MatrixDimOption) { - this._option = option || { data: [] }; - if (!this._option.data) { - this._option.data = []; - } - this._initCells(); - } + private _model: MatrixDimensionModel; + private _ordinalMeta: OrdinalMeta; + // Only for uniformly parsing. + private _scale: Ordinal; - getLeavesCount() { - if (this._leavesCount != null) { - return this._leavesCount; - } - const data = this._option.data; - if (!data) { - this._leavesCount = 0; - return 0; - } - if (isString(data)) { - this._leavesCount = 1; - return 1; - } - let cnt = 0; - for (let i = 0; i < data.length; i++) { - cnt += this._countLeaves(data[i]); - } - this._leavesCount = cnt; - return cnt; - } + private _uniqueValueGen: ReturnType; - getHeight() { - if (!this._option.show) { - return 0; - } - if (this._height != null) { - return this._height; - } - const data = this._option.data; - if (!data) { - return 0; + constructor(dim: 'x' | 'y', dimModel: MatrixDimensionModel) { + this.dim = dim; + this.dimIdx = dim === 'x' ? 0 : 1; + this._model = dimModel; + + this._uniqueValueGen = createUniqueValueGenerator(dim); + + let dimModelData = dimModel.get('data', true); + if (dimModelData != null && !isArray(dimModelData)) { + if (__DEV__) { + error(`Illegal echarts option - matrix.${this.dim}.data must be an array if specified.`); + } + dimModelData = []; } - if (isString(data)) { - return 1; + if (dimModelData) { + this._initByDimModelData(dimModelData); } - let height = 0; - for (let i = 0; i < data.length; i++) { - height = Math.max(height, this._countHeight(data[i])); + else { + this._initBySeriesData(); } - this._height = height; - return height; } - getCells() { - return this._cells; - } + private _initByDimModelData(dimModelData: MatrixDimensionCellLooseOption[]) { + const self = this; + const _cells = self._cells; + const _levels = self._levels; + const sameLocatorCellsLists: MatrixDimensionCell[][] = []; // Save for sorting. + let _cellCount = 0; + + self._leavesCount = traverseInitCells(dimModelData, 0, 0); + + postInitCells(); - getCell(value: ParsedValue) { - for (let i = 0; i < this._cells.length; i++) { - // value can be number while this._cells[i].value is string - // eslint-disable-next-line eqeqeq - if (this._cells[i].value == value) { - return this._cells[i]; + return; + + function traverseInitCells( + dimModelData: MatrixDimensionCellLooseOption[] | NullUndefined, + firstLeafLocator: number, + level: number + ): number { + let totalSpan = 0; + if (!dimModelData) { + return totalSpan; } + + each(dimModelData, (option, optionIdx) => { + let invalidOption = false; + let cellOption: MatrixDimensionCell['option']; + if (isString(option)) { + cellOption = {value: option}; + } + else if (isObject(option)) { + cellOption = option; + if (option.value != null && !isString(option.value)) { + invalidOption = true; + cellOption = {value: null}; + } + } + else { + cellOption = {value: null}; + if (option != null) { + invalidOption = true; + } + } + + if (invalidOption) { + if (__DEV__) { + error(`Illegal echarts option - matrix.${self.dim}.data[${optionIdx}]` + + ' must be `string | {value: string}`.' + ); + } + } + + const cell: MatrixDimensionCell = { + type: MatrixCellLayoutInfoType.nonLeaf, // Update to leaf later if it's a leaf. + ordinal: NaN, // Set it later. + level, + firstLeafLocator, + id: new Point(), // Set it in `_initCellsId`. + span: setDimXYValue(new Point(), self.dimIdx, 1, 1), + option: cellOption, + xy: NaN, + wh: NaN, + dim: self, + rect: createNaNRectLike(), + }; + _cellCount++; + (sameLocatorCellsLists[firstLeafLocator] + || (sameLocatorCellsLists[firstLeafLocator] = []) + ).push(cell); + + if (!_levels[level]) { + // Create a level only if at least one cell exists. + _levels[level] = { + type: MatrixCellLayoutInfoType.level, + xy: NaN, wh: NaN, option: null, id: new Point(), dim: self + }; + } + + const childrenSpan = traverseInitCells( + cellOption.children, firstLeafLocator, level + 1 + ); + const subSpan = Math.max(1, childrenSpan); + cell.span[XY[self.dimIdx]] = subSpan; + + totalSpan += subSpan; + firstLeafLocator += subSpan; + }); + + return totalSpan; } - } - getCellByColId(id: number) { - for (let i = 0; i < this._cells.length; i++) { - if (this._cells[i].colId === id) { - return this._cells[i]; + function postInitCells() { + // Sort to make sure the leaves are at the beginning, so that + // they can be used as the locator of body cells. + const categories: (string | NullUndefined)[] = []; + while (_cells.length < _cellCount) { + for (let locator = 0; locator < sameLocatorCellsLists.length; locator++) { + const cell = sameLocatorCellsLists[locator].pop(); + if (cell) { + cell.ordinal = categories.length; + const val = cell.option.value; + categories.push(val); + _cells.push(cell); + self._uniqueValueGen.calcDupBase(val); + } + } + } + self._uniqueValueGen.ensureValueUnique(categories, _cells); + + const ordinalMeta = self._ordinalMeta = new OrdinalMeta({ + categories: categories, + needCollect: false, + deduplication: false, + }); + self._scale = new Ordinal({ordinalMeta}); + + for (let idx = 0; idx < self._leavesCount; idx++) { + const leaf = self._cells[idx]; + leaf.type = MatrixCellLayoutInfoType.leaf; + // Handle the tree level variation: enlarge the span of the leaves to reach the body cells. + leaf.span[XY[1 - self.dimIdx]] = self._levels.length - leaf.level; } + + self._initCellsId(); + self._initLevelIdOptions(); } } - private _initCells(): void { - this._cells = []; - const data = this._option.data; - for (let i = 0, rowId = 0, colId = 0; i < data.length; i++) { - const node = data[i]; - const result = this._traverseInitCells(node, rowId, colId); - rowId = result.rowId; - colId = result.colId; - } + private _initBySeriesData() { + const self = this; + self._leavesCount = 0; + self._levels = [{ + type: MatrixCellLayoutInfoType.level, + xy: NaN, wh: NaN, option: null, id: new Point(), dim: self + }]; + self._initLevelIdOptions(); + + const ordinalMeta = self._ordinalMeta = new OrdinalMeta({ + needCollect: true, + deduplication: true, + onCollect: (value: unknown, ordinalNumber: number): void => { + const cell = self._cells[ordinalNumber] = { + type: MatrixCellLayoutInfoType.leaf, + ordinal: ordinalNumber, + level: 0, + firstLeafLocator: ordinalNumber, + id: new Point(), // Set it in `_initCellsId`. + span: setDimXYValue(new Point(), self.dimIdx, 1, 1), + // Theoretically `value` is from `dataset` or `series.data`, so it may be any type. + // Do not restrict this case for user's convenience, and here simply convert it to + // string for display. + option: {value: value + ''}, + xy: NaN, + wh: NaN, + dim: self, + rect: createNaNRectLike(), + }; + self._leavesCount++; + self._setCellId(cell); + }, + }); + self._scale = new Ordinal({ordinalMeta}); } - private _traverseInitCells( - node: MatrixNodeOption, - rowId: number, - colId: number = 0 - ): { rowId: number, colId: number } { - if (typeof node === 'string') { - // When node is a string, it's a leaf with colSpan of 1 - this._cells.push({ - value: node, - rowId, - colId, - rowSpan: 1, - colSpan: 1 - }); - return { rowId, colId: colId + 1 }; - } + private _setCellId(cell: MatrixDimensionCell) { + const levelsLen = this._levels.length; + const dimIdx = this.dimIdx; + setDimXYValue(cell.id, dimIdx, cell.firstLeafLocator, cell.level - levelsLen); + } - let currentColId = colId; - let totalColSpan = 0; - const childrenColSpans = []; + private _initCellsId() { + const levelsLen = this._levels.length; + const dimIdx = this.dimIdx; + each(this._cells, cell => { + setDimXYValue(cell.id, dimIdx, cell.firstLeafLocator, cell.level - levelsLen); + }); + } - if (node.children && node.children.length) { - for (const child of node.children) { - const result = this._traverseInitCells(child, rowId + 1, currentColId); - const childColSpan = result.colId - currentColId; - childrenColSpans.push(childColSpan); - currentColId = result.colId; - } - totalColSpan = reduce(childrenColSpans, (a, b) => a + b, 0); - } - else { - // If no children, it's a leaf node with colSpan of 1 - totalColSpan = 1; - } + private _initLevelIdOptions() { + const levelsLen = this._levels.length; + const dimIdx = this.dimIdx; + let levelOptionList = this._model.get('levels', true); + levelOptionList = isArray(levelOptionList) ? levelOptionList : []; - // Create cell for the current node - this._cells.push({ - value: node.value, - rowId, - colId, - rowSpan: 1, - colSpan: totalColSpan + each(this._levels, (levelCfg, level) => { + setDimXYValue(levelCfg.id, dimIdx, 0, level - levelsLen); + levelCfg.option = levelOptionList[level]; }); + } - return { rowId, colId: colId + totalColSpan }; + shouldShow(): boolean { + return !!this._model.getShallow('show', true); } - private _countHeight(node: MatrixNodeOption): number { - if (typeof node === 'string' || !node.children) { - return 1; + /** + * Iterate leaves (they are layout units) if dimIdx === this.dimIdx. + * Iterate levels if dimIdx !== this.dimIdx. + */ + resetLayoutIterator( + it: ListIterator | NullUndefined, + dimIdx: number, + startLocator?: MatrixXYLocator | NullUndefined, + count?: number | NullUndefined, + ): ListIterator { + it = it || new ListIterator(); + if (dimIdx === this.dimIdx) { + const len = this._leavesCount; + const startIdx = startLocator != null ? Math.max(0, startLocator) : 0; + count = count != null ? Math.min(count, len) : len; + it.reset(this._cells, startIdx, startIdx + count); } - let height = 0; - for (let i = 0; i < node.children.length; i++) { - height = Math.max(height, this._countHeight(node.children[i])); + else { + const len = this._levels.length; + // Corner locator is from `-this._levels.length` to `-1`. + const startIdx = startLocator != null ? Math.max(0, startLocator + len) : 0; + count = count != null ? Math.min(count, len) : len; + it.reset(this._levels, startIdx, startIdx + count); } - return height + 1; + return it; + } + + resetCellIterator( + it?: ListIterator + ): ListIterator { + return (it || new ListIterator()).reset(this._cells, 0); + } + + resetLevelIterator( + it?: ListIterator + ): ListIterator { + return (it || new ListIterator()).reset(this._levels, 0); } - private _countLeaves(node: MatrixNodeOption): number { - if (typeof node === 'string' || !node.children) { - return 1; + getLayout(outRect: RectLike, dimIdx: number, locator: MatrixXYLocator): void { + const layout = this.getUnitLayoutInfo(dimIdx, locator); + outRect[XY[dimIdx]] = layout ? layout.xy : NaN; + outRect[WH[dimIdx]] = layout ? layout.wh : NaN; + } + + /** + * Get leaf cell or get level info. + * Should be able to return null/undefined if not found on x or y, thus input `dimIdx` is needed. + */ + getUnitLayoutInfo(dimIdx: number, locator: MatrixXYLocator): MatrixCellLayoutInfo | NullUndefined { + return dimIdx === this.dimIdx + ? (locator < this._leavesCount ? this._cells[locator] : undefined) + : this._levels[locator + this._levels.length]; + } + + /** + * Get dimension cell by data, including leaves and non-leaves. + */ + getCell(value: MatrixCoordValueOption): MatrixDimensionCell | NullUndefined { + const ordinal = this._scale.parse(value); + return eqNaN(ordinal) ? undefined : this._cells[ordinal]; + } + + /** + * Get leaf count or get level count. + */ + getLocatorCount(dimIdx: number): number { + return dimIdx === this.dimIdx ? this._leavesCount : this._levels.length; + } + + getOrdinalMeta(): OrdinalMeta { + return this._ordinalMeta; + } + +} + +function createUniqueValueGenerator(dim: 'x' | 'y') { + const dimUpper = dim.toUpperCase(); + const defaultValReg = new RegExp(`^${dimUpper}([0-9]+)$`); + let dupBase = 0; + + function calcDupBase(val: string | NullUndefined): void { + let matchResult; + if (val != null && (matchResult = val.match(defaultValReg))) { + dupBase = mathMax(dupBase, +matchResult[1] + 1); } - let cnt = 0; - for (let i = 0; i < node.children.length; i++) { - cnt += this._countLeaves(node.children[i]); + } + + function makeUniqueValue(): string { + return `${dimUpper}${dupBase++}`; + } + + // Duplicated value is allowed, because the `matrix.x/y.data` can be a tree and it's reasonable + // that leaves in different subtrees has the same text. But only the first one is allowed to be + // queried by the text, and the other ones can only be queried by index. + // Additionally, `matrix.x/y.data: [null, null, ...]` is allowed. + function ensureValueUnique(categories: (string | NullUndefined)[], cells: MatrixDimensionCell[]): void { + // A simple way to deduplicate or handle illegal or not specified values to avoid unexpected behaviors. + // The tree structure should not be broken even if duplicated. + const cateMap = createHashMap(); + for (let idx = 0; idx < categories.length; idx++) { + let value = categories[idx]; + // value may be set to NullUndefined by users or if illegal. + if (value == null || cateMap.get(value) != null) { + // Still display the original option.value if duplicated, but loose the ability to query by text. + categories[idx] = value = makeUniqueValue(); + cells[idx].option = defaults({value}, cells[idx].option); + } + cateMap.set(value, true); } - return cnt; } + return {calcDupBase, ensureValueUnique}; } diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 979e154139..7b1f17b9b2 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -17,72 +17,300 @@ * under the License. */ +import OrdinalMeta from '../../data/OrdinalMeta'; import ComponentModel from '../../model/Component'; -import { BoxLayoutOptionMixin, ComponentOption, ItemStyleOption, LabelOption } from '../../util/types'; +import Model from '../../model/Model'; +import { + BoxLayoutOptionMixin, CommonTooltipOption, ComponentOption, ItemStyleOption, LabelOption, + LineStyleOption, + NullUndefined, OrdinalNumber, OrdinalRawValue, + PositionSizeOption +} from '../../util/types'; import Matrix from './Matrix'; -import { MatrixNodeOption } from './MatrixDim'; +import { MatrixDim, MatrixXYLocator } from './MatrixDim'; +import { MatrixBodyCorner } from './MatrixBodyCorner'; +import { CoordinateSystemHostModel } from '../CoordinateSystem'; + export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { mainType?: 'matrix'; - x?: { - show?: boolean; - data?: MatrixNodeOption[]; - label?: LabelOption; - itemStyle?: ItemStyleOption; - } - y?: { - show?: boolean; - data?: MatrixNodeOption[]; - label?: LabelOption; - itemStyle?: ItemStyleOption; - } + x?: MatrixDimensionOption; + y?: MatrixDimensionOption; + body?: MatrixBodyOption; + corner?: MatrixCornerOption; + // Only for the matrix overall style, won't be inherited by x/y/coner/body. backgroundStyle?: ItemStyleOption; - innerBackgroundStyle?: ItemStyleOption; + // Used on the outer border and the divider line. + borderZ2?: number; + tooltip?: CommonTooltipOption; +} + +interface MatrixBodyCornerBaseOption extends MatrixCellStyleOption { + /** + * Only specify some special cell definitions. + * It can represent both body cells and top-left corner cells. + * + * [body/corner cell locating]: + * (The rule applied in `matrix.dataToPoint` and `matrix.dataToLayout`.) + * Suppose the matrix.x/y dimensions (header) are defined as: + * matrix: { + * x: [{ value: 'Xa0', children: ['Xb0', 'Xb1'] }, 'Xa1'], + * y: [{ value: 'Ya0', children: ['Yb0', 'Yb1'] }], + * } + * ----------------------------------------- + * | | | Xa0 | | + * |-------+-------+---------------| Xa1 | + * |cornerQ|cornerP| Xb0 | Xb1 | | + * |-------+-------+-------+-------+-------- + * | | Yb0 | bodyR | bodyS | | + * | Ya0 |-------+-------+-------+-------| + * | | Yb1 | | bodyT | + * |---------------|------------------------ + * "Locator number" (`MatrixXYLocator`): + * The term `locator` refers to a integer number to locate cells on x or y direction. + * Use the top-left corner of the body as the orgin point (0, 0), + * the non-negative locator indicates the right/bottom of the origin point; + * the negative locator indicates the left/top of the origin point. + * "Ordinal number" (`OrdinalNumber`): + * This term follows the same meaning as that in category axis of cartesian. They are + * non-negative integer, designating each string `matrix.x.data[i].value`/`matrix.y.data[i].value`. + * 'Xb0', 'Xb2', 'Xa1', 'Xa0' are assigned with the ordinal numbers 0, 1, 2, 3. + * For every leaf dimension cell, `OrdinalNumber` and `MatrixXYLocator` is the same. + * + * A cell or pixel point or rect can be determined/located by a pair of `MatrixCoordValueOption`. + * See also `MatrixBodyCornerCellOption['coord']`. + * + * - The body cell `bodyS` above can be located by: + * - `coord: [1, 0]` (`MatrixXYLocator` or `OrdinalNumber`, which is a non-negative integer) + * - `coord: ['Xb1', 'Yb0']` + * - `coord: ['Xb1', 0]` (mix them) + * - The corner cell `cornerQ` above can be located by: + * - `coord: [-2, -1]` (negative `MatrixXYLocator`) + * - But it is NOT supported to use `coord: ['Y1_0', 'X1_0']` (XY transposed form) here. + * It's mathematically sound, but may introduce confusion and unnecessary + * complexity (consider the 'Xa1' case), and corner locating is not frequently used. + * - `mergeCells`: Body cells or corner cells can be merged, such as "bodyT" above, an input + * - The merging can be defined by: + * `matrix.data[i]: {coord: [['Xb1', 'Xa1'], 'Yb0'], mergeCells: true}`. + * - Input `['Xa1', 'Yb1']` to `dataToPoint` will get a point in the center of "bodyT". + * - Input `['Xa1', 'Yb1']` to `dataToLayout` will get a rect of the "bodyT". + * - If inputing a non-leaf dimension cell to locate, such as `['Xa0', 'Yb0']`, + * - it returns only according to the center of the dimension cells, regardless of the body span. + * (therefore, the result can be on the boundary of two body cells.) + * And the oridinal number assigned to 'Xa0' is 3, thus input `[3, 'Yb0']` get the some result. + * - The dimension (header) cell can be located by negative `MatrixXYLocator`. For example: + * - The center of the node 'Ya0' can be located by `[-2, 'Ya0']`. + */ + data?: MatrixBodyCornerCellOption[]; +} +export interface MatrixBodyOption extends MatrixBodyCornerBaseOption { +} +export interface MatrixCornerOption extends MatrixBodyCornerBaseOption { +} + +/** + * Commonly used as `MatrixCoordRangeOption[]` + * Can locate a cell or a rect range of cells. + * `[2, 8]` indicates a cell. + * `[2, null/undefined/NaN]` means y is the entire column. + * `[null/undefined/NaN, 8]` is the opposite. + * `[[2, 5], 8]` indicates a rect of cells in x range of `2~5` and y `8`. + * `[[2, 5], null/undefined/NaN]` indicates a x range of `2~5` and y is the entire column. + * `[[2, 5], [7, 8]]` indicates a rect of cells in x range of `2~5` and y range of `7~8`. + * `['aNonLeaf', 8]` indicates a rect of cells in x range of `aNonLeaf` and y `8`. + * @see {parseCoordRangeOption} + * @see {MatrixBodyCornerBaseOption['data']} + */ +export type MatrixCoordRangeOption = (MatrixCoordValueOption | MatrixCoordValueOption[] | NullUndefined); +/** + * `OrdinalRawValue` is originally provided by `matrix.x/y.data[i].value` or `series.data`. + */ +export type MatrixCoordValueOption = OrdinalRawValue | OrdinalNumber | MatrixXYLocator; + + +export interface MatrixBaseCellOption extends MatrixCellStyleOption { +} + +export interface MatrixBodyCornerCellOption extends MatrixBaseCellOption { + // Text that can be displayed. + value?: string; + // Use it to reference a coord in matrix. + coord?: MatrixCoordRangeOption[]; + // Merge cells determined by `coord`. + mergeCells?: boolean; +} + +interface MatrixDimensionOption extends MatrixCellStyleOption, MatrixDimensionLevelOption { + type?: 'category'; // For internal usage; force be 'category'. + show?: boolean; + data?: MatrixDimensionCellLooseOption[]; + // `levels[0]`: the topmost (for x dimension) or leftmost (for y dimension) level. + // If not specified, use null/undefined, such as `levels: [null, null, {levelSize: 10}]` + levels?: (MatrixDimensionLevelOption | NullUndefined)[]; + dividerLineStyle?: LineStyleOption; +} + +export interface MatrixDimensionCellOption extends MatrixBaseCellOption { + // Use it to define a coord in matrix. + // Do not use type `OrdinalRawValue` here. Number input is forbiden due to the possible confusion. + // e.g., if `matrix.x.data: [1, 2, 3]` is allowed, then querying `coord: [1, null]` will actually get + // the second column, since number represents the index (i.e. `MatrixXYLocator` or `OrdinalNumber`). + value?: string; + // column width (for x dimension) or row height (for y dimension). + // If not specified (null/undefined), auto calculate it. + // Only available on leaves, to avoid unnecessary complex. + size?: PositionSizeOption; + children?: MatrixDimensionCellOption[]; +} +export type MatrixDimensionCellLooseOption = MatrixDimensionCellOption | MatrixDimensionCellOption['value']; + +export interface MatrixDimensionLevelOption { + // `matrix.levelSize` specifies the default size of every tree levels. + // `matrix.levels[i].levelSize` specifies the size of a certain level. + // For x dimension, that is height; for y dimension, that is width. + // If not specified (null/undefined), auto calculate it. + levelSize?: PositionSizeOption; + // Other level specific options may added if needed, such as border-bottom/right style. +} + +export interface MatrixDimensionModel extends Model { } -const defaultDimOption = { +/** + * Two levels of cascade inheritance: + * - priority-high: style options defined in `matrix.x/y/coner/body.data[i]` (in cell) + * - priority-low: style options defined in `matrix.x/y/coner/body` + */ +export interface MatrixCellStyleOption { + label?: LabelOption; + itemStyle?: ItemStyleOption; + // Padding for the inner content (label / series / other coord sys). + padding?: number | number[]; + cursor?: string; + // By default, auto decide whether to be silent, considering tooltip. + silent?: boolean | NullUndefined; + // Used when style conflict - espetially thick border style. + z2?: number; +} + +export interface MatrixTooltipFormatterParams { + componentType: 'matrix' + matrixIndex: number + name: string + $vars: ['name', 'xyLocator'] +} + +const defaultLabelOption: LabelOption = { show: true, - data: [] as MatrixNodeOption[], - label: { - show: true, - color: '#333' + color: '#333', + overflow: 'truncate', +}; +function makeDefaultCellItemStyleOption(isCorner: boolean) { + return { + color: 'none', + borderWidth: 1, + borderColor: isCorner ? 'transparent' : '#ccc', + }; +}; +const defaultDimOption: MatrixDimensionOption = { + show: true, + label: defaultLabelOption, + itemStyle: makeDefaultCellItemStyleOption(false), + silent: undefined, + dividerLineStyle: { + width: 1, + color: '#aaa', }, - itemStyle: { +}; +const defaultBodyOption: MatrixBodyOption = { + label: defaultLabelOption, + itemStyle: makeDefaultCellItemStyleOption(false), + silent: undefined, +}; +const defaultCornerOption: MatrixCornerOption = { + label: defaultLabelOption, + itemStyle: makeDefaultCellItemStyleOption(true), + silent: undefined, +}; +const defaultMatrixOption: MatrixOption = { + // As a most basic coord sys, `z` should be lower than + // other series and coord sys, such as, grid. + z: -50, + left: '10%', + top: '10%', + right: '10%', + bottom: '10%', + x: defaultDimOption, + y: defaultDimOption, + body: defaultBodyOption, + corner: defaultCornerOption, + backgroundStyle: { color: 'none', + borderColor: '#aaa', borderWidth: 1, - borderColor: '#ccc' - } + }, }; -class MatrixModel extends ComponentModel { + +class MatrixModel extends ComponentModel implements CoordinateSystemHostModel { static type = 'matrix'; type = MatrixModel.type; coordinateSystem: Matrix; - static defaultOption: MatrixOption = { - z: 2, - left: '10%', - top: '10%', - right: '10%', - bottom: '10%', - x: defaultDimOption, - y: defaultDimOption, - backgroundStyle: { - color: 'none', - borderColor: '#aaa', - borderWidth: 1, - borderType: 'solid', - opacity: 1 - }, - innerBackgroundStyle: { - color: 'none', - borderColor: '#ccc', - borderWidth: 1, - borderType: 'solid', - opacity: 1 - } + static layoutMode = 'box' as const; + + private _dimModels: { + x: MatrixDimensionModel; + y: MatrixDimensionModel; }; + + private _body: MatrixBodyCorner<'body'>; + private _corner: MatrixBodyCorner<'corner'>; + + static defaultOption: MatrixOption = defaultMatrixOption; + + optionUpdated(): void { + // Simply re-create all to follow model changes. + + const dimModels = this._dimModels = { + // Do not use matrixModel as the parent model, for preventing from cascade-fetching options to it. + x: new MatrixDimensionModel(this.get('x', true) || {}), + y: new MatrixDimensionModel(this.get('y', true) || {}), + }; + + dimModels.x.option.type = dimModels.y.option.type = 'category'; + const xDim = dimModels.x.dim = new MatrixDim('x', dimModels.x); + const yDim = dimModels.y.dim = new MatrixDim('y', dimModels.y); + + const dims = {x: xDim, y: yDim}; + this._body = new MatrixBodyCorner( + 'body', new Model(this.getShallow('body')), dims + ); + this._corner = new MatrixBodyCorner( + 'corner', new Model(this.getShallow('corner')), dims + ); + } + + getDimensionModel(dim: 'x' | 'y'): MatrixDimensionModel { + return this._dimModels[dim]; + } + + getBody(): MatrixBodyCorner<'body'> { + return this._body; + } + + getCorner(): MatrixBodyCorner<'corner'> { + return this._corner; + } + +} + +export class MatrixDimensionModel extends Model { + dim: MatrixDim; + getOrdinalMeta(): OrdinalMeta { + return this.dim.getOrdinalMeta(); + } } export default MatrixModel; diff --git a/src/coord/matrix/matrixCoordHelper.ts b/src/coord/matrix/matrixCoordHelper.ts new file mode 100644 index 0000000000..4605d74d2c --- /dev/null +++ b/src/coord/matrix/matrixCoordHelper.ts @@ -0,0 +1,363 @@ +/* +* 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 Point from 'zrender/src/core/Point'; +import { + MatrixCellLayoutInfoType, + MatrixCellLayoutInfo, + MatrixDimensionCell, + MatrixDimPair, + MatrixXYLocator, + MatrixXYLocatorRange, +} from './MatrixDim'; +import { NullUndefined } from '../../util/types'; +import { eqNaN, isArray, isNumber } from 'zrender/src/core/util'; +import { WH, XY } from '../../util/graphic'; +import { MatrixCoordRangeOption, MatrixCoordValueOption } from './MatrixModel'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { mathMax, mathMin } from '../../util/number'; + + +/** + * @public Public to users in `chart.convertFromPixel`. + */ +export const MatrixClampOption = { + // No clamp, be falsy, equals to null/undefined. It means if the input part is + // null/undefined/NaN/outOfBoundary, the result part is NaN, rather than clamp to + // the boundary of the matrix. + none: 0, + // Clamp, where null/undefined/NaN/outOfBoundary can be used to cover the entire row/column. + all: 1, + body: 2, + corner: 3, +}; +export type MatrixClampOption = (typeof MatrixClampOption)[keyof typeof MatrixClampOption]; + +/** + * For the x direction, + * - find dimension cell from `xMatrixDim`, + * - If `xDimCell` or `yDimCell` is not a leaf, return the non-leaf cell itself. + * - otherwise find level from `yMatrixDim`. + * - otherwise return `NullUndefined`. + * + * For the y direction, it's the opposite. + */ +export function coordDataToAllCellLevelLayout( + coordValue: MatrixCoordValueOption, + dims: MatrixDimPair, + thisDimIdx: number // 0 | 1 +): MatrixCellLayoutInfo | NullUndefined { + // Find in body. + let result: MatrixCellLayoutInfo | NullUndefined = dims[XY[thisDimIdx]].getCell(coordValue); + // Find in corner or dimension area. + if (!result && isNumber(coordValue) && coordValue < 0) { + result = dims[XY[1 - thisDimIdx]].getUnitLayoutInfo(thisDimIdx, Math.round(coordValue)); + } + return result; +} + +export function resetXYLocatorRange(out: unknown[] | NullUndefined): MatrixXYLocatorRange { + const rg = (out || []) as MatrixXYLocatorRange; + rg[0] = rg[0] || []; + rg[1] = rg[1] || []; + rg[0][0] = rg[0][1] = rg[1][0] = rg[1][1] = NaN; + return rg; +} + +/** + * If illegal or out of boundary, set NaN to `locOut`. (See `isLocatorRangeInvalidOnDim`) + * x dimension and y dimension are calculated separately. + */ +export function parseCoordRangeOption( + locOut: MatrixXYLocatorRange, + // If illegal input or can not find any target, save reason to it. + // Do nothing if `NullUndefined`. + reasonOut: string[] | NullUndefined, + data: MatrixCoordRangeOption[], + dims: MatrixDimPair, + clamp: MatrixClampOption, +): void { + // x and y are supported to be handled separately - if one dimension is invalid + // (may be users do not need that), the other one should also be calculated. + parseCoordRangeOptionOnOneDim(locOut[0], reasonOut, clamp, data, dims, 0); + parseCoordRangeOptionOnOneDim(locOut[1], reasonOut, clamp, data, dims, 1); +} + +function parseCoordRangeOptionOnOneDim( + locDimOut: MatrixXYLocatorRange[number], + reasonOut: string[] | NullUndefined, + clamp: MatrixClampOption, + data: MatrixCoordRangeOption[], + dims: MatrixDimPair, + dimIdx: number, +): void { + locDimOut[0] = Infinity; + locDimOut[1] = -Infinity; + + const dataOnDim = data[dimIdx]; + const coordValArr = isArray(dataOnDim) ? dataOnDim : [dataOnDim]; + const len = coordValArr.length; + const hasClamp = !!clamp; + + if (len === 1 || len === 2) { + parseCoordRangeOptionOnOneDimOnePart(locDimOut, reasonOut, coordValArr, hasClamp, dims, dimIdx, 0); + if (len > 1) { + parseCoordRangeOptionOnOneDimOnePart(locDimOut, reasonOut, coordValArr, hasClamp, dims, dimIdx, 1); + } + } + else { + if (reasonOut) { + reasonOut.push(`Can only contain 1 or 2 coords, rather than ${len}.`); + } + locDimOut[0] = locDimOut[1] = NaN; + } + + if (hasClamp) { + // null/undefined/NaN or illegal data represents the entire row/column; + // Cover the entire locator regardless of body or corner, and confine it later. + let locLowerBound = -dims[XY[1 - dimIdx]].getLocatorCount(dimIdx); + let locUpperBound = dims[XY[dimIdx]].getLocatorCount(dimIdx) - 1; + + if (clamp === MatrixClampOption.body) { + locLowerBound = mathMax(0, locLowerBound); + } + else if (clamp === MatrixClampOption.corner) { + locUpperBound = mathMin(-1, locUpperBound); + } + + if (locUpperBound < locLowerBound) { // Also considered that both x and y has no cell. + locLowerBound = locUpperBound = NaN; + } + + if (eqNaN(locDimOut[0])) { + locDimOut[0] = locLowerBound; + } + if (eqNaN(locDimOut[1])) { + locDimOut[1] = locUpperBound; + } + locDimOut[0] = mathMax(mathMin(locDimOut[0], locUpperBound), locLowerBound); + locDimOut[1] = mathMax(mathMin(locDimOut[1], locUpperBound), locLowerBound); + } +} + +// The return val must be finite or NaN. +function parseCoordRangeOptionOnOneDimOnePart( + locDimOut: MatrixXYLocatorRange[number], + reasonOut: string[] | NullUndefined, + coordValArr: MatrixCoordValueOption[], + hasClamp: boolean, + dims: MatrixDimPair, + dimIdx: number, + partIdx: number, +): void { + const layout = coordDataToAllCellLevelLayout(coordValArr[partIdx], dims, dimIdx); + if (!layout) { + if (!hasClamp && reasonOut) { + reasonOut.push(`Can not find layout by coord[${dimIdx}][${partIdx}].`); + } + locDimOut[0] = locDimOut[1] = NaN; + return; + } + const locatorA = layout.id[XY[dimIdx]]; + let locatorB = locatorA; + const dimCell = cellLayoutInfoToDimCell(layout); + if (dimCell) { // Handle non-leaf + locatorB += dimCell.span[XY[dimIdx]] - 1; + } + locDimOut[0] = mathMin(locDimOut[0], locatorA, locatorB); + locDimOut[1] = mathMax(locDimOut[1], locatorA, locatorB); +} + +/** + * @param locatorRange Must be the return of `parseCoordRangeOption`, + * where if not NaN, it must be a valid locator. + */ +export function isXYLocatorRangeInvalidOnDim( + locatorRange: MatrixXYLocatorRange, dimIdx: number +): boolean { + return eqNaN(locatorRange[dimIdx][0]) || eqNaN(locatorRange[dimIdx][1]); +} + +// `locatorRange` will be expanded (modified) if an intersection is encountered. +export function resolveXYLocatorRangeByCellMerge( + inOutLocatorRange: MatrixXYLocatorRange, + // Item indices coorespond to mergeDefList (len: mergeDefListTravelLen). + // Indicating whether each item has be merged into the `locatorRange` + outMergedMarkList: boolean[] | NullUndefined, + mergeDefList: { + locatorRange: MatrixXYLocatorRange | NullUndefined; + cellMergeOwner: boolean; + }[], + mergeDefListTravelLen: number, +): void { + outMergedMarkList = outMergedMarkList || _tmpOutMergedMarkList; + for (let idx = 0; idx < mergeDefListTravelLen; idx++) { + outMergedMarkList[idx] = false; + } + // In most case, cell merging definition list length is smaller than the range extent, + // therefore, to detection intersection, travelling cell merging definition list is probably + // performant than traveling the four edges of the rect formed by the locator range. + while (true) { + let expanded = false; + for (let idx = 0; idx < mergeDefListTravelLen; idx++) { + const mergeDef = mergeDefList[idx]; + if (!outMergedMarkList[idx] + && mergeDef.cellMergeOwner + && expandXYLocatorRangeIfIntersect(inOutLocatorRange, mergeDef.locatorRange) + ) { + outMergedMarkList[idx] = true; + expanded = true; + } + } + if (!expanded) { + break; + } + } +} +const _tmpOutMergedMarkList: boolean[] = []; + +// Return whether intersect. +// `thisLocRange` will be expanded (modified) if an intersection is encountered. +function expandXYLocatorRangeIfIntersect( + thisLocRange: MatrixXYLocatorRange, + otherLocRange: MatrixXYLocatorRange +): boolean { + if (!locatorRangeIntersectOneDim(thisLocRange[0], otherLocRange[0]) + || !locatorRangeIntersectOneDim(thisLocRange[1], otherLocRange[1]) + ) { + return false; + } + + thisLocRange[0][0] = mathMin(thisLocRange[0][0], otherLocRange[0][0]); + thisLocRange[0][1] = mathMax(thisLocRange[0][1], otherLocRange[0][1]); + thisLocRange[1][0] = mathMin(thisLocRange[1][0], otherLocRange[1][0]); + thisLocRange[1][1] = mathMax(thisLocRange[1][1], otherLocRange[1][1]); + + return true; +} + +// Notice: If containing NaN, not intersect. +function locatorRangeIntersectOneDim( + locRange1OneDim: MatrixXYLocatorRange[number], + locRange2OneDim: MatrixXYLocatorRange[number], +): boolean { + return ( + locRange1OneDim[1] >= locRange2OneDim[0] + && locRange1OneDim[0] <= locRange2OneDim[1] + ); +} + +export function fillIdSpanFromLocatorRange( + owner: {id: Point; span: Point;}, + locatorRange: MatrixXYLocatorRange +): void { + owner.id.set(locatorRange[0][0], locatorRange[1][0]); + owner.span.set(locatorRange[0][1] - owner.id.x + 1, locatorRange[1][1] - owner.id.y + 1); +} + +export function cloneXYLocatorRange( + target: MatrixXYLocatorRange, + source: MatrixXYLocatorRange +): void { + target[0][0] = source[0][0]; + target[0][1] = source[0][1]; + target[1][0] = source[1][0]; + target[1][1] = source[1][1]; +} + +/** + * If illegal, the corresponding x/y/width/height is set to `NaN`. + * `x/width` or `y/height` is supported to be calculated separately, + * i.e., one side are NaN, the other side are normal. + * @param oneDimOut only write to `x/width` or `y/height`, depending on `dimIdx`. + */ +export function xyLocatorRangeToRectOneDim( + oneDimOut: RectLike, + locRange: MatrixXYLocatorRange, + dims: MatrixDimPair, + dimIdx: number +) { + const layoutMin = coordDataToAllCellLevelLayout(locRange[dimIdx][0], dims, dimIdx); + const layoutMax = coordDataToAllCellLevelLayout(locRange[dimIdx][1], dims, dimIdx); + + oneDimOut[XY[dimIdx]] = oneDimOut[WH[dimIdx]] = NaN; + + if (layoutMin && layoutMax) { + oneDimOut[XY[dimIdx]] = layoutMin.xy; + oneDimOut[WH[dimIdx]] = layoutMax.xy + layoutMax.wh - layoutMin.xy; + } +} + +// No need currently, since `span` is not allowed to be defined directly by users. +// /** +// * If either span x or y is valid and > 1, return parsed span, otherwise return `NullUndefined`. +// */ +// export function parseSpanOption( +// spanOptionHost: MatrixCellSpanOptionHost, +// dimCellPair: MatrixCellLayoutInfo[] +// ): Point | NullUndefined { +// const spanX = parseSpanOnDim(spanOptionHost.spanX, dimCellPair[0], 0); +// const spanY = parseSpanOnDim(spanOptionHost.spanY, dimCellPair[1], 1); +// if (!eqNaN(spanX) || !eqNaN(spanY)) { +// return new Point(spanX || 1, spanY || 1); +// } +// function parseSpanOnDim(spanOption: unknown, dimCell: MatrixCellLayoutInfo, dimIdx: number): number { +// if (!isNumber(spanOption)) { +// return NaN; +// } +// // Ensure positive integer (not NaN) to avoid dead loop. +// const span = mathMax(1, Math.round(spanOption || 1)) || 1; +// // Clamp, and consider may also be specified as `Infinity` to span the entire col/row. +// return mathMin(span, mathMax(1, dimCell.dim.getLocatorCount(dimIdx) - dimCell.id[XY[dimIdx]])); +// } +// } + +/** + * @usage To get/set on dimension, use: + * `xyVal[XY[dim]] = val;` // set on this dimension. + * `xyVal[XY[1 - dim]] = val;` // set on the perpendicular dimension. + */ +export function setDimXYValue( + out: Point, + dimIdx: number, // 0 | 1 + valueOnThisDim: MatrixXYLocator, + valueOnOtherDim: MatrixXYLocator +): Point { + out[XY[dimIdx]] = valueOnThisDim; + out[XY[1 - dimIdx]] = valueOnOtherDim; + return out; +} + +/** + * Return NullUndefined if not dimension cell. + */ +function cellLayoutInfoToDimCell( + cellLayoutInfo: MatrixCellLayoutInfo | NullUndefined +): MatrixDimensionCell | NullUndefined { + return ( + cellLayoutInfo && ( + cellLayoutInfo.type === MatrixCellLayoutInfoType.leaf + || cellLayoutInfo.type === MatrixCellLayoutInfoType.nonLeaf + ) + ) ? (cellLayoutInfo as MatrixDimensionCell) : null; +} + +export function createNaNRectLike(): RectLike { + return {x: NaN, y: NaN, width: NaN, height: NaN}; +} diff --git a/src/coord/matrix/prepareCustom.ts b/src/coord/matrix/prepareCustom.ts index b014c22ba0..b3ef772a92 100644 --- a/src/coord/matrix/prepareCustom.ts +++ b/src/coord/matrix/prepareCustom.ts @@ -17,7 +17,7 @@ * under the License. */ -import Matrix from './Matrix'; +import type Matrix from './Matrix'; export default function matrixPrepareCustom(coordSys: Matrix) { const rect = coordSys.getRect(); @@ -31,12 +31,17 @@ export default function matrixPrepareCustom(coordSys: Matrix) { height: rect.height }, api: { - coord: function (data: [string, string]) { - return coordSys.dataToPoint(data); + coord: function ( + data: Parameters[0], + opt?: Parameters[1] + ): ReturnType { + return coordSys.dataToPoint(data, opt); }, - size: function (data: [string, string]) { - const rect = coordSys.dataToRect(data); - return [rect.width, rect.height]; + layout: function ( + data: Parameters[0], + opt?: Parameters[1] + ): ReturnType { + return coordSys.dataToLayout(data, opt); } } }; diff --git a/src/coord/parallel/Parallel.ts b/src/coord/parallel/Parallel.ts index c694f55ff1..5108bbf914 100644 --- a/src/coord/parallel/Parallel.ts +++ b/src/coord/parallel/Parallel.ts @@ -194,13 +194,8 @@ class Parallel implements CoordinateSystemMaster, CoordinateSystem { * Resize the parallel coordinate system. */ resize(parallelModel: ParallelModel, api: ExtensionAPI): void { - this._rect = layoutUtil.getLayoutRect( - parallelModel.getBoxLayoutParams(), - { - width: api.getWidth(), - height: api.getHeight() - } - ); + const refContainer = layoutUtil.createBoxLayoutReference(parallelModel, api).refContainer; + this._rect = layoutUtil.getLayoutRect(parallelModel.getBoxLayoutParams(), refContainer); this._layoutAxes(); } diff --git a/src/coord/polar/Polar.ts b/src/coord/polar/Polar.ts index e7b9db6cce..a0d294f389 100644 --- a/src/coord/polar/Polar.ts +++ b/src/coord/polar/Polar.ts @@ -138,22 +138,22 @@ class Polar implements CoordinateSystem, CoordinateSystemMaster { * Convert a single data item to (x, y) point. * Parameter data is an array which the first element is radius and the second is angle */ - dataToPoint(data: ScaleDataValue[], clamp?: boolean) { + dataToPoint(data: ScaleDataValue[], clamp?: boolean, out?: number[]) { return this.coordToPoint([ this._radiusAxis.dataToRadius(data[0], clamp), this._angleAxis.dataToAngle(data[1], clamp) - ]); + ], out); } /** * Convert a (x, y) point to data */ - pointToData(point: number[], clamp?: boolean) { + pointToData(point: number[], clamp?: boolean, out?: number[]) { + out = out || []; const coord = this.pointToCoord(point); - return [ - this._radiusAxis.radiusToData(coord[0], clamp), - this._angleAxis.angleToData(coord[1], clamp) - ]; + out[0] = this._radiusAxis.radiusToData(coord[0], clamp); + out[1] = this._angleAxis.angleToData(coord[1], clamp); + return out; } /** @@ -190,14 +190,15 @@ class Polar implements CoordinateSystem, CoordinateSystemMaster { /** * Convert a (radius, angle) coord to (x, y) point */ - coordToPoint(coord: number[]) { + coordToPoint(coord: number[], out?: number[]) { + out = out || []; const radius = coord[0]; const radian = coord[1] / 180 * Math.PI; - const x = Math.cos(radian) * radius + this.cx; + out[0] = Math.cos(radian) * radius + this.cx; // Inverse the y - const y = -Math.sin(radian) * radius + this.cy; + out[1] = -Math.sin(radian) * radius + this.cy; - return [x, y]; + return out; } /** @@ -238,12 +239,16 @@ class Polar implements CoordinateSystem, CoordinateSystemMaster { }; } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue[]) { + convertToPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue[] + ) { const coordSys = getCoordSys(finder); return coordSys === this ? this.dataToPoint(value) : null; } - convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { + convertFromPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[] + ) { const coordSys = getCoordSys(finder); return coordSys === this ? this.pointToData(pixel) : null; } diff --git a/src/coord/polar/polarCreator.ts b/src/coord/polar/polarCreator.ts index fe29f3ed18..224de9c3b0 100644 --- a/src/coord/polar/polarCreator.ts +++ b/src/coord/polar/polarCreator.ts @@ -40,20 +40,21 @@ import { SeriesOption } from '../../util/types'; import { SINGLE_REFERRING } from '../../util/model'; import { AxisBaseModel } from '../AxisBaseModel'; import { CategoryAxisBaseOption } from '../axisCommonTypes'; +import { createBoxLayoutReference } from '../../util/layout'; /** * Resize method bound to the polar */ function resizePolar(polar: Polar, polarModel: PolarModel, api: ExtensionAPI) { const center = polarModel.get('center'); - const width = api.getWidth(); - const height = api.getHeight(); - polar.cx = parsePercent(center[0], width); - polar.cy = parsePercent(center[1], height); + const refContainer = createBoxLayoutReference(polarModel, api).refContainer; + + polar.cx = parsePercent(center[0], refContainer.width) + refContainer.x; + polar.cy = parsePercent(center[1], refContainer.height) + refContainer.y; const radiusAxis = polar.getRadiusAxis(); - const size = Math.min(width, height) / 2; + const size = Math.min(refContainer.width, refContainer.height) / 2; let radius = polarModel.get('radius'); if (radius == null) { diff --git a/src/coord/radar/Radar.ts b/src/coord/radar/Radar.ts index 732eceb84a..6ce88ec75f 100644 --- a/src/coord/radar/Radar.ts +++ b/src/coord/radar/Radar.ts @@ -30,6 +30,7 @@ import { ScaleDataValue } from '../../util/types'; import { ParsedModelFinder } from '../../util/model'; import { map, each, isString, isNumber } from 'zrender/src/core/util'; import { alignScaleTicks } from '../axisAlignTicks'; +import { createBoxLayoutReference } from '../../util/layout'; class Radar implements CoordinateSystem, CoordinateSystemMaster { @@ -122,12 +123,12 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { } resize(radarModel: RadarModel, api: ExtensionAPI) { + const refContainer = createBoxLayoutReference(radarModel, api).refContainer; + const center = radarModel.get('center'); - const viewWidth = api.getWidth(); - const viewHeight = api.getHeight(); - const viewSize = Math.min(viewWidth, viewHeight) / 2; - this.cx = numberUtil.parsePercent(center[0], viewWidth); - this.cy = numberUtil.parsePercent(center[1], viewHeight); + const viewSize = Math.min(refContainer.width, refContainer.height) / 2; + this.cx = numberUtil.parsePercent(center[0], refContainer.width) + refContainer.x; + this.cy = numberUtil.parsePercent(center[1], refContainer.height) + refContainer.y; this.startAngle = radarModel.get('startAngle') * Math.PI / 180; diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts index c9226b5aae..80e2ac04c2 100644 --- a/src/coord/single/Single.ts +++ b/src/coord/single/Single.ts @@ -23,7 +23,7 @@ import SingleAxis from './SingleAxis'; import * as axisHelper from '../axisHelper'; -import {getLayoutRect} from '../../util/layout'; +import {createBoxLayoutReference, getLayoutRect} from '../../util/layout'; import {each} from 'zrender/src/core/util'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; @@ -111,20 +111,8 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { * Resize the single coordinate system. */ resize(axisModel: SingleAxisModel, api: ExtensionAPI) { - this._rect = getLayoutRect( - { - left: axisModel.get('left'), - top: axisModel.get('top'), - right: axisModel.get('right'), - bottom: axisModel.get('bottom'), - width: axisModel.get('width'), - height: axisModel.get('height') - }, - { - width: api.getWidth(), - height: api.getHeight() - } - ); + const refContainer = createBoxLayoutReference(axisModel, api).refContainer; + this._rect = getLayoutRect(axisModel.getBoxLayoutParams(), refContainer); this._adjustAxis(); } @@ -215,38 +203,44 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { } } - pointToData(point: number[]) { + pointToData(point: number[], reserved?: null, out?: number[]) { + out = out || []; const axis = this.getAxis(); - return [axis.coordToData(axis.toLocalCoord( + out[0] = axis.coordToData(axis.toLocalCoord( point[axis.orient === 'horizontal' ? 0 : 1] - ))]; + )); + return out; } /** * Convert the series data to concrete point. * Can be [val] | val */ - dataToPoint(val: ScaleDataValue | ScaleDataValue[]) { + dataToPoint(val: ScaleDataValue | ScaleDataValue[], reserved?: unknown, out?: number[]) { const axis = this.getAxis(); const rect = this.getRect(); - const pt = []; + out = out || []; const idx = axis.orient === 'horizontal' ? 0 : 1; if (val instanceof Array) { val = val[0]; } - pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); - pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); - return pt; + out[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); + out[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); + return out; } - convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue[]) { + convertToPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue[] + ) { const coordSys = getCoordSys(finder); return coordSys === this ? this.dataToPoint(value) : null; } - convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { + convertFromPixel( + ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[] + ) { const coordSys = getCoordSys(finder); return coordSys === this ? this.pointToData(pixel) : null; } diff --git a/src/core/CoordinateSystem.ts b/src/core/CoordinateSystem.ts index b1f59222c3..273c24b1cf 100644 --- a/src/core/CoordinateSystem.ts +++ b/src/core/CoordinateSystem.ts @@ -21,42 +21,329 @@ import * as zrUtil from 'zrender/src/core/util'; import type GlobalModel from '../model/Global'; import type ExtensionAPI from './ExtensionAPI'; -import type { CoordinateSystemCreator, CoordinateSystemMaster } from '../coord/CoordinateSystem'; +import type { CoordinateSystem, CoordinateSystemCreator, CoordinateSystemMaster } from '../coord/CoordinateSystem'; +import { SINGLE_REFERRING } from '../util/model'; +import ComponentModel from '../model/Component'; +import SeriesModel from '../model/Series'; +import { error } from '../util/log'; +import { CoordinateSystemDataCoord, NullUndefined } from '../util/types'; -const coordinateSystemCreators: {[type: string]: CoordinateSystemCreator} = {}; +type CoordinateSystemCreatorMap = {[type: string]: CoordinateSystemCreator}; + +/** + * FIXME: + * `nonSeriesBoxCoordSysCreators` and `_nonSeriesBoxMasterList` are hardcoded implementations. + * Regarding "coord sys layout based on another coord sys", currently we only exprimentally support one level + * dpendency, such as, "grid(cartesian)s can be laid out based on matrix/calendar coord sys." + * But a comprehensive implementation may need to support: + * - Recursive dependencies. e.g., a matrix coord sys lays out based on another matrix coord sys. + * That requires in the implementation `create` and `update` of coord sys are called by a dependency graph. + * (@see enableTopologicalTravel in `util/component.ts`) + */ +const nonSeriesBoxCoordSysCreators: CoordinateSystemCreatorMap = {}; +const normalCoordSysCreators: CoordinateSystemCreatorMap = {}; class CoordinateSystemManager { - private _coordinateSystems: CoordinateSystemMaster[] = []; + private _normalMasterList: CoordinateSystemMaster[] = []; + private _nonSeriesBoxMasterList: CoordinateSystemMaster[] = []; + /** + * Typically, + * - in `create`, a coord sys lays out based on a given rect; + * - in `update`, update the pixel and data extent of there axes (if any) based on processed `series.data`. + * After that, a coord sys can serve (typically by `dataToPoint`/`dataToLayout`/`pointToData`). + * If the coordinate system do not lay out based on `series.data`, `update` is not needed. + */ create(ecModel: GlobalModel, api: ExtensionAPI): void { - let coordinateSystems: CoordinateSystemMaster[] = []; - zrUtil.each(coordinateSystemCreators, function (creator, type) { - const list = creator.create(ecModel, api); - coordinateSystems = coordinateSystems.concat(list || []); - }); + this._nonSeriesBoxMasterList = dealCreate(nonSeriesBoxCoordSysCreators, true); + this._normalMasterList = dealCreate(normalCoordSysCreators, false); + + function dealCreate(creatorMap: CoordinateSystemCreatorMap, canBeNonSeriesBox: boolean) { + let coordinateSystems: CoordinateSystemMaster[] = []; + zrUtil.each(creatorMap, function (creator, type) { + const list = creator.create(ecModel, api); + coordinateSystems = coordinateSystems.concat(list || []); - this._coordinateSystems = coordinateSystems; + if (__DEV__) { + if (canBeNonSeriesBox) { + // Disallow `update` is a brutal way to ensure `_nonSeriesBoxMasterList`s are ready to + // serve after `create`. But if `update` has to be involved in `_nonSeriesBoxMasterList` + // for some future case, more complicated mechanisms need to be introduced. + zrUtil.each(list, master => zrUtil.assert(!master.update)); + } + } + }); + return coordinateSystems; + } } + /** + * @see CoordinateSystem['create'] + */ update(ecModel: GlobalModel, api: ExtensionAPI): void { - zrUtil.each(this._coordinateSystems, function (coordSys) { + zrUtil.each(this._normalMasterList, function (coordSys) { coordSys.update && coordSys.update(ecModel, api); }); } getCoordinateSystems(): CoordinateSystemMaster[] { - return this._coordinateSystems.slice(); + return this._normalMasterList.concat(this._nonSeriesBoxMasterList); } static register = function (type: string, creator: CoordinateSystemCreator): void { - coordinateSystemCreators[type] = creator; + if (type === 'matrix' || type === 'calendar') { // FIXME: hardcode, @see nonSeriesBoxCoordSysCreators + nonSeriesBoxCoordSysCreators[type] = creator; + return; + } + normalCoordSysCreators[type] = creator; }; static get = function (type: string): CoordinateSystemCreator { - return coordinateSystemCreators[type]; + return normalCoordSysCreators[type] || nonSeriesBoxCoordSysCreators[type]; }; } +function canBeNonSeriesBoxCoordSys(coordSysType: string): boolean { + return !!nonSeriesBoxCoordSysCreators[coordSysType]; +} + + +export const BoxCoordinateSystemCoordFrom = { + // By default fetch coord from `model.get('coord')`. + coord: 1, + // Some model/series, such as pie, is allowed to also get coord from `model.get('center')`, + // if cannot get from `model.get('coord')`. But historically pie use `center` option, but + // geo use `layoutCenter` option to specify layout center; they are not able to be unified. + // Therefor it is not recommended. + coord2: 2, +} as const; +export type BoxCoordinateSystemCoordFrom = + (typeof BoxCoordinateSystemCoordFrom)[keyof typeof BoxCoordinateSystemCoordFrom]; + +type BoxCoordinateSystemGetCoord2 = (model: ComponentModel) => CoordinateSystemDataCoord; + +/** + * @see_also `createBoxLayoutReference` + * @see_also `injectCoordSysByOption` + */ +export function registerLayOutOnCoordSysUsage(opt: { + // `SomeSeries.type` or `SomeComponent.type` + fullType: ComponentModel['type'], + // @see BoxCoordinateSystemCoordFrom . Be `false` by default. + getCoord2?: BoxCoordinateSystemGetCoord2 +}) { + if (__DEV__) { + zrUtil.assert(!coordSysUseMap.get(opt.fullType)); + } + coordSysUseMap.set(opt.fullType, {getCoord2: undefined}).getCoord2 = opt.getCoord2; +} +const coordSysUseMap = zrUtil.createHashMap< + {getCoord2: BoxCoordinateSystemGetCoord2 | NullUndefined}, + ComponentModel['type'] +>(); + +/** + * @return Be an object, but never be NullUndefined. + */ +export function getCoordForBoxCoordSys( + model: ComponentModel +): { + coord: CoordinateSystemDataCoord | NullUndefined + from: BoxCoordinateSystemCoordFrom +} { + let coord: CoordinateSystemDataCoord = model.getShallow('coord', true); + let from: BoxCoordinateSystemCoordFrom = BoxCoordinateSystemCoordFrom.coord; + if (coord == null) { + const store = coordSysUseMap.get(model.type); + if (store && store.getCoord2) { + from = BoxCoordinateSystemCoordFrom.coord2; + coord = store.getCoord2(model); + } + } + return {coord, from}; +} + +/** + * - "dataCoordSys": each data item is laid out based on a coord sys. + * - "boxCoordSys": the overall bounding rect or anchor point is calculated based on a coord sys. + * e.g., + * grid rect (cartesian rect) is calculate based on matrix/calendar coord sys; + * pie center is calculated based on calendar/cartesian; + * + * The default value (if not declared in option `coordinateSystemUsage`): + * For series, use `dataCoordSys`, since this is the most case and backward compatible. + * For non-series components, use `boxCoordSys`, since `dataCoordSys` is not applicable. + */ +export const CoordinateSystemUsageKind = { + none: 0, + dataCoordSys: 1, + boxCoordSys: 2, +} as const; +export type CoordinateSystemUsageKind = (typeof CoordinateSystemUsageKind)[keyof typeof CoordinateSystemUsageKind]; + +export function decideCoordSysUsageKind( + // Component or series + model: ComponentModel, + printError?: boolean +): { + kind: CoordinateSystemUsageKind; + coordSysType: string | NullUndefined; +} { + // For backward compat, still not use `true` in model.get. + const coordSysType = model.getShallow('coordinateSystem'); + let coordSysUsageOption = model.getShallow('coordinateSystemUsage', true); + const isDeclaredExplicitly = coordSysUsageOption != null; + let kind: CoordinateSystemUsageKind = CoordinateSystemUsageKind.none; + + if (coordSysType) { + const isSeries = model.mainType === 'series'; + if (coordSysUsageOption == null) { + coordSysUsageOption = isSeries ? 'data' : 'box'; + } + + if (coordSysUsageOption === 'data') { + kind = CoordinateSystemUsageKind.dataCoordSys; + if (!isSeries) { + if (__DEV__) { + if (isDeclaredExplicitly && printError) { + error('coordinateSystemUsage "data" is not supported in non-series components.'); + } + } + kind = CoordinateSystemUsageKind.none; + } + } + else if (coordSysUsageOption === 'box') { + kind = CoordinateSystemUsageKind.boxCoordSys; + if (!isSeries && !canBeNonSeriesBoxCoordSys(coordSysType)) { + if (__DEV__) { + if (isDeclaredExplicitly && printError) { + error(`coordinateSystem "${coordSysType}" cannot be used` + + ` as coordinateSystemUsage "box" for "${model.type}" yet.` + ); + } + } + kind = CoordinateSystemUsageKind.none; + } + } + } + + return {coordSysType, kind}; +} + +/** + * These cases are considered: + * (A) Most series can use only "dataCoordSys", but "boxCoordSys" is not applicable: + * - e.g., series.heatmap, series.line, series.bar, series.scatter, ... + * (B) Some series and most components can use only "boxCoordSys", but "dataCoordSys" is not applicable: + * - e.g., series.pie, series.funnel, ... + * - e.g., grid, polar, geo, title, ... + * (C) Several series can use both "boxCoordSys" and "dataCoordSys", even at the same time: + * - e.g., series.graph, series.map + * - If graph or map series use a "boxCoordSys", it creates a internal "dataCoordSys" to lay out its data. + * - Graph series can use matrix coord sys as either the "dataCoordSys" (each item layout on one cell) + * or "boxCoordSys" (the entire series are layout within one cell). + * - To achieve this effect, + * `series.coordinateSystemUsage: 'box'` needs to be specified explicitly. + * + * Check these echarts option settings: + * - If `series: {type: 'bar'}`: + * dataCoordSys: "cartesian2d", boxCoordSys: "none". + * (since `coordinateSystem: 'cartesian2d'` is the default option in bar.) + * - If `grid: {coordinateSystem: 'matrix'}` + * dataCoordSys: "none", boxCoordSys: "matrix". + * - If `series: {type: 'pie', coordinateSystem: 'matrix'}`: + * dataCoordSys: "none", boxCoordSys: "matrix". + * (since `coordinateSystemUsage: 'box'` is the default option in pie.) + * - If `series: {type: 'graph', coordinateSystem: 'matrix'}`: + * dataCoordSys: "matrix", boxCoordSys: "none" + * - If `series: {type: 'graph', coordinateSystem: 'matrix', coordinateSystemUsage: 'box'}`: + * dataCoordSys: "an internal view", boxCoordSys: "the internal view is laid out on a matrix" + * - If `series: {type: 'map'}`: + * dataCoordSys: "a internal geo", boxCoordSys: "none" + * - If `series: {type: 'map', coordinateSystem: 'geo', geoIndex: 0}`: + * dataCoordSys: "a geo", boxCoordSys: "none" + * - If `series: {type: 'map', coordinateSystem: 'matrix'}`: + * not_applicable + * - If `series: {type: 'map', coordinateSystem: 'matrix', coordinateSystemUsage: 'box'}`: + * dataCoordSys: "an internal geo", boxCoordSys: "the internal geo is laid out on a matrix" + * + * @usage + * For case (A) & (B), + * call `injectCoordSysByOption({coordSysType: 'aaa', ...})` once for each series/components. + * For case (C), + * call `injectCoordSysByOption({coordSysType: 'aaa', ...})` once for each series/components, + * and then call `injectCoordSysByOption({coordSysType: 'bbb', ..., isDefaultDataCoordSys: true})` + * once for each series/components. + * + * @return Whether injected. + */ +export function injectCoordSysByOption(opt: { + // series or component + targetModel: ComponentModel; + coordSysType: string; + coordSysProvider: CoordSysInjectionProvider; + isDefaultDataCoordSys?: boolean; +}): boolean { + const { + targetModel, + coordSysType, + coordSysProvider, + isDefaultDataCoordSys, + } = opt; + if (__DEV__) { + zrUtil.assert(!!coordSysType); + } + + let {kind, coordSysType: declaredType} = decideCoordSysUsageKind(targetModel, true); + + if (isDefaultDataCoordSys + && kind !== CoordinateSystemUsageKind.dataCoordSys + ) { + // If both dataCoordSys and boxCoordSys declared in one model. + // There is the only case in series-graph, and no other cases yet. + kind = CoordinateSystemUsageKind.dataCoordSys; + declaredType = coordSysType; + } + + if (kind === CoordinateSystemUsageKind.none || declaredType !== coordSysType) { + return false; + } + + const coordSys = coordSysProvider(coordSysType, targetModel); + if (!coordSys) { + if (__DEV__) { + error(`${coordSysType} cannot be found for` + + ` ${targetModel.type} (index: ${targetModel.componentIndex}).` + ); + } + return false; + } + + if (kind === CoordinateSystemUsageKind.dataCoordSys) { + if (__DEV__) { + zrUtil.assert(targetModel.mainType === 'series'); + } + (targetModel as SeriesModel).coordinateSystem = coordSys; + } + else { // kind === 'boxCoordSys' + targetModel.boxCoordinateSystem = coordSys; + } + + return true; +} + +type CoordSysInjectionProvider = ( + coordSysType: string, injectTargetModel: ComponentModel +) => CoordinateSystem | NullUndefined; + +export const simpleCoordSysInjectionProvider: CoordSysInjectionProvider = function (coordSysType, injectTargetModel) { + const coordSysModel = injectTargetModel.getReferringComponents( + coordSysType, SINGLE_REFERRING + ).models[0] as (ComponentModel & {coordinateSystem: CoordinateSystem}); + return coordSysModel && coordSysModel.coordinateSystem; +}; + + export default CoordinateSystemManager; diff --git a/src/core/echarts.ts b/src/core/echarts.ts index 1c7e4e0389..351429414e 100644 --- a/src/core/echarts.ts +++ b/src/core/echarts.ts @@ -107,7 +107,10 @@ import { ScaleDataValue, ZRElementEventName, ECElementEvent, - AnimationOption + AnimationOption, + CoordinateSystemDataLayout, + NullUndefined, + CoordinateSystemDataCoord, } from '../util/types'; import Displayable from 'zrender/src/graphic/Displayable'; import { seriesSymbolTask, dataSymbolTask } from '../visual/symbol'; @@ -278,12 +281,29 @@ let updateMethods: { updateVisual: UpdateMethod, updateLayout: UpdateMethod }; -let doConvertPixel: ( - ecIns: ECharts, - methodName: string, - finder: ModelFinder, - value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) -) => (number | number[]); +let doConvertPixel: { + ( + ecIns: ECharts, + methodName: 'convertFromPixel', + finder: ModelFinder, + value: number | number[], + opt: unknown + ): number | number[]; + ( + ecIns: ECharts, + methodName: 'convertToPixel', + finder: ModelFinder, + value: CoordinateSystemDataCoord, + opt: unknown + ): number | number[]; + ( + ecIns: ECharts, + methodName: 'convertToLayout', + finder: ModelFinder, + value: CoordinateSystemDataCoord, + opt: unknown + ): CoordinateSystemDataLayout; +}; let updateStreamModes: (ecIns: ECharts, ecModel: GlobalModel) => void; let doDispatchAction: (this: ECharts, payload: Payload, silent: boolean) => void; let flushPendingActions: (this: ECharts, silent: boolean) => void; @@ -940,21 +960,61 @@ class ECharts extends Eventful { /** * Convert from logical coordinate system to pixel coordinate system. * See CoordinateSystem#convertToPixel. + * + * TODO / PENDING: + * currently `convertToPixel` `convertFromPixel` `convertToLayout` may not be suitable + * for some extremely performance-sensitive scenarios (such as, handling massive amounts of data), + * since it performce "find component" every time. + * And it is not friendly to the nuances between different coordinate systems. + * @see https://github.com/apache/echarts/issues/20985 for details + * + * @see CoordinateSystem['dataToPoint'] for parameters and return. + * @see CoordinateSystemDataCoord */ convertToPixel(finder: ModelFinder, value: ScaleDataValue): number; convertToPixel(finder: ModelFinder, value: ScaleDataValue[]): number[]; - convertToPixel(finder: ModelFinder, value: ScaleDataValue | ScaleDataValue[]): number | number[] { - return doConvertPixel(this, 'convertToPixel', finder, value); + convertToPixel( + finder: ModelFinder, value: ScaleDataValue | ScaleDataValue[] + ): number | number[]; + // The above are signatures from before v6, thus they should be preserved for backward compat. + convertToPixel( + finder: ModelFinder, value: (ScaleDataValue | ScaleDataValue[] | NullUndefined)[] + ): number | number[]; + convertToPixel( + finder: ModelFinder, + value: (ScaleDataValue | NullUndefined) | (ScaleDataValue | ScaleDataValue[] | NullUndefined)[], + opt?: unknown + ): number | number[] { + return doConvertPixel(this, 'convertToPixel', finder, value, opt); + } + + /** + * Convert from logical coordinate system to pixel coordinate system. + * See CoordinateSystem#convertToPixel. + * + * @see CoordinateSystem['dataToLayout'] for parameters and return. + * @see CoordinateSystemDataCoord + */ + convertToLayout( + finder: ModelFinder, + value: (ScaleDataValue | NullUndefined) | (ScaleDataValue | ScaleDataValue[] | NullUndefined)[], + opt?: unknown + ): CoordinateSystemDataLayout { + return doConvertPixel(this, 'convertToLayout', finder, value, opt); } /** * Convert from pixel coordinate system to logical coordinate system. * See CoordinateSystem#convertFromPixel. + * + * @see CoordinateSystem['pointToData'] for parameters and return. */ convertFromPixel(finder: ModelFinder, value: number): number; convertFromPixel(finder: ModelFinder, value: number[]): number[]; - convertFromPixel(finder: ModelFinder, value: number | number[]): number | number[] { - return doConvertPixel(this, 'convertFromPixel', finder, value); + convertFromPixel(finder: ModelFinder, value: number | number[]): number | number[]; + // The above are signatures from before v6, thus they should be preserved for backward compat. + convertFromPixel(finder: ModelFinder, value: number | number[], opt?: unknown): number | number[] { + return doConvertPixel(this, 'convertFromPixel', finder, value, opt); } /** @@ -1849,12 +1909,34 @@ class ECharts extends Eventful { } }; - doConvertPixel = function ( + function doConvertPixelImpl( + ecIns: ECharts, + methodName: 'convertFromPixel', + finder: ModelFinder, + value: number | number[], + opt: unknown + ): number | number[]; + function doConvertPixelImpl( + ecIns: ECharts, + methodName: 'convertToPixel', + finder: ModelFinder, + value: CoordinateSystemDataCoord, + opt: unknown + ): number | number[]; + function doConvertPixelImpl( + ecIns: ECharts, + methodName: 'convertToLayout', + finder: ModelFinder, + value: CoordinateSystemDataCoord, + opt: unknown + ): CoordinateSystemDataLayout; + function doConvertPixelImpl( ecIns: ECharts, - methodName: 'convertFromPixel' | 'convertToPixel', + methodName: 'convertFromPixel' | 'convertToPixel' | 'convertToLayout', finder: ModelFinder, - value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) - ): (number | number[]) { + value: number | number[] | CoordinateSystemDataCoord, + opt: unknown + ) { if (ecIns._disposed) { disposedWarning(ecIns.id); return; @@ -1868,7 +1950,7 @@ class ECharts extends Eventful { for (let i = 0; i < coordSysList.length; i++) { const coordSys = coordSysList[i]; if (coordSys[methodName] - && (result = coordSys[methodName](ecModel, parsedFinder, value as any)) != null + && (result = coordSys[methodName](ecModel, parsedFinder, value as any, opt)) != null ) { return result; } @@ -1880,6 +1962,7 @@ class ECharts extends Eventful { ); } }; + doConvertPixel = doConvertPixelImpl; updateStreamModes = function (ecIns: ECharts, ecModel: GlobalModel): void { const chartsMap = ecIns._chartsMap; diff --git a/src/data/OrdinalMeta.ts b/src/data/OrdinalMeta.ts index 441e012afc..85f08b75ce 100644 --- a/src/data/OrdinalMeta.ts +++ b/src/data/OrdinalMeta.ts @@ -33,18 +33,46 @@ class OrdinalMeta { private _map: HashMap; - readonly uid: number; + private _onCollect: (category: OrdinalRawValue, index: number) => void; + readonly uid: number; + /** + * PENDING - Regarding forcibly converting to string: + * In the early days, the underlying hash map impl used JS plain object and converted the key to + * string; later in https://github.com/ecomfe/zrender/pull/966 it was changed to a JS Map (in supported + * platforms), which does not require string keys. But consider any input that `scale/Ordinal['parse']` + * is involved, a number input represents an `OrdinalNumber` (i.e., an index), and affect the query + * behavior: + * - If forcbily converting to string: + * pros: users can use numeric string (such as, '123') to query the raw data (123), tho it's probably + * still confusing. + * cons: NaN/null/undefined in data will be equals to 'NaN'/'null'/'undefined', if simply using + * `val + ''` to convert them, like currently `getName` does. + * - Otherwise: + * pros: see NaN/null/undefined case above. + * cons: users cannot query the raw data (123) any more. + * There are two inconsistent behaviors in the current impl: + * - Force conversion is applied on the case `xAxis{data: ['aaa', 'bbb', ...]}`, + * but no conversion applied to the case `xAxis{data: [{value: 'aaa'}, ...]}` and + * the case `dataset: {source: [['aaa', 123], ['bbb', 234], ...]}`. + * - behaves differently according to whether JS Map is supported (the polyfill is simply using JS + * plain object) (tho it seems rare platform that do not support it). + * Since there's no sufficient good solution to offset cost of the breaking change, we preserve the + * current behavior, until real issues is reported. + */ constructor(opt: { categories?: OrdinalRawValue[], needCollect?: boolean deduplication?: boolean + // Called only on `needCollect` is true and collect happens. + onCollect?: OrdinalMeta['_onCollect'] }) { this.categories = opt.categories || []; this._needCollect = opt.needCollect; this._deduplication = opt.deduplication; this.uid = ++uidBase; + this._onCollect = opt.onCollect; } static createByAxisModel(axisModel: Model): OrdinalMeta { @@ -61,7 +89,6 @@ class OrdinalMeta { }; getOrdinal(category: OrdinalRawValue): OrdinalNumber { - // @ts-ignore return this._getOrCreateMap().get(category); } @@ -92,19 +119,19 @@ class OrdinalMeta { if (needCollect && !this._deduplication) { index = this.categories.length; this.categories[index] = category; + this._onCollect && this._onCollect(category, index); return index; } const map = this._getOrCreateMap(); - // @ts-ignore index = map.get(category); if (index == null) { if (needCollect) { index = this.categories.length; this.categories[index] = category; - // @ts-ignore map.set(category, index); + this._onCollect && this._onCollect(category, index); } else { index = NaN; diff --git a/src/label/labelStyle.ts b/src/label/labelStyle.ts index fd44f45e92..162ec49d55 100644 --- a/src/label/labelStyle.ts +++ b/src/label/labelStyle.ts @@ -323,7 +323,7 @@ export function createTextStyle( return textStyle; } export function createTextConfig( - textStyleModel: Model, + textStyleModel: Model>, opt?: Pick, isNotNormal?: boolean ) { @@ -336,7 +336,7 @@ export function createTextConfig( labelPosition = textStyleModel.getShallow('position') || (isNotNormal ? null : 'inside'); // 'outside' is not a valid zr textPostion value, but used - // in bar series, and magric type should be considered. + // in bar series, and magic type should be considered. labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top'); if (labelPosition != null) { textConfig.position = labelPosition; @@ -357,6 +357,7 @@ export function createTextConfig( : 'auto'; return textConfig; } + /** * The uniform entry of set text style, that is, retrieve style definitions * from `model` and set to `textStyle` object. diff --git a/src/model/Component.ts b/src/model/Component.ts index 889159ff4d..9c20b276a5 100644 --- a/src/model/Component.ts +++ b/src/model/Component.ts @@ -40,8 +40,10 @@ import { ComponentSubType, ComponentFullType, ComponentLayoutMode, - BoxLayoutOptionMixin + BoxLayoutOptionMixin, + NullUndefined } from '../util/types'; +import { CoordinateSystem } from '../coord/CoordinateSystem'; const inner = makeInner<{ defaultOption: ComponentOption @@ -118,6 +120,11 @@ class ComponentModel extends Mode // // `CoordinateSystemHostModel` itself. // coordinateSystem: CoordinateSystemMaster | CoordinateSystemExecutive; + // Determine the layout box based on that coordinate system, if specified. + // Will be injected. + // @see injectCoordinateSystem + boxCoordinateSystem?: CoordinateSystem | NullUndefined; + /** * Support merge layout params. * Only support 'box' now (left/right/top/bottom/width/height). @@ -133,6 +140,7 @@ class ComponentModel extends Mode __viewId: string; __requireNewView: boolean; + static protoInitialize = (function () { const proto = ComponentModel.prototype; proto.type = 'component'; diff --git a/src/model/Series.ts b/src/model/Series.ts index 89be20c7d1..6ede6cc7ca 100644 --- a/src/model/Series.ts +++ b/src/model/Series.ts @@ -146,7 +146,8 @@ class SeriesModel extends ComponentMode // @readonly seriesIndex: number; - // coordinateSystem will be injected in the echarts/CoordinateSystem + // Will be injected. + // @see `injectCoordinateSystem` coordinateSystem: CoordinateSystem; // Injected outside diff --git a/src/model/referHelper.ts b/src/model/referHelper.ts index a3c62c890a..e9010a9835 100644 --- a/src/model/referHelper.ts +++ b/src/model/referHelper.ts @@ -35,6 +35,10 @@ import { SINGLE_REFERRING } from '../util/model'; import { ParallelSeriesOption } from '../chart/parallel/ParallelSeries'; import ParallelModel from '../coord/parallel/ParallelModel'; import ParallelAxisModel from '../coord/parallel/AxisModel'; +import MatrixModel from '../coord/matrix/MatrixModel'; +import type Model from './Model'; +import { AxisBaseOptionCommon } from '../coord/axisCommonTypes'; +import { AxisModelExtendedInCreator } from '../coord/axisModelCreator'; /** * @class @@ -74,12 +78,15 @@ class CoordSysInfo { } } -type SupportedCoordSys = 'cartesian2d' | 'polar' | 'singleAxis' | 'geo' | 'parallel'; +type SupportedCoordSys = 'cartesian2d' | 'polar' | 'singleAxis' | 'geo' | 'parallel' | 'matrix'; +type FetcherAxisModel = + Model> + & Pick; type Fetcher = ( seriesModel: SeriesModel, result: CoordSysInfo, - axisMap: HashMap, - categoryAxisMap: HashMap + axisMap: HashMap, + categoryAxisMap: HashMap ) => void; export function getCoordSysInfoBySeries(seriesModel: SeriesModel) { @@ -92,6 +99,7 @@ export function getCoordSysInfoBySeries(seriesModel: SeriesModel) { } } +// TODO: refactor them to static member of each coord sys, rather than hard code here. const fetchers: Record = { cartesian2d: function ( @@ -202,7 +210,27 @@ const fetchers: Record = { } } }); - } + }, + + matrix: function (seriesModel, result, axisMap, categoryAxisMap) { + const matrixModel = seriesModel.getReferringComponents( + 'matrix', SINGLE_REFERRING + ).models[0] as MatrixModel; + + if (__DEV__) { + if (!matrixModel) { + throw new Error('matrix coordinate system should be specified.'); + } + } + + result.coordSysDims = ['x', 'y']; + const xModel = matrixModel.getDimensionModel('x'); + const yModel = matrixModel.getDimensionModel('y'); + axisMap.set('x', xModel); + axisMap.set('y', yModel); + categoryAxisMap.set('x', xModel); + categoryAxisMap.set('y', yModel); + }, }; function isCategory(axisModel: AxisBaseModel) { diff --git a/src/util/graphic.ts b/src/util/graphic.ts index a7e26e1d98..08511e2e57 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -38,7 +38,7 @@ import Arc from 'zrender/src/graphic/shape/Arc'; import CompoundPath from 'zrender/src/graphic/CompoundPath'; import LinearGradient from 'zrender/src/graphic/LinearGradient'; import RadialGradient from 'zrender/src/graphic/RadialGradient'; -import BoundingRect from 'zrender/src/core/BoundingRect'; +import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import OrientedBoundingRect from 'zrender/src/core/OrientedBoundingRect'; import Point from 'zrender/src/core/Point'; import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; @@ -52,7 +52,8 @@ import { ZRRectLike, ZRStyleProps, CommonTooltipOption, - ComponentItemTooltipLabelFormatterParams + ComponentItemTooltipLabelFormatterParams, + NullUndefined } from './types'; import { extend, @@ -63,7 +64,8 @@ import { keys, each, hasOwn, - isArray + isArray, + isNumber } from 'zrender/src/core/util'; import { getECData } from './innerStore'; import ComponentModel from '../model/Component'; @@ -91,6 +93,9 @@ const _customShapeMap: Dictionary<{ new(): Path }> = {}; type ExtendShapeOpt = Parameters[0]; type ExtendShapeReturn = ReturnType; +export const XY = ['x', 'y'] as const; +export const WH = ['width', 'height'] as const; + /** * Extend shape with parameters */ @@ -281,16 +286,16 @@ export function subPixelOptimizeLine( /** * Sub pixel optimize rect for canvas */ -export function subPixelOptimizeRect(param: { +export function subPixelOptimizeRect( shape: { x: number, y: number, width: number, height: number }, style: { - lineWidth: number + lineWidth?: number } -}) { - subPixelOptimizeUtil.subPixelOptimizeRect(param.shape, param.shape, param.style); - return param; +) { + subPixelOptimizeUtil.subPixelOptimizeRect(shape, shape, style); + return shape; } /** @@ -562,6 +567,87 @@ function nearZero(val: number) { return val <= (1e-6) && val >= -(1e-6); } +/** + * FIXME: merge this method with `applyMarginToRect` in other git branch. + * + * NOTE: + * A negative-width/height rect (due to negative margins) is not supported; + * it will be clampped to zero width/height. + * Although negative-width/height rects can be defined reasonably following the + * similar sense in CSS, but they are rarely used, hard to understand and complicated. + * + * @param rect Assume its width/height >= 0 if existing. + * x/y/width/height is allowed to be NaN, + * for the case that only x/width or y/height is intended to be computed. + * @param delta + * If be `number[]`, should be `[top, right, bottom, left]`, + * which can be used in padding or margin case. + * @see `normalizeCssArray` in `util/format.ts` + * If be `number`, it means [delta, delta, delta, delta], + * which can be used in lineWidth (borderWith) case, + * [NOTICE]: commonly pass lineWidth / 2, following the convention that border is + * half inside half outside of the rect. + * @param shrinkOrExpand + * `true` - shrink if `delta[i]` is positive, commmonly used in `padding` case. + * `false` - expand if `delta[i]` is positive, commmonly used in `margin` case. (default) + * @param noNegative + * `true` - negative `delta[i]` will be clampped to 0. + * `false` - No clamp to `delta`. (defualt). + */ +export function expandOrShrinkRect( + rect: RectLike, + delta: number[] | number | NullUndefined, + shrinkOrExpand: boolean, + noNegative: boolean +): void { + if (delta == null) { + return; + } + else if (isNumber(delta)) { + _tmpExpandRectDelta[0] = _tmpExpandRectDelta[1] = _tmpExpandRectDelta[2] = _tmpExpandRectDelta[3] = delta; + } + else { + _tmpExpandRectDelta[0] = delta[0]; + _tmpExpandRectDelta[1] = delta[1]; + _tmpExpandRectDelta[2] = delta[2]; + _tmpExpandRectDelta[3] = delta[3]; + } + if (noNegative) { + _tmpExpandRectDelta[0] = Math.max(0, _tmpExpandRectDelta[0]); + _tmpExpandRectDelta[1] = Math.max(0, _tmpExpandRectDelta[1]); + _tmpExpandRectDelta[2] = Math.max(0, _tmpExpandRectDelta[2]); + _tmpExpandRectDelta[3] = Math.max(0, _tmpExpandRectDelta[3]); + } + if (shrinkOrExpand) { + _tmpExpandRectDelta[0] = -_tmpExpandRectDelta[0]; + _tmpExpandRectDelta[1] = -_tmpExpandRectDelta[1]; + _tmpExpandRectDelta[2] = -_tmpExpandRectDelta[2]; + _tmpExpandRectDelta[3] = -_tmpExpandRectDelta[3]; + } + expandRectOnOneDimension(rect, _tmpExpandRectDelta, 'x', 'width', 3, 1); + expandRectOnOneDimension(rect, _tmpExpandRectDelta, 'y', 'height', 0, 2); +} +const _tmpExpandRectDelta = [0, 0, 0, 0]; +function expandRectOnOneDimension( + rect: RectLike, delta: number[], xy: 'x' | 'y', wh: 'width' | 'height', ltIdx: 3 | 0, rbIdx: 1 | 2 +): void { + const deltaSum = delta[rbIdx] + delta[ltIdx]; + const oldSize = rect[wh]; + rect[wh] += deltaSum; + if (rect[wh] < 0) { + rect[wh] = 0; + // Try to make the position of the zero rect reasonable in most visual cases. + rect[xy] += ( + delta[ltIdx] >= 0 ? -delta[ltIdx] + : delta[rbIdx] >= 0 ? oldSize + delta[rbIdx] + : Math.abs(deltaSum) > 1e-8 ? oldSize * delta[ltIdx] / deltaSum + : 0 + ); + } + else { + rect[xy] -= delta[ltIdx]; + } +} export function setTooltipConfig(opt: { el: Element, diff --git a/src/util/layout.ts b/src/util/layout.ts index 31445d83e0..b237077a16 100644 --- a/src/util/layout.ts +++ b/src/util/layout.ts @@ -20,13 +20,17 @@ // Layout helpers for each component positioning import * as zrUtil from 'zrender/src/core/util'; -import BoundingRect from 'zrender/src/core/BoundingRect'; +import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import {parsePercent} from './number'; import * as formatUtil from './format'; -import { BoxLayoutOptionMixin, ComponentLayoutMode } from './types'; +import { BoxLayoutOptionMixin, ComponentLayoutMode, NullUndefined } from './types'; import Group from 'zrender/src/graphic/Group'; import Element from 'zrender/src/Element'; import { Dictionary } from 'zrender/src/core/types'; +import { ComponentModel } from '../echarts.all'; +import ExtensionAPI from '../core/ExtensionAPI'; +import { error } from './log'; +import { BoxCoordinateSystemCoordFrom, getCoordForBoxCoordSys } from '../core/CoordinateSystem'; const each = zrUtil.each; @@ -189,6 +193,13 @@ export function getAvailableSize( }; } +type GetLayoutRectInputContainerRect = { + x?: number, // 0 by default + y?: number, // 0 by default + width: number, + height: number, +}; + /** * Parse position info. */ @@ -196,7 +207,7 @@ export function getLayoutRect( positionInfo: BoxLayoutOptionMixin & { aspect?: number // aspect is width / height }, - containerRect: {width: number, height: number}, + containerRect: GetLayoutRectInputContainerRect, margin?: number | number[] ): LayoutRect { margin = formatUtil.normalizeCssArray(margin || 0); @@ -287,11 +298,105 @@ export function getLayoutRect( height = containerHeight - verticalMargin - top - (bottom || 0); } - const rect = new BoundingRect(left + margin[3], top + margin[0], width, height) as LayoutRect; + const rect = new BoundingRect( + (containerRect.x || 0) + left + margin[3], + (containerRect.y || 0) + top + margin[0], + width, + height + ) as LayoutRect; rect.margin = margin; return rect; } +type CreateBoxLayoutReferenceOpt = { + // Use this only if: + // - Intending to layout based on coord sys that can not get a rect from `dataToLayout`. + // - Can layout based only on center but a rect is not essential, such as pie, chord. + enableLayoutOnlyByCenter?: TEnableByCenter; +}; +export const BoxLayoutReferenceType = { + rect: 1, + point: 2, +} as const; +export type BoxLayoutReferenceType = (typeof BoxLayoutReferenceType)[keyof typeof BoxLayoutReferenceType]; + +type BoxLayoutReferenceResult = TEnableByCenter extends true + ? (BoxLayoutReferenceRectResult | BoxLayoutReferencePointResult) + : BoxLayoutReferenceRectResult; +type BoxLayoutReferenceRectResult = { + // This is the defualt way. + type: typeof BoxLayoutReferenceType.rect; + refContainer: LayoutRect; + refPoint: number[]; // The center of rect in this case. + boxCoordFrom: BoxCoordinateSystemCoordFrom | NullUndefined; +}; +type BoxLayoutReferencePointResult = { + // This is available only if `enableLayoutOnlyByCenter: true` + // and `layoutRect` is not available. + type: typeof BoxLayoutReferenceType.point; + refPoint: number[]; + boxCoordFrom: BoxCoordinateSystemCoordFrom | NullUndefined; +}; + +/** + * Uniformly calculate layout reference (rect or center) based on either: + * - viewport: + * - Get `refContainer` as `{x: 0, y: 0, width: api.getWidth(), height: api.getHeight()}` + * - coordinate system, which can serve in several ways: + * - Use `dataToPoint` to get the `refPoint`, such as, in cartesian2d coord sys. + * - Use `dataToLayout` to get the `refContainer`, such as, in matrix coord sys. + */ +export function createBoxLayoutReference( + model: ComponentModel, + api: ExtensionAPI, + opt?: CreateBoxLayoutReferenceOpt +): BoxLayoutReferenceResult { + + let refContainer: RectLike | NullUndefined; + let refPoint: number[] | NullUndefined; + let layoutRefType: BoxLayoutReferenceType | NullUndefined; + + const boxCoordSys = model.boxCoordinateSystem; + let boxCoordFrom: BoxCoordinateSystemCoordFrom | NullUndefined; + if (boxCoordSys) { + const {coord, from} = getCoordForBoxCoordSys(model); + // Do not use `clamp` in `dataToLayout` and `dataToPoint`, because: + // 1. Should support overflow (such as, by dataZoom), where NaN should be in the result. + // 2. Be consistent with the way used in `series.data` + if (boxCoordSys.dataToLayout) { + layoutRefType = BoxLayoutReferenceType.rect; + boxCoordFrom = from; + const result = boxCoordSys.dataToLayout(coord); + refContainer = result.contentRect || result.rect; + } + else if (opt && opt.enableLayoutOnlyByCenter && boxCoordSys.dataToPoint) { + layoutRefType = BoxLayoutReferenceType.point; + boxCoordFrom = from; + refPoint = boxCoordSys.dataToPoint(coord); + } + else { + if (__DEV__) { + error(`${model.type}[${model.componentIndex}]` + + ` layout based on ${boxCoordSys.type} is not supported.` + ); + } + } + } + + if (layoutRefType == null) { + layoutRefType = BoxLayoutReferenceType.rect; + } + + if (layoutRefType === BoxLayoutReferenceType.rect) { + if (!refContainer) { + refContainer = {x: 0, y: 0, width: api.getWidth(), height: api.getHeight()}; + } + refPoint = [refContainer.x + refContainer.width / 2, refContainer.y + refContainer.height / 2]; + } + + return {type: layoutRefType, refContainer, refPoint, boxCoordFrom} as BoxLayoutReferenceResult; +} + /** * Position a zr element in viewport @@ -336,7 +441,7 @@ export function getLayoutRect( export function positionElement( el: Element, positionInfo: BoxLayoutOptionMixin, - containerRect: {width: number, height: number}, + containerRect: GetLayoutRectInputContainerRect, margin?: number[] | number, opt?: { hv: [1 | 0 | boolean, 1 | 0 | boolean], diff --git a/src/util/model.ts b/src/util/model.ts index 9b3e65ff36..dbeee76fd0 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -45,14 +45,15 @@ import { Payload, OptionId, OptionName, - InterpolatableValue + InterpolatableValue, + NullUndefined, } from './types'; import { Dictionary } from 'zrender/src/core/types'; import SeriesModel from '../model/Series'; import CartesianAxisModel from '../coord/cartesian/AxisModel'; import GridModel from '../coord/cartesian/GridModel'; import { isNumeric, getRandomIdBase, getPrecision, round } from './number'; -import { warn } from './log'; +import { error, warn } from './log'; function interpolateNumber(p0: number, p1: number, percent: number): number { return (p1 - p0) * percent + p0; @@ -952,16 +953,32 @@ export function queryReferringComponents( } if (indexOption === 'none' || indexOption === false) { - assert(opt.enableNone, '`"none"` or `false` is not a valid value on index option.'); - result.models = []; - return result; + if (opt.enableNone) { + result.models = []; + return result; + } + else { + // Do not throw; consider if some component previously does not use this method, + // and start to use it, need to be fault-tolerant for backward compatibility. + if (__DEV__) { + error('`"none"` or `false` is not a valid value on index option.'); + } + indexOption = -1; // Can not query by index but may still query by id/name if specified. + } } // `queryComponents` will return all components if // both all of index/id/name are null/undefined. if (indexOption === 'all') { - assert(opt.enableAll, '`"all"` is not a valid value on index option.'); - indexOption = idOption = nameOption = null; + if (opt.enableAll) { + indexOption = idOption = nameOption = null; + } + else { + if (__DEV__) { + error('`"all"` is not a valid value on index option.'); + } + indexOption = -1; + } } result.models = ecModel.queryComponents({ mainType: mainType, @@ -1095,3 +1112,58 @@ export function interpolateRawValues( return interpolated; } } + +/** + * Use an iterator to avoid exposing the internal list or duplicating it + * for the outside traveller, and no extra heap allocation. + * @usage + * for (const it = resetIterator(); it.next();) { + * const item = it.item; + * const key = it.key; + * const itIdx = it.itIdx; + * // ... + * } + * @usage + * const it = resetIterator(); + * while (it.next()) { ... } + * @usage + * for (resetIterator(it); it.next();) { ... } + */ +export class ListIterator { + + private _idx: number; + private _end: number; + private _list: TItem[]; + private _step: number; + + item: TItem | NullUndefined; + key: number; + + /** + * The loop condition is `idx < end` if `step > 0`; + * The loop condition is `idx >= end` if `step < 0`. + * + * @param end By default `list.length` if `step > 0`; `0` if `step < 0`. + * @param step By default `1`. + */ + reset(list: TItem[], start: number, end?: number, step?: number): ListIterator { + this._list = list; + this._step = step = step || 1; + this._idx = start; + this._end = end != null ? end : step > 0 ? list.length : 0; + + this.item = null; + this.key = NaN; + + return this; + } + + next(): boolean { + if (this._step > 0 ? this._idx < this._end : this._idx >= this._end) { + this.item = this._list[this._idx]; + this.key = this._idx = this._idx + this._step; + return true; + } + return false; + } +} diff --git a/src/util/number.ts b/src/util/number.ts index cd6f42994b..10053afc56 100644 --- a/src/util/number.ts +++ b/src/util/number.ts @@ -37,6 +37,9 @@ function _trim(str: string): string { return str.replace(/^\s+|\s+$/g, ''); } +export const mathMin = Math.min; +export const mathMax = Math.max; + /** * Linear mapping a value from domain to range * @param val @@ -100,33 +103,48 @@ export function linearMap( } /** - * Convert a percent string to absolute number. - * Returns NaN if percent is not a valid string or number + * Preserve the name `parsePercent` for backward compatibility, + * and it's effectively published as `echarts.number.parsePercent`. */ -export function parsePercent(percent: number | string, all: number): number { - switch (percent) { +export const parsePercent = parsePositionOption; + +/** + * @see {parsePositionSizeOption} and also accept a string preset. + * @see {PositionSizeOption} + */ +export function parsePositionOption(option: unknown, percentBase: number): number { + switch (option) { case 'center': case 'middle': - percent = '50%'; + option = '50%'; break; case 'left': case 'top': - percent = '0%'; + option = '0%'; break; case 'right': case 'bottom': - percent = '100%'; + option = '100%'; break; } - if (zrUtil.isString(percent)) { - if (_trim(percent).match(/%$/)) { - return parseFloat(percent) / 100 * all; - } + return parsePositionSizeOption(option, percentBase); +} - return parseFloat(percent); +/** + * Accept number, or numeric stirng (`'123'`), or percentage ('100%'), as x/y/width/height pixel number. + * If null/undefined or invalid, return NaN. + * (But allow JS type coercion (`+option`) due to backward compatibility) + * @see {PositionSizeOption} + */ +export function parsePositionSizeOption(option: unknown, percentBase: number): number { + if (zrUtil.isString(option)) { + if (_trim(option).match(/%$/)) { + return parseFloat(option) / 100 * percentBase; + } + return parseFloat(option); } - - return percent == null ? NaN : +percent; + // Allow flexible input due to backward compatibility. + return option == null ? NaN : +option; } /** diff --git a/src/util/types.ts b/src/util/types.ts index 9045eadc4a..af351b36e5 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -345,9 +345,10 @@ export type TooltipOrderMode = 'valueAsc' | 'valueDesc' | 'seriesAsc' | 'seriesD // Data and dimension related types // --------------------------------- +// Represents the values in series.data that need to be treated in a non-numeric way. +// Can be used by axis.type 'category' (i.e. scale.type 'ordinal'). // Finally the user data will be parsed and stored in `list._storage`. // `NaN` represents "no data" (raw data `null`/`undefined`/`NaN`/`'-'`). -// `Date` will be parsed to timestamp. // Ordinal/category data will be parsed to its index if possible, otherwise // keep its original string in list._storage. // Check `convertValue` for more details. @@ -387,6 +388,23 @@ export type ParsedValueNumeric = number | OrdinalNumber; */ export type ScaleDataValue = ParsedValueNumeric | OrdinalRawValue | Date; +/** + * - `ScaleDataValue`: + * e.g. geo accept that primative input, like `convertToPixel('some_place')`; + * Some coord sys, such as 'cartesian2d', also supports that for only query only a single axis. + * - `ScaleDataValue[]`: + * This is the most common case, each array item represent each data in + * every dimension required by the coord sys. e.g., `[12, 98]` represents `[xData, yData]`. + * - `(ScaleDataValue[])[]`: + * represents `[data_range_x, data_range_y]`. e.g., `dataToPoint([[5, 600], [8889, 9000]])`, + * represents data range `[5, 600]` in x, and `[8889, 9000]` in y. + * Can be also `[5, [8999, 9000]]`. + */ +export type CoordinateSystemDataCoord = + (ScaleDataValue | NullUndefined) + | (ScaleDataValue | NullUndefined)[] + | (ScaleDataValue | ScaleDataValue[] | NullUndefined)[]; + export interface ScaleTick { level?: number, value: number @@ -421,6 +439,22 @@ export interface OrdinalScaleTick extends ScaleTick { value: number }; +/** + * Return type of API `CoordinateSystem['dataToLayout']`, expose to users. + */ +export interface CoordinateSystemDataLayout { + // Base layout rect for a data item. + rect?: RectLike; + // Commonly equals or shrinked from `rect, may considered padding and border + // (depends on every coordinate system). + contentRect?: RectLike; + // Only available in matrix coordinate system. + matrixXYLocatorRange?: number[][]; + + // May extend. +} + + // Can only be string or index, because it is used in object key in some code. // Making the type alias here just intending to show the meaning clearly in code. export type DimensionIndex = number; @@ -771,17 +805,24 @@ export interface ColorPaletteOptionMixin { color?: ZRColor | ZRColor[] colorLayer?: ZRColor[][] } + /** * Mixin of option set to control the box layout of each component. */ export interface BoxLayoutOptionMixin { - width?: number | string; - height?: number | string; - top?: number | string; - right?: number | string; - bottom?: number | string; - left?: number | string; + width?: PositionSizeOption; + height?: PositionSizeOption; + top?: PositionSizeOption; + right?: PositionSizeOption; + bottom?: PositionSizeOption; + left?: PositionSizeOption; } +/** + * Need to be parsed by `parsePositionOption` or `parsePositionSizeOption`. + * Accept number, or numeric stirng (`'123'`), or percentage ('100%'), as x/y/width/height pixel number. + * If null/undefined or invalid, return NaN. + */ +export type PositionSizeOption = number | string; export interface CircleLayoutOptionMixin { // Can be percent @@ -1018,7 +1059,7 @@ export interface TextCommonOption extends ShadowOptionMixin { borderRadius?: number | number[] padding?: number | number[] - width?: number | string// Percent + width?: number | string // Percent height?: number textBorderColor?: string textBorderWidth?: number @@ -1040,14 +1081,16 @@ export interface LabelFormatterCallback { * LabelOption is an option set to control the style of labels. * Include color, background, shadow, truncate, rotation, distance, etc.. */ -export interface LabelOption extends TextCommonOption { +export interface LabelOption< + TNuance extends {positionExtra: unknown} = {positionExtra: never} +> extends TextCommonOption { /** * If show label */ show?: boolean // TODO: TYPE More specified 'inside', 'insideTop'.... // x, y can be both percent string or number px. - position?: ElementTextConfig['position'] + position?: ElementTextConfig['position'] | TNuance['positionExtra'] distance?: number rotate?: number offset?: number[] @@ -1071,8 +1114,11 @@ export interface LabelOption extends TextCommonOption { rich?: Dictionary } -export interface SeriesLabelOption extends LabelOption { - formatter?: string | LabelFormatterCallback +export interface SeriesLabelOption< + TCallbackDataParams extends CallbackDataParams = CallbackDataParams, + TNuance extends {positionExtra: unknown} = {positionExtra: never} +> extends LabelOption { + formatter?: string | LabelFormatterCallback } /** @@ -1462,8 +1508,25 @@ export interface ComponentOption { z?: number; zlevel?: number; + + coordinateSystem?: string + coordinateSystemUsage?: CoordinateSystemUsageOption + coord?: CoordinateSystemDataCoord } +/** + * - "data": Use it as "dataCoordSys", each data item is laid out based on a coord sys. + * - "box": Use it as "boxCoordSys", the overall bounding rect or anchor point is calculated based on a coord sys. + * e.g., + * grid rect (cartesian rect) is calculate based on matrix/calendar coord sys; + * pie center is calculated based on calendar/cartesian; + * + * The default value (if not declared in option `coordinateSystemUsage`): + * For series, be "data", since this is the most case and backward compatible. + * For non-series components, be "box", since "data" is not applicable. + */ +export type CoordinateSystemUsageOption = 'data' | 'box'; + export type BlurScope = 'coordinateSystem' | 'series' | 'global'; /** @@ -1595,10 +1658,6 @@ export interface SeriesOption< progressive?: number | false progressiveThreshold?: number progressiveChunkMode?: 'mod' - /** - * Not available on every series - */ - coordinateSystem?: string hoverLayerThreshold?: number @@ -1665,7 +1724,7 @@ export interface SeriesOnCalendarOptionMixin { calendarId?: string } -export interface SeriesOnMatrixOptionMixin { +export interface ComponentOnMatrixOptionMixin { matrixIndex?: number matrixId?: string } diff --git a/test/calendar-other-coord-sys.html b/test/calendar-other-coord-sys.html new file mode 100644 index 0000000000..b03f0a4c6b --- /dev/null +++ b/test/calendar-other-coord-sys.html @@ -0,0 +1,748 @@ + + + + + + + calendar + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + diff --git a/test/matrix.html b/test/matrix.html index 41b8f938af..20e744c2ec 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -42,9 +42,9 @@
-
- +
+
@@ -53,8 +53,6 @@ - - - - - - + + + + + + + + + + + + + + + diff --git a/test/matrix2.html b/test/matrix2.html new file mode 100644 index 0000000000..eef0405768 --- /dev/null +++ b/test/matrix2.html @@ -0,0 +1,1802 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/matrix3.html b/test/matrix3.html new file mode 100644 index 0000000000..a4e3c115a2 --- /dev/null +++ b/test/matrix3.html @@ -0,0 +1,858 @@ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + diff --git a/test/matrix_application.html b/test/matrix_application.html index ff689f841c..97c195d9d8 100644 --- a/test/matrix_application.html +++ b/test/matrix_application.html @@ -37,9 +37,11 @@
+
+
+ + + + + + + + + + + + + diff --git a/test/matrix_application2.html b/test/matrix_application2.html new file mode 100644 index 0000000000..1c8c0a1a74 --- /dev/null +++ b/test/matrix_application2.html @@ -0,0 +1,801 @@ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/pie-coordinate-system.html b/test/pie-coordinate-system.html index 969b7685d1..6cdaa6122b 100644 --- a/test/pie-coordinate-system.html +++ b/test/pie-coordinate-system.html @@ -254,7 +254,21 @@ radius: 20, center: ['Tue', 230], data: [ - { value: 1048, name: 'A' }, + { value: 1048, name: 'use_center' }, + { value: 735, name: 'B' }, + { value: 580, name: 'C' }, + { value: 484, name: 'D' }, + { value: 300, name: 'E' } + ] + }, + { + type: 'pie', + name: 'Cartesian2D(category axis & value axis)', + coordinateSystem: 'cartesian2d', + radius: 20, + coord: ['Fri', 130], + data: [ + { value: 1048, name: 'use_coord' }, { value: 735, name: 'B' }, { value: 580, name: 'C' }, { value: 484, name: 'D' }, From 7e49f51534a0ecce3da16068394f73ddbfd01c38 Mon Sep 17 00:00:00 2001 From: 100pah Date: Sun, 1 Jun 2025 00:11:47 +0800 Subject: [PATCH 25/49] fix(matrix): fix test case and typo --- src/coord/matrix/MatrixModel.ts | 5 +++-- test/matrix_application.html | 14 +++++++++----- test/matrix_application2.html | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 7b1f17b9b2..71cf87c453 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -51,7 +51,8 @@ interface MatrixBodyCornerBaseOption extends MatrixCellStyleOption { * It can represent both body cells and top-left corner cells. * * [body/corner cell locating]: - * (The rule applied in `matrix.dataToPoint` and `matrix.dataToLayout`.) + * The rule is uniformly applied, such as, in `matrix.dataToPoint` + * and `matrix.dataToLayout` and `xxxComponent.coord`. * Suppose the matrix.x/y dimensions (header) are defined as: * matrix: { * x: [{ value: 'Xa0', children: ['Xb0', 'Xb1'] }, 'Xa1'], @@ -189,7 +190,7 @@ export interface MatrixCellStyleOption { cursor?: string; // By default, auto decide whether to be silent, considering tooltip. silent?: boolean | NullUndefined; - // Used when style conflict - espetially thick border style. + // Used when style conflict - especially for thick border style. z2?: number; } diff --git a/test/matrix_application.html b/test/matrix_application.html index 97c195d9d8..8043d82ad7 100644 --- a/test/matrix_application.html +++ b/test/matrix_application.html @@ -588,7 +588,10 @@ }, itemStyle: { borderWidth: 0 - } + }, + dividerLineStyle: { + width: 0, + }, }, y: { data: Array.from({length: 10}, (_, i) => i + 1 + ''), @@ -597,7 +600,10 @@ }, itemStyle: { borderWidth: 0 - } + }, + dividerLineStyle: { + width: 0, + }, }, left: 'center', width: 900, @@ -608,7 +614,7 @@ itemStyle: { borderWidth: 0 } - } + }, }, series: { type: 'custom', @@ -831,8 +837,6 @@ + + + + + + + + + + + + + + + + From 31cf38681e9bbcdbf309c737cfdb4d2c6f54cf8b Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 4 Jun 2025 18:28:48 +0800 Subject: [PATCH 29/49] tweak(series data): Add validator to hint incorrect option. --- src/data/helper/dataProvider.ts | 59 +++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts index caf047fb91..c1ac07eebb 100644 --- a/src/data/helper/dataProvider.ts +++ b/src/data/helper/dataProvider.ts @@ -21,7 +21,7 @@ // ??? refactor? check the outer usage of data provider. // merge with defaultDimValueGetter? -import {isTypedArray, extend, assert, each, isObject, bind} from 'zrender/src/core/util'; +import {isTypedArray, extend, assert, each, isObject, bind, isArray} from 'zrender/src/core/util'; import {getDataItemValue} from '../../util/model'; import { createSourceFromSeriesDataOption, Source, isSourceInstance } from '../Source'; import {ArrayLike, Dictionary} from 'zrender/src/core/types'; @@ -37,6 +37,7 @@ import { OptionDataItem, OptionDataValue, SourceFormat, SeriesLayoutBy, ParsedValue, DimensionLoose, NullUndefined } from '../../util/types'; import SeriesData from '../SeriesData'; +import { error } from '../../util/log'; export interface DataProvider { /** @@ -112,9 +113,11 @@ export class DefaultDataProvider implements DataProvider { // declare source is Source; this._source = source; const data = this._data = source.data; + const sourceFormat = source.sourceFormat; + const seriesLayoutBy = source.seriesLayoutBy; // Typed array. TODO IE10+? - if (source.sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { + if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { if (__DEV__) { if (dimSize == null) { throw new Error('Typed array data must specify dimension size'); @@ -125,6 +128,11 @@ export class DefaultDataProvider implements DataProvider { this._data = data; } + if (__DEV__) { + const validator = rawSourceDataValidatorMap[getMethodMapKey(sourceFormat, seriesLayoutBy)]; + validator && validator(data, source.dimensionsDefine); + } + mountMethods(this, data, source); } @@ -286,6 +294,39 @@ export class DefaultDataProvider implements DataProvider { } +type RawSourceDataValidator = ( + rawData: OptionSourceData, + dimsDef: { name?: DimensionName }[], +) => void; + +const validateSimply: RawSourceDataValidator = function ( + rawData +) { + if (!isArray(rawData)) { + error('series.data or dataset.source must be an array.'); + } +}; + +/** + * Only run in dev mode - hint users for debug. + */ +const rawSourceDataValidatorMap: Dictionary = { + [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: validateSimply, + [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: validateSimply, + [SOURCE_FORMAT_OBJECT_ROWS]: validateSimply, + [SOURCE_FORMAT_KEYED_COLUMNS]: function ( + rawData, dimsDef + ) { + for (let i = 0; i < dimsDef.length; i++) { + const dimName = dimsDef[i].name; + if (dimName == null) { + error('dimension name must not be null/undefined.'); + } + } + }, + [SOURCE_FORMAT_ORIGINAL]: validateSimply +}; + type RawSourceItemGetter = ( rawData: OptionSourceData, @@ -328,12 +369,7 @@ const rawSourceItemGetterMap: Dictionary = { const item = out || []; for (let i = 0; i < dimsDef.length; i++) { const dimName = dimsDef[i].name; - if (__DEV__) { - if (dimName == null) { - throw new Error(); - } - } - const col = (rawData as Dictionary)[dimName]; + const col = dimName != null ? (rawData as Dictionary)[dimName] : null; item[i] = col ? col[idx] : null; } return item; @@ -383,12 +419,7 @@ const rawSourceDataCounterMap: Dictionary = { rawData, startIndex, dimsDef ) { const dimName = dimsDef[0].name; - if (__DEV__) { - if (dimName == null) { - throw new Error(); - } - } - const col = (rawData as Dictionary)[dimName]; + const col = dimName != null ? (rawData as Dictionary)[dimName] : null; return col ? col.length : 0; }, [SOURCE_FORMAT_ORIGINAL]: countSimply From 3123a3a930194a6ab2e2229a20d424fcaef996a6 Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 4 Jun 2025 19:57:14 +0800 Subject: [PATCH 30/49] test(matrix): Add an application case. --- test/matrix_application.html | 321 ++++++++++++++++++++++++++++++++++- 1 file changed, 319 insertions(+), 2 deletions(-) diff --git a/test/matrix_application.html b/test/matrix_application.html index 8043d82ad7..0f6ea81a5f 100644 --- a/test/matrix_application.html +++ b/test/matrix_application.html @@ -28,6 +28,7 @@ + @@ -41,7 +42,8 @@
-
+
+
+ + + + + + + + + + + + + From 4a978a0a9fbe7e7d7f60d2c6d5a5b797e2270615 Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 4 Jun 2025 23:44:30 +0800 Subject: [PATCH 31/49] feat(matrix): fix treemap breadcrumb layout in matrix. --- src/chart/treemap/Breadcrumb.ts | 33 +++++++++++++----------------- src/util/layout.ts | 36 --------------------------------- 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/src/chart/treemap/Breadcrumb.ts b/src/chart/treemap/Breadcrumb.ts index 0875c4a2b3..413e2ec8b3 100644 --- a/src/chart/treemap/Breadcrumb.ts +++ b/src/chart/treemap/Breadcrumb.ts @@ -41,11 +41,6 @@ interface OnSelectCallback { } interface LayoutParam { - pos: BoxLayoutOptionMixin - box: { - width: number, - height: number - } emptyItemWidth: number totalWidth: number renderList: { @@ -87,29 +82,29 @@ class Breadcrumb { const textStyleModel = normalStyleModel.getModel('textStyle'); const emphasisTextStyleModel = emphasisModel.getModel(['itemStyle', 'textStyle']); + const refContainer = layout.createBoxLayoutReference(seriesModel, api).refContainer; + const boxLayoutParams = { + left: model.get('left'), + right: model.get('right'), + top: model.get('top'), + bottom: model.get('bottom') + }; const layoutParam: LayoutParam = { - pos: { - left: model.get('left'), - right: model.get('right'), - top: model.get('top'), - bottom: model.get('bottom') - }, - box: { - width: api.getWidth(), - height: api.getHeight() - }, emptyItemWidth: model.get('emptyItemWidth'), totalWidth: 0, renderList: [] }; - + const availableSize = layout.getLayoutRect( + boxLayoutParams, + refContainer, + ); this._prepare(targetNode, layoutParam, textStyleModel); this._renderContent( - seriesModel, layoutParam, normalStyleModel, + seriesModel, layoutParam, availableSize, normalStyleModel, emphasisModel, textStyleModel, emphasisTextStyleModel, onSelect ); - layout.positionElement(thisGroup, layoutParam.pos, layoutParam.box); + layout.positionElement(thisGroup, boxLayoutParams, refContainer); } /** @@ -139,6 +134,7 @@ class Breadcrumb { _renderContent( seriesModel: TreemapSeriesModel, layoutParam: LayoutParam, + availableSize: {width: number, height: number}, normalStyleModel: BreadcrumbItemStyleModel, emphasisModel: BreadcrumbEmphasisItemStyleModel, textStyleModel: BreadcrumbTextStyleModel, @@ -149,7 +145,6 @@ class Breadcrumb { let lastX = 0; const emptyItemWidth = layoutParam.emptyItemWidth; const height = seriesModel.get(['breadcrumb', 'height']); - const availableSize = layout.getAvailableSize(layoutParam.pos, layoutParam.box); let totalWidth = layoutParam.totalWidth; const renderList = layoutParam.renderList; const emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle(); diff --git a/src/util/layout.ts b/src/util/layout.ts index f098f0b7fe..8578bdaad4 100644 --- a/src/util/layout.ts +++ b/src/util/layout.ts @@ -163,42 +163,6 @@ export const vbox = zrUtil.curry(boxLayout, 'vertical'); */ export const hbox = zrUtil.curry(boxLayout, 'horizontal'); -/** - * If x or x2 is not specified or 'center' 'left' 'right', - * the width would be as long as possible. - * If y or y2 is not specified or 'middle' 'top' 'bottom', - * the height would be as long as possible. - */ -export function getAvailableSize( - positionInfo: { - left?: number | string - top?: number | string - right?: number | string - bottom?: number | string - }, - containerRect: { width: number, height: number }, - margin?: number[] | number -) { - const containerWidth = containerRect.width; - const containerHeight = containerRect.height; - - let x = parsePercent(positionInfo.left, containerWidth); - let y = parsePercent(positionInfo.top, containerHeight); - let x2 = parsePercent(positionInfo.right, containerWidth); - let y2 = parsePercent(positionInfo.bottom, containerHeight); - - (isNaN(x) || isNaN(parseFloat(positionInfo.left as string))) && (x = 0); - (isNaN(x2) || isNaN(parseFloat(positionInfo.right as string))) && (x2 = containerWidth); - (isNaN(y) || isNaN(parseFloat(positionInfo.top as string))) && (y = 0); - (isNaN(y2) || isNaN(parseFloat(positionInfo.bottom as string))) && (y2 = containerHeight); - - margin = formatUtil.normalizeCssArray(margin || 0); - - return { - width: Math.max(x2 - x - margin[1] - margin[3], 0), - height: Math.max(y2 - y - margin[0] - margin[2], 0) - }; -} type CircleLayoutSeriesOption = SeriesOption & CircleLayoutOptionMixin<{ // `center: string | number` has been accepted in series.pie. From 59e8c645a3e94fd441f329187f9b38cd159e50b9 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 18:01:19 +0800 Subject: [PATCH 32/49] feat(matrix): change the definition of cell.size and levelSize - based on matrix size rather than viewport size. --- src/coord/matrix/Matrix.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 451e4e04fb..487996fd81 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -124,15 +124,14 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { private _resize(matrixModel: MatrixModel, api: ExtensionAPI) { const dims = this._dims; const dimModels = this._dimModels; - const viewportSize = new Point(api.getWidth(), api.getHeight()); const rect = this._rect = getLayoutRect(matrixModel.getBoxLayoutParams(), { - width: viewportSize.x, - height: viewportSize.y, + width: api.getWidth(), + height: api.getHeight(), }); - layOutUnitsOnDimension(dimModels, dims, rect, viewportSize, 0); - layOutUnitsOnDimension(dimModels, dims, rect, viewportSize, 1); + layOutUnitsOnDimension(dimModels, dims, rect, 0); + layOutUnitsOnDimension(dimModels, dims, rect, 1); layOutDimCellsRestInfoByUnit(0, dims); layOutDimCellsRestInfoByUnit(1, dims); @@ -325,7 +324,6 @@ function layOutUnitsOnDimension( dimModels: Matrix['_dimModels'], dims: MatrixDimPair, matrixRect: RectLike, - viewportSize: Point, dimIdx: number ): void { const otherDimIdx = 1 - dimIdx; @@ -363,7 +361,7 @@ function layOutUnitsOnDimension( } } function layOutSpecified(item: MatrixCellLayoutInfo, sizeOption: unknown): void { - const size = parseSizeOption(sizeOption, dimIdx, matrixRect, viewportSize); + const size = parseSizeOption(sizeOption, dimIdx, matrixRect); if (!eqNaN(size)) { item.wh = confineSize(size, restSize); restSize = confineSize(restSize - item.wh); @@ -457,9 +455,8 @@ function parseSizeOption( sizeOption: unknown, dimIdx: number, matrixRect: RectLike, - viewportSize: Point ): number { - const sizeNum = parsePositionSizeOption(sizeOption, viewportSize[XY[dimIdx]]); + const sizeNum = parsePositionSizeOption(sizeOption, matrixRect[WH[dimIdx]]); return confineSize(sizeNum, matrixRect[WH[dimIdx]]); } From 1a3e819daf51c947cf9cf73aaf3e29a550a39f27 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 18:48:12 +0800 Subject: [PATCH 33/49] feat(matrix): (1) Fix text truncate area. (2) Remove extra padding, use label.padding uniformly. --- src/chart/treemap/Breadcrumb.ts | 2 +- src/component/matrix/MatrixView.ts | 75 ++++-------- src/coord/matrix/MatrixModel.ts | 26 +++- src/label/labelStyle.ts | 16 ++- test/matrix2.html | 9 +- test/matrix3.html | 190 ++++++++++++++++++++++++----- 6 files changed, 225 insertions(+), 93 deletions(-) diff --git a/src/chart/treemap/Breadcrumb.ts b/src/chart/treemap/Breadcrumb.ts index 413e2ec8b3..7c7124ac97 100644 --- a/src/chart/treemap/Breadcrumb.ts +++ b/src/chart/treemap/Breadcrumb.ts @@ -25,7 +25,7 @@ import TreemapSeriesModel, { TreemapSeriesNodeItemOption, TreemapSeriesOption } import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import { curry, defaults } from 'zrender/src/core/util'; -import { ZRElementEvent, BoxLayoutOptionMixin, ECElement } from '../../util/types'; +import { ZRElementEvent, ECElement } from '../../util/types'; import Element from 'zrender/src/Element'; import Model from '../../model/Model'; import { convertOptionIdName } from '../../util/model'; diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index 62478940a3..7cef55cb39 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -19,7 +19,6 @@ import MatrixModel, { MatrixBaseCellOption, MatrixCellStyleOption, MatrixOption } from '../../coord/matrix/MatrixModel'; import ComponentView from '../../view/Component'; -import { createTextStyle } from '../../label/labelStyle'; import { MatrixCellLayoutInfo, MatrixDim, MatrixXYLocator } from '../../coord/matrix/MatrixDim'; import Model from '../../model/Model'; import { NullUndefined } from '../../util/types'; @@ -30,12 +29,12 @@ import { ItemStyleProps } from '../../model/mixin/itemStyle'; import { LineStyleProps } from '../../model/mixin/lineStyle'; import { LineShape } from 'zrender/src/graphic/shape/Line'; import { subPixelOptimize } from 'zrender/src/graphic/helper/subPixelOptimize'; -import { Group, Text, Rect, Line, XY, WH, setTooltipConfig, expandOrShrinkRect } from '../../util/graphic'; +import { Group, Text, Rect, Line, XY, setTooltipConfig, expandOrShrinkRect } from '../../util/graphic'; import { ListIterator } from '../../util/model'; -import { isNumber, normalizeCssArray, retrieve2 } from 'zrender/src/core/util'; +import { clone, retrieve2 } from 'zrender/src/core/util'; import { createNaNRectLike } from '../../coord/matrix/matrixCoordHelper'; import { MatrixBodyCorner, MatrixBodyOrCornerKind } from '../../coord/matrix/MatrixBodyCorner'; -import { ElementTextConfig } from 'zrender/src/Element'; +import { setLabelStyle } from '../../label/labelStyle'; const round = Math.round; @@ -64,6 +63,9 @@ class MatrixView extends ComponentView { const xDim = xDimModel.dim; const yDim = yDimModel.dim; + // PENDING: + // reuse the existing text and rect elements for performance? + renderDimensionCells( group, matrixModel @@ -278,7 +280,7 @@ function createMatrixCell( if (cursorOption != null) { cellRect.attr('cursor', cursorOption); } - let cellText: Text; + let cellText: Text | NullUndefined; if (textValue != null) { const text = textValue + ''; @@ -287,31 +289,24 @@ function createMatrixCell( if (_tmpCellLabelModel.getShallow('show')) { labelShown = true; - const padding = decideCellPadding(_tmpCellModel); - const lineWidth = cellRect && cellRect.style && cellRect.style.lineWidth; - BoundingRect.copy(_tmpContentRect, shape); - expandOrShrinkRect(_tmpContentRect, lineWidth, true, true); - expandOrShrinkRect(_tmpContentRect, padding, true, true); - - // Be conservative, currently do not use `labelStyle.ts#createTextConfig` - // to support all `LabelOption`. - const textConfig: ElementTextConfig = {}; - textConfig.position = _tmpCellLabelModel.getShallow('position') || 'inside'; - textConfig.distance = retrieve2(_tmpCellLabelModel.getShallow('distance'), 5); - cellRect.setTextConfig(textConfig); - - cellRect.setTextContent( - cellText = new Text({ - style: createTextStyle(_tmpCellLabelModel, { - text, - width: makeCellLabelWH(_tmpContentRect, _tmpCellLabelModel, 0), - height: makeCellLabelWH(_tmpContentRect, _tmpCellLabelModel, 1), - }), - ignoreHostSilent: true, - z2: z2 + 1, - }) + const lineWidth = cellRect.style?.lineWidth || 0; + // `lineWidth` is half outside half inside the bounding rect. + expandOrShrinkRect(_tmpContentRect, lineWidth / 2, true, true); + + setLabelStyle( + cellRect, + // Currently do not support other states (`emphasis`, `select`, `blur`) + {normal: _tmpCellLabelModel}, + { + defaultText: text, + autoOverflowArea: true, + // By default based on boundingRect. But boundingRect contains borderWidth, + // and borderWidth is half outside the cell. Thus specific `layoutRect` explicitly. + layoutRect: clone(cellRect.shape) + }, ); + cellText = cellRect.getTextContent(); } setTooltipConfig({ // At least for text overflow. el: cellRect, @@ -325,6 +320,7 @@ function createMatrixCell( } if (cellText) { + cellText.z2 = z2 + 1; let labelSilent = _tmpCellLabelModel.get('silent'); // auto, tooltip of text cells need silient: false, but non-text cells // do not need a special cursor in most cases. @@ -332,6 +328,7 @@ function createMatrixCell( labelSilent = !(tooltipOptionShow && labelShown); } cellText.silent = labelSilent; + cellText.ignoreHostSilent = true; } let rectSilent = _tmpCellModel.get('silent'); if (rectSilent == null) { @@ -388,26 +385,4 @@ function createMatrixLine(shape: Omit, style: LineStylePro }); } -function makeCellLabelWH( - contentRect: RectLike, cellLabelModel: Model, dimIdx: number -): number { - // - To add some spacing between the text and cell border, using percentage value for `label.width/height` - // is not ideal, becuase the padding would vary with the cell size, making the layout inconsistent. - // - `lineWith` is not considered deliberatedly, since it may be modified with user interaction. - let size = cellLabelModel.getShallow(WH[dimIdx]); - if (!isNumber(size)) { - size = contentRect[WH[dimIdx]]; - } - return size; -} - -export function decideCellPadding(cellModel: Model): number[] { - const paddingOption = retrieve2(cellModel.getShallow('padding'), DEFAULT_CELL_PADDING_IF_TEXT); - // If cell is used to fill other series, such as heatmap, it's preferable no padding, - // but it's irrelevant since currently `dataToLayout` does not consider padding. - return normalizeCssArray(paddingOption); -} -// Consider text overflow truncate -const DEFAULT_CELL_PADDING_IF_TEXT = 3; - export default MatrixView; diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 5bfab709f6..810efc7dc2 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -43,6 +43,8 @@ export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { // Used on the outer border and the divider line. borderZ2?: number; tooltip?: CommonTooltipOption; + + // PENDING: do we need to support other states, i.e., `emphasis`, `blur`, `select`? } interface MatrixBodyCornerBaseOption extends MatrixCellStyleOption { @@ -64,12 +66,12 @@ interface MatrixBodyCornerBaseOption extends MatrixCellStyleOption { * |cornerQ|cornerP| Xb0 | Xb1 | | * |-------+-------+-------+-------+-------- * | | Yb0 | bodyR | bodyS | | - * | Ya0 |-------+-------+-------+-------| + * | Ya0 |-------+-------+---------------| * | | Yb1 | | bodyT | * |---------------|------------------------ * "Locator number" (`MatrixXYLocator`): * The term `locator` refers to a integer number to locate cells on x or y direction. - * Use the top-left corner of the body as the orgin point (0, 0), + * Use the top-left cell of the body as the origin point (0, 0), * the non-negative locator indicates the right/bottom of the origin point; * the negative locator indicates the left/top of the origin point. * "Ordinal number" (`OrdinalNumber`): @@ -160,6 +162,7 @@ export interface MatrixDimensionCellOption extends MatrixBaseCellOption { // column width (for x dimension) or row height (for y dimension). // If not specified (null/undefined), auto calculate it. // Only available on leaves, to avoid unnecessary complex. + // If it is a percentage, such as '30%', based on the matrix width/height, rather than the canvas size. size?: PositionSizeOption; children?: MatrixDimensionCellOption[]; } @@ -170,6 +173,7 @@ export interface MatrixDimensionLevelOption { // `matrix.levels[i].levelSize` specifies the size of a certain level. // For x dimension, that is height; for y dimension, that is width. // If not specified (null/undefined), auto calculate it. + // If it is a percentage, such as '30%', based on the matrix width/height, rather than the canvas size. levelSize?: PositionSizeOption; // Other level specific options may added if needed, such as border-bottom/right style. } @@ -183,10 +187,16 @@ export interface MatrixDimensionModel extends Model { * - priority-low: style options defined in `matrix.x/y/coner/body` */ export interface MatrixCellStyleOption { + // [NOTE - padding]: + // - Consider the option for the space between cell boundary to text, percentage value for + // `label.width/height` is not supported, because it is not ideal - the padding would vary + // with the cell size, making the layout inconsistent. + // - The inner text padding uses `lable.style.padding`. + // The text truncation rect is obtained by cell rect minus by padding. + // - The inner series / other coord sys padding is not supported, to avoid necessary complexity. + // Consider some series, such as heatmap, prefer no padding. label?: LabelOption; itemStyle?: ItemStyleOption; - // Padding for the inner content (label / series / other coord sys). - padding?: number | number[]; cursor?: string; // By default, auto decide whether to be silent, considering tooltip. silent?: boolean | NullUndefined; @@ -204,14 +214,18 @@ export interface MatrixTooltipFormatterParams { const defaultLabelOption: LabelOption = { show: true, color: '#333', - overflow: 'truncate', + // overflow: 'truncate', + overflow: 'break', lineOverflow: 'truncate', + padding: [2, 3, 2, 3], + // Prefer to use `padding`, rather than distance. + distance: 0, }; function makeDefaultCellItemStyleOption(isCorner: boolean) { return { color: 'none', borderWidth: 1, - borderColor: isCorner ? 'transparent' : '#ccc', + borderColor: isCorner ? 'none' : '#ccc', }; }; const defaultDimOption: MatrixDimensionOption = { diff --git a/src/label/labelStyle.ts b/src/label/labelStyle.ts index dc856efde9..4bcbc14b05 100644 --- a/src/label/labelStyle.ts +++ b/src/label/labelStyle.ts @@ -65,6 +65,9 @@ type TextCommonParams = { // supportLegacyAuto?: boolean textStyle?: ZRStyleProps + + autoOverflowArea?: ElementTextConfig['autoOverflowArea'], + layoutRect?: ElementTextConfig['layoutRect'], }; const EMPTY_OBJ = {}; @@ -313,7 +316,7 @@ export function getLabelStatesModels( export function createTextStyle( textStyleModel: Model, specifiedTextStyle?: TextStyleProps, // Fixed style in the code. Can't be set by model. - opt?: Pick, + opt?: Parameters[2], isNotNormal?: boolean, isAttached?: boolean // If text is attached on an element. If so, auto color will handling in zrender. ) { @@ -325,7 +328,10 @@ export function createTextStyle( } export function createTextConfig( textStyleModel: Model>, - opt?: Pick, + opt?: Pick< + TextCommonParams, + 'defaultOutsidePosition' | 'inheritColor' | 'autoOverflowArea' | 'layoutRect' + >, isNotNormal?: boolean ) { opt = opt || {}; @@ -356,6 +362,12 @@ export function createTextConfig( textConfig.outsideFill = textStyleModel.get('color') === 'inherit' ? (opt.inheritColor || null) : 'auto'; + if (opt.autoOverflowArea != null) { + textConfig.autoOverflowArea = opt.autoOverflowArea; + } + if (opt.layoutRect != null) { + textConfig.layoutRect = opt.layoutRect; + } return textConfig; } diff --git a/test/matrix2.html b/test/matrix2.html index eef0405768..dd3049f992 100644 --- a/test/matrix2.html +++ b/test/matrix2.html @@ -1093,9 +1093,10 @@ return; } const {input, expectDefault} = _testCase; - const isDefaultCase = - (_convertToLayoutClampOpt == null || _convertToLayoutClampOpt === MatrixClampOption.none) - && !_ignoreMergeCells; + const shouldBeClampped = !( + _convertToLayoutClampOpt == null || _convertToLayoutClampOpt === MatrixClampOption.none + ); + const isDefaultCase = !shouldBeClampped && !_ignoreMergeCells; const meta = calcMeta(); let opt = undefined; @@ -1134,7 +1135,7 @@ assert(isFinite(result.rect[prop])); } } - else { + else if (shouldBeClampped) { assert(isFinite(result.rect[prop])); } }); diff --git a/test/matrix3.html b/test/matrix3.html index 98ef478ac8..68ad46a081 100644 --- a/test/matrix3.html +++ b/test/matrix3.html @@ -39,7 +39,7 @@
-
+
@@ -872,6 +872,7 @@ var chart = testHelper.create(echarts, 'main_layout_components_series', { title: [ 'Declaratively lay out components(coord sys) and series.', + 'Roam trigger area should be only the cell' ], height: 550, option: option, @@ -1003,6 +1004,28 @@ require(['echarts'], function (echarts) { var LARGE_TEXT_EN = `Thus, we have to figure out what kind of exist brexit isMoreover, for many years, people with brexit have been viewed as different. It is disappointing that, for me, the emergent rise of brexit may change my life. By contrasts, what could we do anyway to ahchieve brexit. Chiefly, rebecca West told us that, the trouble about man is twofold. He cannot learn truths which are too complicated; he forgets truths which are too simple. That solved my problem. It is important to note that If we think about it from a different point of view, for example, friedrich Nietzsche stated in his book that, i have forgotten my umbrella.  That aroused my imagination. Similarly, with these questions, let's take a closer look to brexit. Therefore, we generally say, if we grasped the key of the problem of brexit, everthing else will be easily solved. With respect to We have been thinking about brexit for a long time.Above all, under this inevitable circumstance situation, we have to solve brexit. It is important to note that As this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. Above all, does the government provide help for brexit? Besides, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Similarly, the evidence presented in this assignment has shown that, in particular, david Mamet concluded that, it's only words.. unless they're true. That inspired me. However, brexit has become increasingly evident among teens according to many scientists, however, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. In addition, for me, the emergent rise of brexit may change my life. On the other hand, brexit continues to be a relevant controversial issue in society today, as In comparison, what should we do to give rise to brexit, and what should we do to prevent brexit's happening. Alternatively, the problem lies in the difference of people raised by brexit, on the contrary, what is the key point of brexit? To look at another way, the evidence presented in this assignment has shown that, to look at another way, for brexit, the key point is how to express brexit. Furthermore, why does brexit happen? It is important to note that What is the key point of brexit? Chiefly, according to Orson Scott Card, eko brushed a tear from her eye, and Immo jeered at her, but father held up a hand. "Never mock a tender heart," he said. That solved my problem. To look at another way, if we think about it from a different point of view, alternatively, what is the key point of brexit? Furthermore, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. It is important to note that We have been thinking about brexit for a long time.Since that is so, for me, the emergent rise of brexit may change my life. Thus, we have to face an embarrassing situation, that is, in that case, now, solving brexit is becoming a more and more significant issue, in fact, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. To look at another way, when facing this difficult choice of brexit, i rarely slept well. By contrasts, if we think about it from a different point of view, as in the following example, rebecca McClanahan argued that, the shortest distance between two points is always under construction. Which enlighten me. With respect to If brexit takes place in our daliy life, we have to consider its consequence. In that case, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Besides, as far as I know, on the other hand, in general, however, brexit continues to be a relevant controversial issue in society today, as Thus, under this inevitable circumstance situation, we have to solve brexit. It is disappointing that, as far as I know, it is disappointing that, the problem lies in the difference of people raised by brexit, similarly, after seeing this evidence, there is no way we can agree with brexit. In other words, we have been thinking about brexit for a long time.In comparison, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. On the other hand, brexit has become increasingly evident among teens according to many scientists, alternatively, more current studies of people who take brexit heavely show no evidence of emotional damage. On the one hand, from my piont of view, brexit means a lot to me, and In particular, rebecca West told us that, the trouble about man is twofold. He cannot learn truths which are too complicated; he forgets truths which are too simple. Which enlighten me. In particular, according to Ljupka Cvetanova, smile and the world will laugh at you. That inspired me. In addition, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Namely, steve Wozniak told us that, never trust a computer you can't throw out a window. That solved my problem. Besides, why does brexit happen? Since that is so, for me, the emergent rise of brexit may change my life. On the one hand, in my opinion, in fact, what is the key point of brexit? Above all, when facing this difficult choice of brexit, i rarely slept well. Another way of viewing this is, modern vehement arguments against brexit alone become suspect. As in the following example, according to David Mamet, it's only words.. unless they're true. That solved my problem. Similarly, why does brexit happen? Another way of viewing this is, we have to face an embarrassing situation, that is, in that case, brexit continues to be a relevant controversial issue in society today, as By contrasts, in general, on the one hand, we generally say, if we grasped the key of the problem of brexit, everthing else will be easily solved. In fact, now, solving brexit is becoming a more and more significant issue, above all, with these questions, let's take a closer look to brexit. However, what is the key point of brexit? In comparison, brexit is a common condition among civilians in today’s society, so, thus, after seeing this evidence, there is no way we can agree with brexit. With respect to we have to face an embarrassing situation, that is, it is disappointing that, what could we do anyway to ahchieve brexit. It is important to note that When facing this difficult choice of brexit, i rarely slept well. In particular, kamand Kojouri showed us that, what you choose also chooses you. That solved my problem. By contrasts, some people may disagree with brexit, by contrasts, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. Since that is so, more current studies of people who take brexit heavely show no evidence of emotional damage. Alternatively, for brexit, the key point is how to express brexit. Since that is so, what if brexit happens, and what if brexit does not happen. Since that is so, every person has to face these problems caused by brexit, when facing these problems, in fact, more current studies of people who take brexit heavely show no evidence of emotional damage. To look at another way, we have to face an embarrassing situation, that is, in that case, the problem lies in the difference of people raised by brexit, similarly, is inevitable. In fact, for me, the emergent rise of brexit may change my life. In comparison, what should we do to give rise to brexit, and what should we do to prevent brexit's happening. It is important to note that As this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. It is important to note that brexit has become increasingly evident among teens according to many scientists, that is to say, james Thurber mentioned that, all human beings should try to learn before they die what they are running from, and to, and why. That solved my problem. Similarly, the consequece of brexit is of great significance to me, and to many other people. Chiefly, to quote from William Penn, all Excess is ill: but Drunkenness is of the worst Sort. It spoils Health, dismounts the Mind, and unmans Men: it reveals Secrets, is Quarrelsome, lascivious, impudent, dangerous and Mad. In fine, he that is drunk is not a Man: because he is so long void of Reason, that distinguishes a Man from a Beast. That inspired me. Therefore, with these questions, let's take a closer look to brexit. Since that is so, as far as I know, thus, for me, the emergent rise of brexit may change my life. Thus, for brexit, the key point is how to express brexit. Above all, now, solving brexit is becoming a more and more significant issue, in fact, in my opinion, furthermore, is inevitable. Namely, plato, symposium / Phaedrus stated in his book that, there is truth in wine and children. That inspired me. Above all, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. In fact, what is the key point of brexit? On the one hand, but these are not the most urgent issue, a more pressing issue about brexit is, alternatively, the problem lies in the difference of people raised by brexit, on the other hand, but these are not the most urgent issue, a more pressing issue about brexit is, in addition, the problem lies in the difference of people raised by brexit, thus, if brexit takes place in our daliy life, we have to consider its consequence. Another possibility is, to a certain extent, brexit is right. That is to say, james Thurber showed us that, all human beings should try to learn before they die what they are running from, and to, and why. Which enlighten me. In fact, in my opinion, namely, to quote from Comte de Lautréamont, we say sound things when we do not strive to say to say extraordinary ones. Which brought a new way of thinking it. `; + var _xDataAItem = {value: 'a'}; + var _xData = [ + _xDataAItem, + {value: LARGE_TEXT_EN}, + // 'c' + ]; + var _yDataTItem = {value: 't'}; + var _yData = [ + 'r', 's', _yDataTItem + ]; + var _bodyCellCase1 = { + coord: ['a', 't'], + value: 'The quick brown fox jumps over the lazy dog.', + label: {color: 'red'}, + }; + var _bodyData = [ + { + coord: ['a', 's'], + value: LARGE_TEXT_EN + }, + _bodyCellCase1, + ]; var dates = ["2010", '2011', '2012', '2013']; var option = { @@ -1018,41 +1041,40 @@ 'overflow-wrap:break-word;', ].join('') }, - matrix: { + title: [{ + text: [ + '- expect: by default only text show tooltip, but cell border is silent.', + '- expect: by default padding should be appropriate.' + ].join('\n'), + top: 5, + left: 10, + textStyle: {fontSize: 12, color: 'blue'}, + }], + matrix: [{ + id: 'matrix0', + left: 10, + width: '80%', + bottom: '55%', tooltip: {show: true}, - x: { - data: [ - 'a', - {value: LARGE_TEXT_EN}, - 'c' - ] - }, - y: { - data: ['r', 's', 't'] - }, - body: { - data: [ - { - coord: ['a', 's'], - value: LARGE_TEXT_EN - } - ] - } - }, + x: {data: _xData}, + y: {data: _yData}, + body: {data: _bodyData}, + }], }; - var chart = testHelper.create(echarts, 'main_text_overflow', { + var chart = testHelper.create(echarts, 'main_text_overflow_and_tooltip', { title: [ - 'Cell text overflow', - 'silent expect: by default only text show tooltip, but cell border is silent', + 'Cell text overflow and tooltip.', ], option: option, - height: 300, + height: 550, + // boundingRect: true, + draggable: true, inputsStyle: 'compact', inputs: [{ type: 'select', text: 'text overflow:', - values: ['truncate', 'break', 'none', undefined], + values: ['break', 'truncate', 'breakAll', 'none', undefined], onchange() { const val = this.value; chart.setOption({ @@ -1125,13 +1147,121 @@ var padding = this.value; chart.setOption({ matrix: { - x: {padding}, - y: {padding}, - body: {padding}, - corner: {padding}, + x: {label: {padding}}, + y: {label: {padding}}, + body: {label: {padding}}, + corner: {label: {padding}}, } }); } + }, { + type: 'select', + text: 'borderWidth:', + options: [{ + value: undefined + }, { + input: { + type: 'range', + value: 1, + min: -10, + max: 50, + } + }], + onchange() { + const borderWidth = this.value; + chart.setOption({ + matrix: { + x: {itemStyle: {borderWidth}}, + y: {itemStyle: {borderWidth}}, + body: {itemStyle: {borderWidth}}, + }, + }); + } + }, { + type: 'range', + text: 'col a size(%):', + min: 0, + max: 100, + value: 80, + onchange: function () { + const size = this.value + '%' + _xDataAItem.size = size; + chart.setOption({ + matrix: {x: {data: _xData}} + }); + } + }, { + type: 'range', + text: 'row t size(%):', + min: 0, + max: 100, + value: 80, + onchange: function () { + const size = this.value + '%' + _yDataTItem.size = size; + chart.setOption({ + matrix: {y: {data: _yData}} + }); + } + }, { + type: 'br', + }, { + type: 'select', + text: `cell ${testHelper.printObject(_bodyCellCase1.coord)} label.position:`, + values: [ + 'inside', + 'insideTop', 'insideLeft', 'insideRight', 'insideBottom', + 'insideTopLeft', 'insideTopRight', 'insideBottomLeft', 'insideBottomRight', + 'left', 'right', 'top', 'bottom', + ], + onchange() { + var position = this.value; + _bodyCellCase1.label.position = position; + chart.setOption({ + matrix: {body: {data: _bodyData}} + }); + } + }, { + type: 'select', + text: `cell ${testHelper.printObject(_bodyCellCase1.coord)} label.rotate:`, + options: [ + {value: undefined}, + {value: 90}, + {value: 180}, + {value: 270}, + {input: {type: 'range', min: -180, max: 180, value: 0}}, + ], + onchange() { + var rotate = this.value; + _bodyCellCase1.label.rotate = rotate; + chart.setOption({ + matrix: {body: {data: _bodyData}} + }); + } + }, { + type: 'br', + }, { + type: 'select', + text: `cell ${testHelper.printObject(_bodyCellCase1.coord)} label.align:`, + values: [undefined, 'left', 'center', 'right'], + onchange() { + var textAlign = this.value; + _bodyCellCase1.label.align = textAlign; + chart.setOption({ + matrix: {body: {data: _bodyData}} + }); + } + }, { + type: 'select', + text: `cell ${testHelper.printObject(_bodyCellCase1.coord)} label.verticalAlign:`, + values: [undefined, 'top', 'middle', 'bottom'], + onchange() { + var verticalAlign = this.value; + _bodyCellCase1.label.verticalAlign = verticalAlign; + chart.setOption({ + matrix: {body: {data: _bodyData}} + }); + } }] }); From 9b3f71eea38ac1f8b5bbe8f9242a6747a8479189 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 18:48:51 +0800 Subject: [PATCH 34/49] test(matrix): Add visual test (partial, not complete) --- test/runTest/actions/__meta__.json | 3 +++ test/runTest/actions/matrix2.json | 1 + test/runTest/actions/matrix_application.json | 1 + test/runTest/actions/matrix_application2.json | 1 + 4 files changed, 6 insertions(+) create mode 100644 test/runTest/actions/matrix2.json create mode 100644 test/runTest/actions/matrix_application.json create mode 100644 test/runTest/actions/matrix_application2.json diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 397679110f..5192ef5670 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -150,6 +150,9 @@ "mapWorld": 1, "markArea": 3, "marker-case": 1, + "matrix_application": 4, + "matrix_application2": 2, + "matrix2": 5, "media-dataZoom": 1, "media-finance": 2, "media-pie": 1, diff --git a/test/runTest/actions/matrix2.json b/test/runTest/actions/matrix2.json new file mode 100644 index 0000000000..b3eecf24ba --- /dev/null +++ b/test/runTest/actions/matrix2.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":543,"x":612,"y":165},{"type":"mousemove","time":743,"x":318,"y":198},{"type":"mousemove","time":950,"x":260,"y":218},{"type":"mousemove","time":1227,"x":259,"y":218},{"type":"mousemove","time":1427,"x":134,"y":108},{"type":"mousemove","time":1633,"x":121,"y":64},{"type":"mousemove","time":1849,"x":122,"y":54},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":2734,"target":"select"},{"time":2735,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2757,"x":262,"y":55},{"type":"mousemove","time":2958,"x":277,"y":57},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"1","time":3935,"target":"select"},{"time":3936,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3956,"x":277,"y":73},{"type":"mousemove","time":4158,"x":284,"y":56},{"type":"mousemove","time":4371,"x":285,"y":54},{"type":"mousemove","time":4643,"x":285,"y":54},{"type":"mousemove","time":4854,"x":115,"y":74},{"type":"mousedown","time":4983,"x":99,"y":71},{"type":"mousemove","time":5066,"x":98,"y":71},{"type":"mouseup","time":5092,"x":98,"y":71},{"time":5093,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5193,"x":101,"y":69},{"type":"mousemove","time":5400,"x":110,"y":62},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":6588,"target":"select"},{"time":6589,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6633,"x":262,"y":54},{"type":"mousemove","time":6836,"x":282,"y":56},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"0","time":8051,"target":"select"},{"time":8052,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8069,"x":285,"y":38},{"type":"mousemove","time":8270,"x":297,"y":41},{"type":"mousemove","time":8477,"x":317,"y":47},{"type":"mousemove","time":8677,"x":388,"y":57},{"type":"mousemove","time":8878,"x":409,"y":125},{"type":"mousemove","time":9085,"x":354,"y":144},{"type":"mousemove","time":9293,"x":324,"y":142},{"type":"mousedown","time":9450,"x":347,"y":123},{"type":"mousemove","time":9493,"x":347,"y":123},{"type":"mouseup","time":9542,"x":347,"y":123},{"time":9543,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9793,"x":347,"y":126},{"type":"mousemove","time":9994,"x":371,"y":153},{"type":"mousemove","time":10201,"x":429,"y":133},{"type":"mousemove","time":10410,"x":574,"y":116},{"type":"mousedown","time":10575,"x":577,"y":114},{"type":"mousemove","time":10616,"x":577,"y":114},{"type":"mouseup","time":10692,"x":577,"y":114},{"time":10693,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10976,"x":582,"y":115},{"type":"mousedown","time":11187,"x":601,"y":121},{"type":"mousemove","time":11206,"x":601,"y":121},{"type":"mouseup","time":11309,"x":601,"y":121},{"time":11310,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11559,"x":598,"y":121},{"type":"mousemove","time":11768,"x":552,"y":125},{"type":"mousedown","time":11866,"x":549,"y":121},{"type":"mousemove","time":11969,"x":549,"y":121},{"type":"mouseup","time":11993,"x":549,"y":121},{"time":11994,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12176,"x":548,"y":155},{"type":"mousemove","time":12377,"x":590,"y":160},{"type":"mousedown","time":12560,"x":594,"y":150},{"type":"mousemove","time":12584,"x":594,"y":150},{"type":"mouseup","time":12692,"x":594,"y":150},{"time":12693,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12927,"x":594,"y":152},{"type":"mousemove","time":13137,"x":600,"y":184},{"type":"mousemove","time":13343,"x":592,"y":184},{"type":"mousedown","time":13501,"x":619,"y":177},{"type":"mousemove","time":13551,"x":619,"y":177},{"type":"mouseup","time":13609,"x":619,"y":177},{"time":13610,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13865,"x":618,"y":179},{"type":"mousemove","time":14071,"x":575,"y":216},{"type":"mousemove","time":14277,"x":575,"y":214},{"type":"mousedown","time":14401,"x":578,"y":201},{"type":"mousemove","time":14484,"x":578,"y":201},{"type":"mouseup","time":14510,"x":578,"y":201},{"time":14511,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14711,"x":568,"y":200},{"type":"mousemove","time":14922,"x":207,"y":158},{"type":"mousedown","time":15117,"x":233,"y":154},{"type":"mousemove","time":15151,"x":233,"y":154},{"type":"mouseup","time":15225,"x":233,"y":154},{"time":15226,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15476,"x":231,"y":154},{"type":"mousedown","time":15657,"x":178,"y":185},{"type":"mousemove","time":15693,"x":178,"y":185},{"type":"mouseup","time":15746,"x":178,"y":185},{"time":15747,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15976,"x":175,"y":185},{"type":"mousemove","time":16176,"x":88,"y":164},{"type":"mousedown","time":16199,"x":88,"y":164},{"type":"mouseup","time":16325,"x":88,"y":164},{"time":16326,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16377,"x":88,"y":164},{"type":"mousemove","time":16494,"x":88,"y":164},{"type":"mousemove","time":16706,"x":91,"y":119},{"type":"mousedown","time":16813,"x":90,"y":106},{"type":"mousemove","time":16907,"x":90,"y":106},{"type":"mouseup","time":16934,"x":90,"y":106},{"time":16935,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17160,"x":90,"y":107},{"type":"mousemove","time":17360,"x":74,"y":444},{"type":"mousemove","time":17568,"x":91,"y":349},{"type":"mousedown","time":17743,"x":96,"y":296},{"type":"mousemove","time":17787,"x":96,"y":296},{"type":"mouseup","time":17861,"x":96,"y":296},{"time":17862,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18093,"x":99,"y":296},{"type":"mousemove","time":18304,"x":122,"y":352},{"type":"mousemove","time":18505,"x":121,"y":575},{"type":"mousemove","time":18644,"x":87,"y":595},{"type":"mousemove","time":18844,"x":72,"y":569},{"type":"mousedown","time":18901,"x":72,"y":569},{"type":"mouseup","time":19027,"x":72,"y":569},{"time":19028,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19069,"x":72,"y":569},{"type":"mousemove","time":19193,"x":72,"y":569},{"type":"mousemove","time":19393,"x":153,"y":583},{"type":"mousedown","time":19585,"x":128,"y":581},{"type":"mousemove","time":19601,"x":128,"y":581},{"type":"mouseup","time":19709,"x":128,"y":581},{"time":19710,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19995,"x":129,"y":581},{"type":"mousedown","time":20163,"x":185,"y":571},{"type":"mousemove","time":20211,"x":186,"y":571},{"type":"mouseup","time":20260,"x":186,"y":571},{"time":20261,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20494,"x":188,"y":571},{"type":"mousemove","time":20704,"x":425,"y":559},{"type":"mousedown","time":20772,"x":426,"y":560},{"type":"mouseup","time":20876,"x":426,"y":560},{"time":20877,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20909,"x":424,"y":562},{"type":"mousemove","time":21119,"x":372,"y":563},{"type":"mousedown","time":21330,"x":315,"y":568},{"type":"mousemove","time":21339,"x":315,"y":568},{"type":"mouseup","time":21444,"x":315,"y":568},{"time":21445,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21539,"x":315,"y":571},{"type":"mousemove","time":21743,"x":534,"y":568},{"type":"mousedown","time":21943,"x":571,"y":566},{"type":"mousemove","time":21976,"x":571,"y":566},{"type":"mouseup","time":22059,"x":571,"y":566},{"time":22060,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22210,"x":571,"y":565},{"type":"mousemove","time":22411,"x":621,"y":556},{"type":"mousedown","time":22433,"x":621,"y":556},{"type":"mouseup","time":22562,"x":621,"y":556},{"time":22563,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22618,"x":621,"y":536},{"type":"mousemove","time":22835,"x":416,"y":500},{"type":"mousedown","time":22974,"x":396,"y":501},{"type":"mousemove","time":23040,"x":396,"y":501},{"type":"mouseup","time":23097,"x":396,"y":501},{"time":23098,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23526,"x":396,"y":501},{"type":"mousemove","time":23727,"x":376,"y":468},{"type":"mousedown","time":23744,"x":376,"y":468},{"type":"mouseup","time":23842,"x":375,"y":467},{"time":23843,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23938,"x":375,"y":467},{"type":"mousemove","time":24094,"x":375,"y":467},{"type":"mousemove","time":24306,"x":372,"y":215},{"type":"mousemove","time":24506,"x":363,"y":197},{"type":"mousedown","time":24602,"x":374,"y":188},{"type":"mouseup","time":24709,"x":374,"y":188},{"time":24710,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24756,"x":374,"y":188},{"type":"mousemove","time":25012,"x":370,"y":186},{"type":"mousemove","time":25216,"x":336,"y":160},{"type":"mousedown","time":25260,"x":335,"y":160},{"type":"mousemove","time":25417,"x":335,"y":160},{"type":"mouseup","time":25442,"x":335,"y":160},{"time":25443,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25540,"x":335,"y":162},{"type":"mousemove","time":25760,"x":335,"y":194},{"type":"mousedown","time":25782,"x":335,"y":194},{"type":"mouseup","time":25895,"x":335,"y":194},{"time":25896,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25960,"x":335,"y":194},{"type":"mousemove","time":26169,"x":295,"y":196},{"type":"mousedown","time":26193,"x":295,"y":196},{"type":"mouseup","time":26276,"x":295,"y":196},{"time":26277,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26374,"x":295,"y":208},{"type":"mousedown","time":26579,"x":295,"y":284},{"type":"mousemove","time":26588,"x":295,"y":284},{"type":"mouseup","time":26692,"x":295,"y":284},{"time":26693,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26993,"x":295,"y":286},{"type":"mousemove","time":27205,"x":361,"y":332},{"type":"mousemove","time":27408,"x":381,"y":324},{"type":"mousemove","time":27612,"x":411,"y":438},{"type":"mousemove","time":27817,"x":401,"y":376},{"type":"mousemove","time":28018,"x":400,"y":401},{"type":"mousedown","time":28140,"x":401,"y":390},{"type":"mousemove","time":28218,"x":401,"y":390},{"type":"mouseup","time":28261,"x":401,"y":390},{"time":28262,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28445,"x":399,"y":388},{"type":"mousemove","time":28655,"x":230,"y":186},{"type":"mousemove","time":28862,"x":217,"y":170},{"type":"mousedown","time":28918,"x":217,"y":169},{"type":"mouseup","time":29010,"x":217,"y":169},{"time":29011,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29071,"x":217,"y":169},{"type":"mousemove","time":29176,"x":217,"y":170},{"type":"mousemove","time":29377,"x":218,"y":240},{"type":"mousedown","time":29511,"x":215,"y":243},{"type":"mousemove","time":29585,"x":215,"y":243},{"type":"mouseup","time":29626,"x":215,"y":243},{"time":29627,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29785,"x":187,"y":267},{"type":"mousemove","time":29991,"x":155,"y":269},{"type":"mousedown","time":30102,"x":148,"y":269},{"type":"mouseup","time":30192,"x":148,"y":269},{"time":30193,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":30239,"x":148,"y":269},{"type":"mousemove","time":30443,"x":148,"y":271},{"type":"mousemove","time":30643,"x":191,"y":496},{"type":"mousemove","time":30843,"x":209,"y":465},{"type":"mousedown","time":30942,"x":210,"y":461},{"type":"mouseup","time":31043,"x":210,"y":461},{"time":31044,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31089,"x":210,"y":461},{"type":"mousemove","time":31585,"x":211,"y":456},{"type":"mousemove","time":31786,"x":387,"y":301},{"type":"mousemove","time":32003,"x":452,"y":204},{"type":"mousemove","time":32227,"x":452,"y":201},{"type":"mousemove","time":32436,"x":303,"y":75},{"type":"mousemove","time":32644,"x":288,"y":60},{"type":"mousemove","time":32844,"x":416,"y":73},{"type":"mousemove","time":33053,"x":532,"y":55},{"type":"mousemove","time":33268,"x":533,"y":54},{"type":"mousemove","time":33427,"x":533,"y":54}],"scrollY":102,"scrollX":0,"timestamp":1748966621272},{"name":"Action 2","ops":[{"type":"mousemove","time":512,"x":423,"y":296},{"type":"mousemove","time":716,"x":466,"y":169},{"type":"mousemove","time":920,"x":556,"y":49},{"type":"mousemove","time":1129,"x":525,"y":35},{"type":"mousemove","time":1336,"x":523,"y":43},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":2588,"target":"select"},{"time":2589,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2629,"x":519,"y":63},{"type":"mousemove","time":3363,"x":518,"y":64},{"type":"mousemove","time":3571,"x":461,"y":107},{"type":"mousemove","time":3790,"x":336,"y":235},{"type":"mousedown","time":4006,"x":336,"y":235},{"type":"mousemove","time":4016,"x":339,"y":236},{"type":"mousemove","time":4219,"x":411,"y":287},{"type":"mousemove","time":4437,"x":412,"y":288},{"type":"mouseup","time":4478,"x":412,"y":288},{"time":4479,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4762,"x":412,"y":288},{"type":"mousemove","time":4969,"x":382,"y":313},{"type":"mousemove","time":5230,"x":382,"y":313},{"type":"mousemove","time":5430,"x":382,"y":309},{"type":"mousemove","time":5637,"x":384,"y":304},{"type":"mousemove","time":5846,"x":408,"y":289},{"type":"mousedown","time":6037,"x":466,"y":264},{"type":"mousemove","time":6055,"x":466,"y":264},{"type":"mouseup","time":6194,"x":466,"y":264},{"time":6195,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6312,"x":467,"y":264},{"type":"mousemove","time":6513,"x":586,"y":164},{"type":"mousedown","time":6561,"x":586,"y":164},{"type":"mouseup","time":6698,"x":586,"y":164},{"time":6699,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6744,"x":586,"y":164},{"type":"mousemove","time":6878,"x":586,"y":164},{"type":"mousemove","time":7078,"x":591,"y":210},{"type":"mousedown","time":7130,"x":591,"y":213},{"type":"mouseup","time":7237,"x":591,"y":213},{"time":7238,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7295,"x":591,"y":213},{"type":"mousemove","time":7579,"x":591,"y":214},{"type":"mousemove","time":7787,"x":585,"y":229},{"type":"mousedown","time":7811,"x":585,"y":229},{"type":"mouseup","time":7945,"x":585,"y":229},{"time":7946,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8013,"x":585,"y":229},{"type":"mousemove","time":8081,"x":585,"y":229},{"type":"mousemove","time":8295,"x":587,"y":120},{"type":"mousedown","time":8392,"x":588,"y":103},{"type":"mouseup","time":8495,"x":588,"y":103},{"time":8496,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8517,"x":588,"y":103},{"type":"mousemove","time":8778,"x":588,"y":103},{"type":"mousemove","time":8994,"x":549,"y":104},{"type":"mousedown","time":9112,"x":549,"y":104},{"type":"mousemove","time":9206,"x":549,"y":104},{"type":"mouseup","time":9244,"x":549,"y":104},{"time":9245,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9433,"x":544,"y":104},{"type":"mousemove","time":9643,"x":345,"y":127},{"type":"mousedown","time":9701,"x":345,"y":127},{"type":"mouseup","time":9861,"x":345,"y":127},{"time":9862,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9933,"x":345,"y":127},{"type":"mousemove","time":9958,"x":344,"y":127},{"type":"mousemove","time":10166,"x":297,"y":117},{"type":"mousemove","time":10369,"x":244,"y":109},{"type":"mousedown","time":10533,"x":214,"y":102},{"type":"mousemove","time":10571,"x":213,"y":102},{"type":"mouseup","time":10611,"x":213,"y":102},{"time":10612,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10868,"x":209,"y":102},{"type":"mousemove","time":11083,"x":100,"y":111},{"type":"mousedown","time":11228,"x":90,"y":105},{"type":"mousemove","time":11287,"x":90,"y":105},{"type":"mouseup","time":11361,"x":90,"y":105},{"time":11362,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11646,"x":90,"y":106},{"type":"mousedown","time":11833,"x":91,"y":166},{"type":"mousemove","time":11870,"x":91,"y":166},{"type":"mouseup","time":11945,"x":91,"y":166},{"time":11946,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12162,"x":92,"y":166},{"type":"mousedown","time":12316,"x":168,"y":171},{"type":"mousemove","time":12377,"x":169,"y":171},{"type":"mouseup","time":12428,"x":169,"y":171},{"time":12429,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12581,"x":283,"y":171},{"type":"mousedown","time":12762,"x":307,"y":171},{"type":"mousemove","time":12801,"x":307,"y":171},{"type":"mouseup","time":12851,"x":307,"y":171},{"time":12852,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13045,"x":307,"y":175},{"type":"mousemove","time":13266,"x":212,"y":267},{"type":"mousedown","time":13324,"x":210,"y":268},{"type":"mouseup","time":13461,"x":209,"y":268},{"time":13462,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13500,"x":209,"y":268},{"type":"mousemove","time":13711,"x":145,"y":281},{"type":"mousedown","time":13802,"x":145,"y":281},{"type":"mousemove","time":13925,"x":145,"y":281},{"type":"mouseup","time":13950,"x":145,"y":281},{"time":13951,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14015,"x":145,"y":282},{"type":"mousemove","time":14232,"x":362,"y":454},{"type":"mousedown","time":14301,"x":363,"y":455},{"type":"mouseup","time":14436,"x":363,"y":455},{"time":14437,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14458,"x":363,"y":455},{"type":"mousemove","time":14666,"x":366,"y":453},{"type":"mousemove","time":14871,"x":336,"y":506},{"type":"mousedown","time":15029,"x":336,"y":507},{"type":"mousemove","time":15084,"x":336,"y":507},{"type":"mouseup","time":15178,"x":336,"y":507},{"time":15179,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15214,"x":336,"y":507},{"type":"mousedown","time":15435,"x":279,"y":551},{"type":"mousemove","time":15459,"x":279,"y":551},{"type":"mouseup","time":15548,"x":279,"y":551},{"time":15549,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15670,"x":182,"y":551},{"type":"mousemove","time":15870,"x":75,"y":552},{"type":"mousedown","time":15952,"x":68,"y":550},{"type":"mouseup","time":16083,"x":68,"y":550},{"time":16084,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16110,"x":81,"y":550},{"type":"mousemove","time":16326,"x":684,"y":550},{"type":"mousemove","time":16533,"x":597,"y":564},{"type":"mousedown","time":16599,"x":583,"y":561},{"type":"mouseup","time":16728,"x":582,"y":561},{"time":16729,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16762,"x":582,"y":561},{"type":"mousedown","time":16954,"x":602,"y":504},{"type":"mousemove","time":16998,"x":602,"y":504},{"type":"mouseup","time":17045,"x":602,"y":504},{"time":17046,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17234,"x":602,"y":504},{"type":"mousemove","time":17444,"x":569,"y":50},{"type":"mousemove","time":17646,"x":549,"y":51},{"type":"mousemove","time":17855,"x":549,"y":51},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"2","time":18940,"target":"select"},{"time":18941,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18995,"x":444,"y":128},{"type":"mousemove","time":19195,"x":543,"y":83},{"type":"mousedown","time":19272,"x":547,"y":82},{"type":"mouseup","time":19380,"x":547,"y":82},{"time":19381,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19426,"x":547,"y":82},{"type":"mousedown","time":19644,"x":601,"y":82},{"type":"mousemove","time":19682,"x":601,"y":82},{"type":"mouseup","time":19788,"x":601,"y":82},{"time":19789,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19838,"x":601,"y":84},{"type":"mousemove","time":20052,"x":601,"y":132},{"type":"mousemove","time":20265,"x":601,"y":134},{"type":"mousedown","time":20323,"x":601,"y":134},{"type":"mouseup","time":20428,"x":601,"y":134},{"time":20429,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20477,"x":601,"y":134},{"type":"mousemove","time":20645,"x":601,"y":136},{"type":"mousedown","time":20835,"x":594,"y":160},{"type":"mousemove","time":20875,"x":594,"y":160},{"type":"mouseup","time":20989,"x":594,"y":160},{"time":20990,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21014,"x":586,"y":171},{"type":"mousemove","time":21225,"x":578,"y":258},{"type":"mousedown","time":21367,"x":582,"y":256},{"type":"mousemove","time":21477,"x":582,"y":256},{"type":"mouseup","time":21488,"x":582,"y":256},{"time":21489,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21679,"x":582,"y":266},{"type":"mousemove","time":21902,"x":620,"y":509},{"type":"mousemove","time":22112,"x":607,"y":538},{"type":"mousemove","time":22312,"x":605,"y":523},{"type":"mousedown","time":22505,"x":603,"y":526},{"type":"mousemove","time":22515,"x":603,"y":526},{"type":"mouseup","time":22611,"x":603,"y":526},{"time":22612,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22762,"x":601,"y":531},{"type":"mousedown","time":22925,"x":588,"y":546},{"type":"mousemove","time":22975,"x":588,"y":546},{"type":"mouseup","time":23028,"x":588,"y":546},{"time":23029,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23179,"x":553,"y":546},{"type":"mousedown","time":23291,"x":538,"y":546},{"type":"mousemove","time":23392,"x":537,"y":546},{"type":"mouseup","time":23402,"x":537,"y":546},{"time":23403,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23613,"x":536,"y":544},{"type":"mousedown","time":23770,"x":517,"y":516},{"type":"mousemove","time":23833,"x":517,"y":516},{"type":"mouseup","time":23885,"x":517,"y":516},{"time":23886,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24040,"x":516,"y":513},{"type":"mousedown","time":24217,"x":481,"y":367},{"type":"mousemove","time":24257,"x":481,"y":366},{"type":"mouseup","time":24322,"x":481,"y":366},{"time":24323,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24490,"x":477,"y":366},{"type":"mousemove","time":24693,"x":132,"y":222},{"type":"mousemove","time":24894,"x":173,"y":189},{"type":"mousedown","time":24963,"x":174,"y":186},{"type":"mouseup","time":25045,"x":174,"y":185},{"time":25046,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25098,"x":182,"y":185},{"type":"mousemove","time":25316,"x":285,"y":138},{"type":"mousedown","time":25356,"x":285,"y":138},{"type":"mouseup","time":25464,"x":285,"y":138},{"time":25465,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25505,"x":285,"y":141},{"type":"mousedown","time":25674,"x":285,"y":193},{"type":"mousemove","time":25731,"x":285,"y":193},{"type":"mouseup","time":25798,"x":285,"y":193},{"time":25799,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25942,"x":180,"y":243},{"type":"mousemove","time":26151,"x":152,"y":267},{"type":"mousedown","time":26230,"x":152,"y":267},{"type":"mousemove","time":26358,"x":152,"y":267},{"type":"mouseup","time":26395,"x":152,"y":267},{"time":26396,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26662,"x":152,"y":267},{"type":"mousemove","time":26876,"x":180,"y":555},{"type":"mousemove","time":27077,"x":158,"y":561},{"type":"mousedown","time":27088,"x":158,"y":561},{"type":"mouseup","time":27178,"x":158,"y":561},{"time":27179,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27283,"x":109,"y":561},{"type":"mousedown","time":27426,"x":87,"y":561},{"type":"mousemove","time":27494,"x":86,"y":561},{"type":"mouseup","time":27533,"x":86,"y":561},{"time":27534,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27700,"x":72,"y":497},{"type":"mousedown","time":27752,"x":72,"y":496},{"type":"mouseup","time":27886,"x":72,"y":496},{"time":27887,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27938,"x":72,"y":496},{"type":"mousemove","time":28131,"x":72,"y":496},{"type":"mousemove","time":28338,"x":141,"y":252},{"type":"mousedown","time":28433,"x":137,"y":249},{"type":"mouseup","time":28531,"x":137,"y":249},{"time":28532,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28567,"x":137,"y":249},{"type":"mousedown","time":28757,"x":87,"y":249},{"type":"mousemove","time":28768,"x":87,"y":249},{"type":"mouseup","time":28879,"x":87,"y":249},{"time":28880,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29055,"x":88,"y":246},{"type":"mousemove","time":29273,"x":480,"y":66},{"type":"mousemove","time":29474,"x":485,"y":63},{"type":"mousemove","time":29688,"x":529,"y":23},{"type":"mousemove","time":29922,"x":517,"y":40},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"3","time":31105,"target":"select"},{"time":31106,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31146,"x":517,"y":73},{"type":"mousemove","time":31356,"x":517,"y":87},{"type":"mousemove","time":31562,"x":573,"y":99},{"type":"mousemove","time":31762,"x":544,"y":95},{"type":"mousedown","time":31783,"x":544,"y":95},{"type":"mouseup","time":31929,"x":544,"y":95},{"time":31930,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31982,"x":544,"y":95},{"type":"mousedown","time":32203,"x":578,"y":95},{"type":"mousemove","time":32229,"x":578,"y":95},{"type":"mouseup","time":32367,"x":578,"y":95},{"time":32368,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32420,"x":578,"y":95},{"type":"mousemove","time":32627,"x":578,"y":104},{"type":"mousemove","time":32837,"x":443,"y":118},{"type":"mousemove","time":33040,"x":298,"y":117},{"type":"mousedown","time":33166,"x":237,"y":119},{"type":"mousemove","time":33255,"x":237,"y":119},{"type":"mouseup","time":33378,"x":237,"y":119},{"time":33379,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":33645,"x":236,"y":119},{"type":"mousedown","time":33822,"x":218,"y":189},{"type":"mousemove","time":33869,"x":218,"y":189},{"type":"mouseup","time":33945,"x":218,"y":189},{"time":33946,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34078,"x":285,"y":158},{"type":"mousedown","time":34266,"x":308,"y":141},{"type":"mousemove","time":34278,"x":308,"y":140},{"type":"mouseup","time":34446,"x":308,"y":140},{"time":34447,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34546,"x":308,"y":141},{"type":"mousemove","time":34752,"x":309,"y":182},{"type":"mousedown","time":34799,"x":309,"y":183},{"type":"mouseup","time":34962,"x":309,"y":183},{"time":34963,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35001,"x":309,"y":183},{"type":"mousemove","time":35013,"x":306,"y":183},{"type":"mousedown","time":35193,"x":232,"y":241},{"type":"mousemove","time":35239,"x":232,"y":242},{"type":"mouseup","time":35312,"x":232,"y":242},{"time":35313,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35579,"x":231,"y":242},{"type":"mousedown","time":35772,"x":185,"y":226},{"type":"mousemove","time":35784,"x":185,"y":225},{"type":"mouseup","time":35911,"x":185,"y":225},{"time":35912,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36001,"x":184,"y":263},{"type":"mousedown","time":36179,"x":164,"y":374},{"type":"mousemove","time":36219,"x":164,"y":374},{"type":"mouseup","time":36283,"x":164,"y":374},{"time":36284,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36502,"x":164,"y":375},{"type":"mousemove","time":36718,"x":38,"y":419},{"type":"mousedown","time":36747,"x":38,"y":419},{"type":"mouseup","time":36880,"x":38,"y":419},{"time":36881,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36948,"x":38,"y":419},{"type":"mousemove","time":36962,"x":40,"y":423},{"type":"mousemove","time":37173,"x":55,"y":571},{"type":"mousedown","time":37201,"x":55,"y":571},{"type":"mouseup","time":37371,"x":55,"y":571},{"time":37372,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37426,"x":55,"y":571},{"type":"mousemove","time":37567,"x":55,"y":571},{"type":"mousedown","time":37714,"x":139,"y":540},{"type":"mousemove","time":37787,"x":139,"y":540},{"type":"mouseup","time":37865,"x":139,"y":540},{"time":37866,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37999,"x":177,"y":527},{"type":"mousedown","time":38138,"x":195,"y":449},{"type":"mousemove","time":38207,"x":195,"y":449},{"type":"mouseup","time":38262,"x":195,"y":449},{"time":38263,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38479,"x":195,"y":451},{"type":"mousemove","time":38690,"x":235,"y":525},{"type":"mousemove","time":38891,"x":236,"y":530},{"type":"mousedown","time":39079,"x":215,"y":373},{"type":"mousemove","time":39123,"x":215,"y":373},{"type":"mouseup","time":39228,"x":215,"y":373},{"time":39229,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39324,"x":215,"y":426},{"type":"mousemove","time":39543,"x":254,"y":576},{"type":"mousemove","time":39747,"x":260,"y":563},{"type":"mousedown","time":39874,"x":271,"y":559},{"type":"mousemove","time":39956,"x":272,"y":559},{"type":"mouseup","time":39967,"x":272,"y":559},{"time":39968,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40173,"x":358,"y":543},{"type":"mousemove","time":40375,"x":563,"y":537},{"type":"mousedown","time":40413,"x":563,"y":538},{"type":"mouseup","time":40562,"x":563,"y":538},{"time":40563,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40602,"x":565,"y":538},{"type":"mousedown","time":40801,"x":579,"y":538},{"type":"mousemove","time":40813,"x":579,"y":538},{"type":"mouseup","time":40947,"x":579,"y":538},{"time":40948,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41019,"x":579,"y":501},{"type":"mousedown","time":41114,"x":579,"y":475},{"type":"mousemove","time":41251,"x":579,"y":475},{"type":"mouseup","time":41264,"x":579,"y":475},{"time":41265,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41338,"x":579,"y":477},{"type":"mousemove","time":41538,"x":587,"y":563},{"type":"mousemove","time":41755,"x":636,"y":507},{"type":"mousemove","time":41966,"x":587,"y":310},{"type":"mousedown","time":42162,"x":532,"y":299},{"type":"mousemove","time":42173,"x":532,"y":299},{"type":"mouseup","time":42262,"x":532,"y":299},{"time":42263,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42480,"x":531,"y":299},{"type":"mousedown","time":42693,"x":478,"y":317},{"type":"mousemove","time":42704,"x":478,"y":317},{"type":"mouseup","time":42788,"x":478,"y":317},{"time":42789,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42931,"x":478,"y":316},{"type":"mousemove","time":43141,"x":594,"y":17},{"type":"mousemove","time":43351,"x":559,"y":36},{"type":"mousemove","time":43557,"x":548,"y":44},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"4","time":44724,"target":"select"},{"time":44725,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":44787,"x":487,"y":206},{"type":"mousemove","time":44989,"x":468,"y":335},{"type":"mousedown","time":45002,"x":468,"y":335},{"type":"mouseup","time":45128,"x":468,"y":335},{"time":45129,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45226,"x":468,"y":335},{"type":"mousemove","time":45348,"x":459,"y":343},{"type":"mousedown","time":45521,"x":346,"y":450},{"type":"mousemove","time":45566,"x":346,"y":450},{"type":"mouseup","time":45634,"x":346,"y":450},{"time":45635,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45850,"x":346,"y":449},{"type":"mousedown","time":46055,"x":322,"y":273},{"type":"mousemove","time":46067,"x":322,"y":273},{"type":"mouseup","time":46162,"x":322,"y":273},{"time":46163,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46317,"x":325,"y":270},{"type":"mousedown","time":46517,"x":354,"y":156},{"type":"mousemove","time":46529,"x":354,"y":156},{"type":"mouseup","time":46669,"x":354,"y":156},{"time":46670,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46731,"x":349,"y":156},{"type":"mousemove","time":46937,"x":300,"y":156},{"type":"mousedown","time":46969,"x":299,"y":156},{"type":"mouseup","time":47097,"x":299,"y":156},{"time":47098,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47167,"x":299,"y":156},{"type":"mousemove","time":47241,"x":299,"y":156},{"type":"mousedown","time":47398,"x":281,"y":174},{"type":"mousemove","time":47458,"x":281,"y":174},{"type":"mouseup","time":47517,"x":281,"y":174},{"time":47518,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47672,"x":281,"y":113},{"type":"mousedown","time":47756,"x":281,"y":109},{"type":"mouseup","time":47871,"x":281,"y":109},{"time":47872,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47926,"x":281,"y":109},{"type":"mousemove","time":48014,"x":280,"y":109},{"type":"mousedown","time":48176,"x":185,"y":110},{"type":"mousemove","time":48233,"x":185,"y":110},{"type":"mouseup","time":48290,"x":185,"y":110},{"time":48291,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48398,"x":182,"y":110},{"type":"mousemove","time":48601,"x":96,"y":110},{"type":"mousedown","time":48628,"x":96,"y":110},{"type":"mouseup","time":48764,"x":95,"y":110},{"time":48765,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48802,"x":95,"y":110},{"type":"mousemove","time":49015,"x":164,"y":163},{"type":"mousedown","time":49118,"x":198,"y":149},{"type":"mouseup","time":49212,"x":198,"y":149},{"time":49213,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":49262,"x":198,"y":149},{"type":"mousemove","time":49436,"x":197,"y":149},{"type":"mousemove","time":49639,"x":157,"y":169},{"type":"mousedown","time":49780,"x":192,"y":176},{"type":"mousemove","time":49845,"x":192,"y":176},{"type":"mouseup","time":49968,"x":192,"y":176},{"time":49969,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50021,"x":194,"y":176},{"type":"mousedown","time":50192,"x":219,"y":176},{"type":"mousemove","time":50250,"x":219,"y":176},{"type":"mouseup","time":50353,"x":219,"y":176},{"time":50354,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50507,"x":219,"y":176},{"type":"mousemove","time":50714,"x":150,"y":140},{"type":"mousedown","time":50762,"x":149,"y":140},{"type":"mouseup","time":50912,"x":149,"y":140},{"time":50913,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50935,"x":149,"y":140},{"type":"mousedown","time":51124,"x":72,"y":258},{"type":"mousemove","time":51138,"x":72,"y":258},{"type":"mouseup","time":51262,"x":72,"y":258},{"time":51263,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51410,"x":72,"y":258},{"type":"mousemove","time":51627,"x":181,"y":328},{"type":"mousedown","time":51656,"x":181,"y":328},{"type":"mouseup","time":51798,"x":181,"y":328},{"time":51799,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51854,"x":181,"y":328},{"type":"mousemove","time":52068,"x":276,"y":307},{"type":"mousedown","time":52137,"x":284,"y":307},{"type":"mouseup","time":52252,"x":284,"y":307},{"time":52253,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":52317,"x":284,"y":307},{"type":"mousemove","time":52382,"x":284,"y":307},{"type":"mousemove","time":52582,"x":244,"y":523},{"type":"mousemove","time":52788,"x":212,"y":551},{"type":"mousedown","time":52853,"x":208,"y":556},{"type":"mousemove","time":52991,"x":207,"y":557},{"type":"mouseup","time":53003,"x":207,"y":557},{"time":53004,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53200,"x":56,"y":550},{"type":"mousedown","time":53293,"x":46,"y":550},{"type":"mouseup","time":53402,"x":46,"y":550},{"time":53403,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53428,"x":46,"y":550},{"type":"mousemove","time":53630,"x":368,"y":545},{"type":"mousedown","time":53753,"x":400,"y":539},{"type":"mousemove","time":53875,"x":400,"y":539},{"type":"mouseup","time":53905,"x":400,"y":539},{"time":53906,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":54160,"x":400,"y":539},{"type":"mousemove","time":54176,"x":454,"y":408},{"type":"mousemove","time":54390,"x":543,"y":134},{"type":"mousemove","time":54593,"x":552,"y":45},{"type":"mousemove","time":54793,"x":552,"y":43},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":57076,"target":"select"},{"time":57077,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":57180,"x":551,"y":33},{"type":"mousemove","time":57390,"x":643,"y":295}],"scrollY":118,"scrollX":0,"timestamp":1748966697954},{"name":"Action 3","ops":[{"type":"mousemove","time":72,"x":683,"y":236},{"type":"mousemove","time":272,"x":753,"y":64},{"type":"mousemove","time":472,"x":707,"y":47},{"type":"mousemove","time":672,"x":699,"y":55},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":2146,"target":"select"},{"time":2147,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2173,"x":699,"y":73},{"type":"mousemove","time":2387,"x":699,"y":72},{"type":"mousemove","time":2622,"x":701,"y":57},{"type":"mousemove","time":2839,"x":591,"y":127},{"type":"mousemove","time":3039,"x":497,"y":159},{"type":"mousemove","time":3249,"x":496,"y":146},{"type":"mousemove","time":3456,"x":499,"y":171},{"type":"mousemove","time":3682,"x":499,"y":177},{"type":"mousemove","time":4039,"x":499,"y":177},{"type":"mousemove","time":4249,"x":505,"y":151},{"type":"mousemove","time":4456,"x":535,"y":107},{"type":"mousemove","time":4665,"x":705,"y":33},{"type":"mousemove","time":4872,"x":706,"y":44},{"type":"mousemove","time":5083,"x":704,"y":49},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"2","time":6029,"target":"select"},{"time":6030,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6042,"x":704,"y":65},{"type":"mousemove","time":6288,"x":700,"y":66},{"type":"mousemove","time":6507,"x":511,"y":153},{"type":"mousemove","time":6718,"x":494,"y":155},{"type":"mousemove","time":6933,"x":494,"y":145},{"type":"mousemove","time":7138,"x":498,"y":173},{"type":"mousemove","time":7349,"x":498,"y":174},{"type":"mousemove","time":7556,"x":533,"y":149},{"type":"mousemove","time":7756,"x":691,"y":47},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":9099,"target":"select"},{"time":9100,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9129,"x":696,"y":10},{"type":"mousemove","time":9500,"x":695,"y":10},{"type":"mousemove","time":9700,"x":239,"y":105},{"type":"mousemove","time":9901,"x":236,"y":105},{"type":"mousemove","time":9973,"x":233,"y":104},{"type":"mousemove","time":10184,"x":170,"y":74},{"type":"mousemove","time":10399,"x":154,"y":71},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"1","time":11465,"target":"select"},{"time":11466,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11495,"x":151,"y":96},{"type":"mousemove","time":11713,"x":146,"y":65},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"2","time":12630,"target":"select"},{"time":12631,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12663,"x":252,"y":72},{"type":"mousemove","time":12877,"x":376,"y":57},{"type":"mousemove","time":13094,"x":411,"y":60},{"type":"mousemove","time":13298,"x":411,"y":60},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":14531,"target":"select"},{"time":14532,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14545,"x":419,"y":90},{"type":"mousemove","time":14746,"x":440,"y":75},{"type":"mousemove","time":14976,"x":449,"y":64},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":16216,"target":"select"},{"time":16217,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16249,"x":443,"y":76},{"type":"mousemove","time":16474,"x":442,"y":67},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":17414,"target":"select"},{"time":17415,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17445,"x":442,"y":72},{"type":"mousemove","time":17664,"x":443,"y":72},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":18713,"target":"select"},{"time":18714,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18743,"x":440,"y":69},{"type":"mousedown","time":18929,"x":448,"y":54},{"type":"mousemove","time":18973,"x":448,"y":54},{"type":"mouseup","time":19013,"x":448,"y":54},{"time":19014,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19118,"x":448,"y":58},{"type":"mousemove","time":19318,"x":448,"y":101},{"type":"mousemove","time":19523,"x":444,"y":74},{"type":"mousemove","time":19733,"x":444,"y":71},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":20916,"target":"select"},{"time":20917,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20950,"x":443,"y":58},{"type":"mousemove","time":21180,"x":443,"y":58},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":22198,"target":"select"},{"time":22199,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22229,"x":449,"y":67},{"type":"mousemove","time":22438,"x":449,"y":67},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":23350,"target":"select"},{"time":23351,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23382,"x":445,"y":76},{"type":"mousemove","time":23604,"x":444,"y":70},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":24615,"target":"select"},{"time":24616,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24647,"x":445,"y":70},{"type":"mousemove","time":24864,"x":445,"y":68},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":25920,"target":"select"},{"time":25921,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25953,"x":447,"y":67},{"type":"mousemove","time":26181,"x":447,"y":66},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":27080,"target":"select"},{"time":27081,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27095,"x":447,"y":95},{"type":"mousemove","time":27297,"x":470,"y":61},{"type":"mousemove","time":27512,"x":511,"y":45},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"3","time":29013,"target":"select"},{"time":29014,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29027,"x":478,"y":87},{"type":"mousemove","time":29238,"x":443,"y":56},{"type":"mousemove","time":29453,"x":443,"y":57},{"type":"mousemove","time":29654,"x":443,"y":64},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":31279,"target":"select"},{"time":31280,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31295,"x":439,"y":57},{"type":"mousemove","time":31514,"x":436,"y":70},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":32798,"target":"select"},{"time":32799,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32828,"x":429,"y":52},{"type":"mousemove","time":33030,"x":425,"y":65},{"type":"mousemove","time":33255,"x":425,"y":69},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":34114,"target":"select"},{"time":34115,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34128,"x":429,"y":70},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":35462,"target":"select"},{"time":35463,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35493,"x":428,"y":62},{"type":"mousemove","time":35710,"x":428,"y":68},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":37029,"target":"select"},{"time":37030,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37060,"x":431,"y":51},{"type":"mousedown","time":37192,"x":432,"y":51},{"type":"mousemove","time":37272,"x":432,"y":51},{"type":"mouseup","time":37315,"x":432,"y":51},{"time":37316,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37473,"x":432,"y":53},{"type":"mousemove","time":37684,"x":432,"y":64},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":38614,"target":"select"},{"time":38615,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38647,"x":437,"y":63},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":39814,"target":"select"},{"time":39815,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39846,"x":435,"y":60},{"type":"mousemove","time":40061,"x":435,"y":68},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":40964,"target":"select"},{"time":40965,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40996,"x":435,"y":66},{"type":"mousemove","time":41222,"x":435,"y":71},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":42216,"target":"select"},{"time":42217,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42250,"x":435,"y":58},{"type":"mousemove","time":42456,"x":434,"y":73},{"type":"mousemove","time":42642,"x":437,"y":72},{"type":"mousemove","time":42849,"x":526,"y":44},{"type":"mousemove","time":43081,"x":526,"y":44},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"2","time":45543,"target":"select"},{"time":45544,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45560,"x":475,"y":38},{"type":"mousemove","time":45761,"x":428,"y":67},{"type":"mousemove","time":45977,"x":428,"y":68},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":47163,"target":"select"},{"time":47164,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47178,"x":423,"y":98},{"type":"mousemove","time":47383,"x":436,"y":64},{"type":"mousemove","time":47588,"x":436,"y":64},{"type":"mousedown","time":48499,"x":436,"y":82},{"type":"mousemove","time":48511,"x":436,"y":82},{"type":"mouseup","time":48705,"x":436,"y":82},{"time":48706,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48781,"x":436,"y":81},{"type":"mousemove","time":49004,"x":439,"y":71},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":49816,"target":"select"},{"time":49817,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":49846,"x":450,"y":71},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":50880,"target":"select"},{"time":50881,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50910,"x":450,"y":72},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":51915,"target":"select"},{"time":51916,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51948,"x":451,"y":72},{"type":"mousemove","time":52177,"x":451,"y":69},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":53060,"target":"select"},{"time":53061,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53075,"x":451,"y":76},{"type":"mousemove","time":53307,"x":451,"y":74},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":54114,"target":"select"},{"time":54115,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":54145,"x":454,"y":62},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":55135,"target":"select"},{"time":55136,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":55167,"x":451,"y":69},{"type":"mousemove","time":55369,"x":451,"y":68},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":56130,"target":"select"},{"time":56131,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":56162,"x":451,"y":68},{"type":"mousemove","time":56390,"x":451,"y":66},{"type":"valuechange","selector":"#main_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":57232,"target":"select"},{"time":57233,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":57264,"x":552,"y":116},{"type":"mousedown","time":57377,"x":630,"y":147},{"type":"mousemove","time":57472,"x":630,"y":147},{"type":"mouseup","time":57484,"x":630,"y":147},{"time":57485,"delay":400,"type":"screenshot-auto"}],"scrollY":118,"scrollX":0,"timestamp":1748966763745},{"name":"Action 4","ops":[{"type":"mousemove","time":107,"x":402,"y":476},{"type":"mousemove","time":307,"x":724,"y":490},{"type":"mousemove","time":524,"x":726,"y":490},{"type":"mousemove","time":725,"x":774,"y":511},{"type":"mousemove","time":936,"x":783,"y":526},{"type":"mousemove","time":1152,"x":783,"y":529},{"type":"mousedown","time":1388,"x":783,"y":529},{"type":"mousemove","time":1401,"x":783,"y":529},{"type":"mousemove","time":1604,"x":699,"y":511},{"type":"mousemove","time":1817,"x":698,"y":510},{"type":"mousemove","time":1924,"x":697,"y":510},{"type":"mousemove","time":2124,"x":582,"y":480},{"type":"mousemove","time":2337,"x":582,"y":480},{"type":"mousemove","time":2375,"x":582,"y":480},{"type":"mousemove","time":2586,"x":539,"y":476},{"type":"mousemove","time":2803,"x":501,"y":477},{"type":"mousemove","time":2958,"x":498,"y":476},{"type":"mousemove","time":3158,"x":504,"y":427},{"type":"mousemove","time":3358,"x":503,"y":384},{"type":"mousemove","time":3625,"x":503,"y":383},{"type":"mousemove","time":3825,"x":506,"y":380},{"type":"mousemove","time":4036,"x":511,"y":375},{"type":"mousemove","time":4241,"x":522,"y":360},{"type":"mousemove","time":4452,"x":525,"y":353},{"type":"mousemove","time":4658,"x":529,"y":338},{"type":"mousemove","time":4859,"x":534,"y":316},{"type":"mousemove","time":5071,"x":535,"y":306},{"type":"mousemove","time":5275,"x":546,"y":334},{"type":"mousemove","time":5478,"x":544,"y":351},{"type":"mousemove","time":5694,"x":528,"y":386},{"type":"mousemove","time":5907,"x":500,"y":408},{"type":"mousemove","time":6090,"x":499,"y":408},{"type":"mousemove","time":6291,"x":275,"y":417},{"type":"mousemove","time":6504,"x":274,"y":418},{"type":"mousemove","time":6561,"x":273,"y":419},{"type":"mousemove","time":6769,"x":241,"y":423},{"type":"mousemove","time":6975,"x":227,"y":424},{"type":"mousemove","time":7191,"x":215,"y":424},{"type":"mousemove","time":7397,"x":186,"y":425},{"type":"mousemove","time":7607,"x":155,"y":425},{"type":"mousemove","time":7810,"x":174,"y":438},{"type":"mousemove","time":8022,"x":190,"y":443},{"type":"mousemove","time":8228,"x":212,"y":445},{"type":"mousemove","time":8440,"x":273,"y":446},{"type":"mousemove","time":8655,"x":277,"y":445},{"type":"mousemove","time":8857,"x":478,"y":464},{"type":"mousemove","time":9069,"x":496,"y":465},{"type":"mousemove","time":9274,"x":696,"y":498},{"type":"mousemove","time":9476,"x":727,"y":500},{"type":"mousemove","time":9680,"x":744,"y":510},{"type":"mousemove","time":9891,"x":751,"y":516},{"type":"mousemove","time":10104,"x":753,"y":516},{"type":"mouseup","time":10142,"x":753,"y":516},{"time":10143,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10156,"x":601,"y":430},{"type":"mousemove","time":10356,"x":145,"y":175},{"type":"mousemove","time":10525,"x":145,"y":175},{"type":"mousemove","time":10725,"x":135,"y":179},{"type":"mousemove","time":10938,"x":134,"y":179},{"type":"mousemove","time":11141,"x":179,"y":167},{"type":"mousemove","time":11355,"x":227,"y":167},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":12328,"target":"select"},{"time":12329,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12375,"x":233,"y":176},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":13409,"target":"select"},{"time":13410,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13468,"x":236,"y":181},{"type":"mousemove","time":13670,"x":236,"y":179},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":14461,"target":"select"},{"time":14462,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14516,"x":242,"y":169},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":15676,"target":"select"},{"time":15677,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15716,"x":235,"y":177},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":16708,"target":"select"},{"time":16709,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16767,"x":247,"y":175},{"type":"mousemove","time":16970,"x":248,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":17792,"target":"select"},{"time":17793,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17846,"x":253,"y":175},{"type":"mousemove","time":18054,"x":254,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":18874,"target":"select"},{"time":18875,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18924,"x":257,"y":179},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":19944,"target":"select"},{"time":19945,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":19985,"x":258,"y":176},{"type":"mousemove","time":20185,"x":260,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":21025,"target":"select"},{"time":21026,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21062,"x":267,"y":176},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":22043,"target":"select"},{"time":22044,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22081,"x":272,"y":170},{"type":"mousemove","time":22286,"x":272,"y":170},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"11","time":23157,"target":"select"},{"time":23158,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23184,"x":136,"y":172},{"type":"mousemove","time":23387,"x":125,"y":163},{"type":"mousemove","time":23604,"x":125,"y":160},{"type":"mousemove","time":23821,"x":125,"y":158},{"type":"mousemove","time":24036,"x":124,"y":158},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":25766,"target":"select"},{"time":25767,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25797,"x":226,"y":157},{"type":"mousemove","time":26011,"x":286,"y":151},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"1","time":26881,"target":"select"},{"time":26882,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26910,"x":249,"y":176},{"type":"mousemove","time":27121,"x":249,"y":176},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":28010,"target":"select"},{"time":28011,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28057,"x":244,"y":171},{"type":"mousemove","time":28258,"x":244,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":29309,"target":"select"},{"time":29310,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29350,"x":245,"y":178},{"type":"mousedown","time":29456,"x":243,"y":184},{"type":"mousemove","time":29553,"x":243,"y":184},{"type":"mouseup","time":29564,"x":243,"y":184},{"time":29565,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29692,"x":243,"y":183},{"type":"mousemove","time":29903,"x":262,"y":159},{"type":"mousemove","time":30153,"x":255,"y":176},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":31043,"target":"select"},{"time":31044,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31097,"x":236,"y":171},{"type":"mousemove","time":31302,"x":236,"y":171},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":32151,"target":"select"},{"time":32152,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32193,"x":245,"y":177},{"type":"mousemove","time":32402,"x":245,"y":178},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":33143,"target":"select"},{"time":33144,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":33172,"x":252,"y":180},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":34298,"target":"select"},{"time":34299,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34359,"x":254,"y":167},{"type":"mousemove","time":34571,"x":249,"y":175},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":35962,"target":"select"},{"time":35963,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35989,"x":258,"y":152},{"type":"mousemove","time":36189,"x":258,"y":168},{"type":"mousemove","time":36436,"x":258,"y":168},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":37227,"target":"select"},{"time":37228,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37268,"x":264,"y":177},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":38359,"target":"select"},{"time":38360,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38386,"x":269,"y":169},{"type":"mousemove","time":38587,"x":270,"y":169},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":39525,"target":"select"},{"time":39526,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39550,"x":277,"y":147},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"0","time":40849,"target":"select"},{"time":40850,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40865,"x":178,"y":131},{"type":"mousemove","time":41095,"x":126,"y":144},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":42156,"target":"select"},{"time":42157,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42173,"x":254,"y":131},{"type":"mousemove","time":42382,"x":507,"y":146},{"type":"mousemove","time":42608,"x":514,"y":149},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"2","time":44627,"target":"select"},{"time":44628,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":44655,"x":352,"y":199},{"type":"mousemove","time":44855,"x":280,"y":180},{"type":"mousemove","time":45069,"x":280,"y":179},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":45862,"target":"select"},{"time":45863,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45906,"x":311,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":46911,"target":"select"},{"time":46912,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46952,"x":310,"y":172},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":47926,"target":"select"},{"time":47927,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47967,"x":311,"y":171},{"type":"mousemove","time":48171,"x":312,"y":167},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":48995,"target":"select"},{"time":48996,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":49054,"x":312,"y":169},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":49997,"target":"select"},{"time":49998,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50042,"x":321,"y":178},{"type":"mousemove","time":50254,"x":322,"y":175},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":51043,"target":"select"},{"time":51044,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51083,"x":318,"y":167},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":52628,"target":"select"},{"time":52629,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":52655,"x":301,"y":198},{"type":"mousemove","time":52856,"x":304,"y":178},{"type":"mousemove","time":53071,"x":304,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":53828,"target":"select"},{"time":53829,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":53883,"x":318,"y":162},{"type":"mousemove","time":53897,"x":318,"y":162},{"type":"mouseup","time":53992,"x":318,"y":162},{"time":53993,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":54159,"x":318,"y":163},{"type":"mousemove","time":54369,"x":315,"y":168},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":55244,"target":"select"},{"time":55245,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":55283,"x":321,"y":169},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"11","time":56227,"target":"select"},{"time":56228,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":56257,"x":462,"y":168},{"type":"mousemove","time":56459,"x":510,"y":145},{"type":"mousemove","time":56672,"x":509,"y":145},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"3","time":57530,"target":"select"},{"time":57531,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":57546,"x":469,"y":172},{"type":"mousemove","time":57755,"x":311,"y":178},{"type":"mousemove","time":57972,"x":311,"y":178},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":58913,"target":"select"},{"time":58914,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":58942,"x":295,"y":172},{"type":"mousemove","time":59157,"x":283,"y":193},{"type":"mousemove","time":59371,"x":282,"y":177},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":60444,"target":"select"},{"time":60445,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":60487,"x":285,"y":174},{"type":"mousemove","time":60688,"x":296,"y":181},{"type":"mousemove","time":60905,"x":296,"y":178},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":61912,"target":"select"},{"time":61913,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":61955,"x":282,"y":168},{"type":"mousemove","time":62156,"x":278,"y":168},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":62994,"target":"select"},{"time":62995,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":63036,"x":276,"y":168},{"type":"mousemove","time":63238,"x":273,"y":175},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":64662,"target":"select"},{"time":64663,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":64690,"x":272,"y":157},{"type":"mousemove","time":64895,"x":266,"y":172},{"type":"mousemove","time":65106,"x":266,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":65936,"target":"select"},{"time":65937,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":65973,"x":273,"y":164},{"type":"mousemove","time":66173,"x":267,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":67061,"target":"select"},{"time":67062,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":67090,"x":272,"y":171},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":68112,"target":"select"},{"time":68113,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":68141,"x":280,"y":167},{"type":"mousemove","time":68355,"x":280,"y":167},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":69297,"target":"select"},{"time":69298,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":69360,"x":281,"y":159},{"type":"mousemove","time":69572,"x":274,"y":171},{"type":"mousemove","time":69791,"x":274,"y":171},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":70513,"target":"select"},{"time":70514,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":70543,"x":374,"y":156},{"type":"mousemove","time":70746,"x":491,"y":152},{"type":"mousemove","time":70956,"x":492,"y":152},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"4","time":71747,"target":"select"},{"time":71748,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":71763,"x":315,"y":179},{"type":"mousemove","time":71995,"x":277,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":72922,"target":"select"},{"time":72923,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":72971,"x":311,"y":177},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":73948,"target":"select"},{"time":73949,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":73992,"x":314,"y":163},{"type":"mousemove","time":74193,"x":314,"y":163},{"type":"mousemove","time":74393,"x":306,"y":178},{"type":"mousemove","time":74607,"x":306,"y":178},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":75396,"target":"select"},{"time":75397,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":75427,"x":323,"y":172},{"type":"mousemove","time":75638,"x":323,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":76379,"target":"select"},{"time":76380,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":76426,"x":323,"y":169},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":77447,"target":"select"},{"time":77448,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":77493,"x":322,"y":170},{"type":"mousemove","time":77693,"x":322,"y":179},{"type":"mousemove","time":77894,"x":321,"y":200},{"type":"mousemove","time":78093,"x":321,"y":200},{"type":"mousemove","time":78293,"x":322,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":79231,"target":"select"},{"time":79232,"delay":400,"type":"screenshot-auto"},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":80379,"target":"select"},{"time":80380,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":80421,"x":316,"y":170},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":81447,"target":"select"},{"time":81448,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":81498,"x":319,"y":165},{"type":"mousemove","time":81709,"x":318,"y":165},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":82679,"target":"select"},{"time":82680,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":82727,"x":314,"y":178},{"type":"mousemove","time":82939,"x":314,"y":178},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"11","time":83781,"target":"select"},{"time":83782,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":83796,"x":382,"y":184},{"type":"mousemove","time":84009,"x":471,"y":181},{"type":"mousemove","time":84210,"x":480,"y":173},{"type":"mousemove","time":84410,"x":498,"y":158},{"type":"mousemove","time":84621,"x":511,"y":145},{"type":"mousemove","time":84859,"x":512,"y":145},{"type":"mousemove","time":85074,"x":514,"y":146},{"type":"mousemove","time":85194,"x":514,"y":146},{"type":"mousemove","time":85406,"x":514,"y":146},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":86595,"target":"select"},{"time":86596,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":86625,"x":726,"y":137},{"type":"mousemove","time":86826,"x":738,"y":143},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"2","time":88529,"target":"select"},{"time":88530,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":88548,"x":572,"y":198},{"type":"mousemove","time":88757,"x":241,"y":198},{"type":"mousemove","time":88859,"x":241,"y":197},{"type":"mousemove","time":89065,"x":267,"y":175},{"type":"mousemove","time":89274,"x":269,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"10","time":90339,"target":"select"},{"time":90340,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":90374,"x":271,"y":169},{"type":"mousemove","time":90575,"x":271,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"9","time":91447,"target":"select"},{"time":91448,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":91478,"x":264,"y":175},{"type":"mousemove","time":91690,"x":264,"y":175},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"8","time":92579,"target":"select"},{"time":92580,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":92611,"x":596,"y":154},{"type":"mousemove","time":92860,"x":797,"y":153},{"type":"mousemove","time":93074,"x":737,"y":143},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":94047,"target":"select"},{"time":94048,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":94078,"x":721,"y":145},{"type":"mousemove","time":94291,"x":720,"y":147},{"type":"mousemove","time":94506,"x":719,"y":150},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"2","time":95420,"target":"select"},{"time":95421,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":95440,"x":458,"y":160},{"type":"mousemove","time":95641,"x":309,"y":181},{"type":"mousemove","time":95860,"x":309,"y":172},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"7","time":96847,"target":"select"},{"time":96848,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":96893,"x":311,"y":166},{"type":"mousemove","time":97107,"x":309,"y":172},{"type":"mousemove","time":97325,"x":308,"y":173},{"type":"mousemove","time":98811,"x":622,"y":203},{"type":"mousemove","time":99011,"x":730,"y":141},{"type":"mousedown","time":99098,"x":731,"y":141},{"type":"mouseup","time":99198,"x":731,"y":141},{"time":99199,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":99257,"x":731,"y":141},{"type":"mousemove","time":99272,"x":731,"y":141},{"type":"mousemove","time":99476,"x":721,"y":154},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":100615,"target":"select"},{"time":100616,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":100677,"x":715,"y":151},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"2","time":101850,"target":"select"},{"time":101851,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":101883,"x":635,"y":167},{"type":"mousemove","time":102095,"x":312,"y":172},{"type":"mousemove","time":102307,"x":304,"y":172},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"6","time":103195,"target":"select"},{"time":103196,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":103242,"x":293,"y":169},{"type":"mousemove","time":103443,"x":292,"y":172},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"5","time":104589,"target":"select"},{"time":104590,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":104630,"x":294,"y":170},{"type":"mousemove","time":104839,"x":294,"y":173},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"4","time":105729,"target":"select"},{"time":105730,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":105771,"x":294,"y":176},{"type":"mousemove","time":105972,"x":293,"y":176},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"3","time":106817,"target":"select"},{"time":106818,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":106865,"x":296,"y":167},{"type":"mousemove","time":107074,"x":296,"y":168},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"2","time":107933,"target":"select"},{"time":107934,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":107967,"x":290,"y":162},{"type":"mousemove","time":108176,"x":284,"y":172},{"type":"mousemove","time":108391,"x":283,"y":174},{"type":"valuechange","selector":"#main_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":109214,"target":"select"},{"time":109215,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":109231,"x":293,"y":155},{"type":"mousemove","time":109440,"x":295,"y":156},{"type":"mousemove","time":110128,"x":295,"y":156},{"type":"mousemove","time":110342,"x":297,"y":156},{"type":"mousemove","time":110544,"x":351,"y":156},{"type":"mousemove","time":110754,"x":405,"y":166},{"type":"mousemove","time":110956,"x":408,"y":167},{"type":"mousemove","time":111144,"x":408,"y":167},{"type":"mousemove","time":111344,"x":408,"y":167},{"type":"mousemove","time":111557,"x":408,"y":166},{"type":"mousemove","time":111944,"x":408,"y":167},{"type":"mousedown","time":112066,"x":408,"y":167},{"type":"mouseup","time":112191,"x":408,"y":167},{"time":112192,"delay":400,"type":"screenshot-auto"}],"scrollY":671,"scrollX":0,"timestamp":1748968199670},{"name":"Action 5","ops":[{"type":"mousemove","time":704,"x":738,"y":219},{"type":"mousemove","time":917,"x":517,"y":196},{"type":"mousemove","time":1133,"x":407,"y":188},{"type":"mousemove","time":1236,"x":407,"y":188},{"type":"mousemove","time":1436,"x":263,"y":174},{"type":"mousemove","time":1647,"x":224,"y":162},{"type":"mousemove","time":1851,"x":216,"y":164},{"type":"mousemove","time":2320,"x":216,"y":164},{"type":"mousedown","time":2416,"x":216,"y":163},{"type":"mouseup","time":2535,"x":216,"y":163},{"time":2536,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2575,"x":216,"y":163},{"type":"mousemove","time":2670,"x":217,"y":163},{"type":"mousemove","time":2870,"x":333,"y":162},{"type":"mousedown","time":2985,"x":343,"y":160},{"type":"mousemove","time":3082,"x":343,"y":160},{"type":"mouseup","time":3108,"x":343,"y":160},{"time":3109,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3203,"x":346,"y":160},{"type":"mousemove","time":3404,"x":472,"y":167},{"type":"mousemove","time":3620,"x":518,"y":159},{"type":"mousedown","time":3792,"x":522,"y":157},{"type":"mousemove","time":3866,"x":522,"y":157},{"type":"mouseup","time":3896,"x":522,"y":157},{"time":3897,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3986,"x":519,"y":157},{"type":"mousemove","time":4199,"x":306,"y":187},{"type":"mousemove","time":4402,"x":195,"y":193},{"type":"mousemove","time":4602,"x":189,"y":185},{"type":"mousedown","time":4694,"x":186,"y":182},{"type":"mouseup","time":4766,"x":186,"y":182},{"time":4767,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4825,"x":186,"y":182},{"type":"mousemove","time":5069,"x":186,"y":182},{"type":"mousemove","time":5270,"x":136,"y":249},{"type":"mousedown","time":5404,"x":129,"y":260},{"type":"mousemove","time":5493,"x":129,"y":260},{"type":"mouseup","time":5536,"x":129,"y":260},{"time":5537,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6004,"x":130,"y":260},{"type":"mousemove","time":6206,"x":209,"y":264},{"type":"mousedown","time":6286,"x":211,"y":264},{"type":"mouseup","time":6400,"x":211,"y":264},{"time":6401,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6436,"x":211,"y":264},{"type":"mousemove","time":6652,"x":212,"y":264},{"type":"mousemove","time":6856,"x":360,"y":248},{"type":"mousedown","time":6919,"x":363,"y":248},{"type":"mouseup","time":7017,"x":363,"y":248},{"time":7018,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7065,"x":363,"y":248},{"type":"mousemove","time":7203,"x":365,"y":248},{"type":"mousemove","time":7411,"x":579,"y":256},{"type":"mousedown","time":7478,"x":583,"y":257},{"type":"mouseup","time":7559,"x":583,"y":257},{"time":7560,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7617,"x":608,"y":257},{"type":"mousemove","time":7820,"x":732,"y":250},{"type":"mousedown","time":7989,"x":755,"y":256},{"type":"mousemove","time":8036,"x":755,"y":256},{"type":"mouseup","time":8094,"x":755,"y":256},{"time":8095,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8254,"x":755,"y":256},{"type":"mousedown","time":8459,"x":763,"y":319},{"type":"mousemove","time":8488,"x":763,"y":319},{"type":"mouseup","time":8566,"x":763,"y":319},{"time":8567,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8637,"x":763,"y":319},{"type":"mousemove","time":8849,"x":754,"y":477},{"type":"mousemove","time":9051,"x":753,"y":489},{"type":"mousedown","time":9124,"x":753,"y":489},{"type":"mouseup","time":9235,"x":753,"y":489},{"time":9236,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9387,"x":752,"y":494},{"type":"mousedown","time":9558,"x":744,"y":531},{"type":"mousemove","time":9604,"x":744,"y":531},{"type":"mouseup","time":9651,"x":744,"y":531},{"time":9652,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9806,"x":704,"y":531},{"type":"mousedown","time":9907,"x":684,"y":532},{"type":"mouseup","time":10018,"x":683,"y":532},{"time":10019,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10038,"x":683,"y":532},{"type":"mousemove","time":10245,"x":324,"y":531},{"type":"mousemove","time":10445,"x":251,"y":530},{"type":"mousedown","time":10588,"x":243,"y":529},{"type":"mouseup","time":10652,"x":243,"y":529},{"time":10653,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10692,"x":243,"y":529},{"type":"mousemove","time":10869,"x":243,"y":529},{"type":"mousedown","time":11049,"x":184,"y":545},{"type":"mousemove","time":11099,"x":184,"y":545},{"type":"mouseup","time":11145,"x":184,"y":545},{"time":11146,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11319,"x":177,"y":545},{"type":"mousedown","time":11534,"x":94,"y":543},{"type":"mousemove","time":11565,"x":94,"y":543},{"type":"mouseup","time":11677,"x":94,"y":543},{"time":11678,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11745,"x":94,"y":536},{"type":"mousedown","time":11890,"x":94,"y":499},{"type":"mousemove","time":11954,"x":94,"y":499},{"type":"mouseup","time":12102,"x":94,"y":499},{"time":12103,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12282,"x":94,"y":497},{"type":"mousedown","time":12432,"x":98,"y":429},{"type":"mousemove","time":12499,"x":98,"y":429},{"type":"mouseup","time":12547,"x":98,"y":429},{"time":12548,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12836,"x":98,"y":428},{"type":"mousedown","time":13064,"x":119,"y":355},{"type":"mousemove","time":13078,"x":119,"y":355},{"type":"mouseup","time":13152,"x":119,"y":355},{"time":13153,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13278,"x":228,"y":353},{"type":"mousedown","time":13345,"x":241,"y":353},{"type":"mouseup","time":13447,"x":241,"y":353},{"time":13448,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13487,"x":244,"y":353},{"type":"mousemove","time":13717,"x":381,"y":326},{"type":"mousedown","time":13749,"x":381,"y":326},{"type":"mouseup","time":13862,"x":381,"y":326},{"time":13863,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13937,"x":381,"y":326},{"type":"mousemove","time":14143,"x":251,"y":407},{"type":"mousedown","time":14354,"x":202,"y":439},{"type":"mousemove","time":14371,"x":201,"y":439},{"type":"mouseup","time":14484,"x":201,"y":439},{"time":14485,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14580,"x":218,"y":438},{"type":"mousedown","time":14749,"x":294,"y":426},{"type":"mousemove","time":14803,"x":294,"y":426},{"type":"mouseup","time":14818,"x":294,"y":426},{"time":14819,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14974,"x":294,"y":426},{"type":"mousedown","time":15136,"x":342,"y":425},{"type":"mousemove","time":15207,"x":343,"y":425},{"type":"mouseup","time":15253,"x":343,"y":425},{"time":15254,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15424,"x":416,"y":425},{"type":"mousedown","time":15545,"x":428,"y":425},{"type":"mousemove","time":15644,"x":428,"y":425},{"type":"mouseup","time":15691,"x":428,"y":425},{"time":15692,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15743,"x":428,"y":426},{"type":"mousemove","time":15949,"x":334,"y":447},{"type":"mousedown","time":16139,"x":327,"y":354},{"type":"mousemove","time":16185,"x":327,"y":354},{"type":"mouseup","time":16301,"x":327,"y":354},{"time":16302,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16900,"x":326,"y":348},{"type":"mousemove","time":17102,"x":134,"y":169},{"type":"mousemove","time":17310,"x":135,"y":154},{"type":"mousemove","time":17524,"x":127,"y":130},{"type":"mousedown","time":17629,"x":124,"y":127},{"type":"mousemove","time":17741,"x":124,"y":127},{"type":"mouseup","time":17785,"x":124,"y":127},{"time":17786,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17874,"x":124,"y":126},{"type":"mousemove","time":18079,"x":124,"y":120},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":18938,"target":"select"},{"time":18939,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18956,"x":164,"y":135},{"type":"mousemove","time":19189,"x":294,"y":117},{"type":"mousemove","time":19418,"x":288,"y":113},{"type":"mousemove","time":19886,"x":288,"y":113},{"type":"mousemove","time":20101,"x":250,"y":221},{"type":"mousedown","time":20313,"x":246,"y":248},{"type":"mousemove","time":20363,"x":246,"y":248},{"type":"mouseup","time":20433,"x":246,"y":248},{"time":20434,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20528,"x":245,"y":248},{"type":"mousemove","time":20736,"x":117,"y":242},{"type":"mousemove","time":20970,"x":116,"y":242},{"type":"mousedown","time":20986,"x":116,"y":242},{"type":"mouseup","time":21148,"x":116,"y":242},{"time":21149,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21222,"x":117,"y":242},{"type":"mousemove","time":21422,"x":150,"y":237},{"type":"mousemove","time":21624,"x":80,"y":308},{"type":"mousedown","time":21677,"x":79,"y":308},{"type":"mouseup","time":21852,"x":79,"y":308},{"time":21853,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21889,"x":79,"y":308},{"type":"mousemove","time":22291,"x":79,"y":307},{"type":"mousemove","time":22492,"x":75,"y":293},{"type":"mousemove","time":22704,"x":76,"y":456},{"type":"mousemove","time":22905,"x":89,"y":518},{"type":"mousedown","time":22936,"x":89,"y":518},{"type":"mouseup","time":23101,"x":89,"y":518},{"time":23102,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23146,"x":89,"y":518},{"type":"mousemove","time":23304,"x":89,"y":522},{"type":"mousemove","time":23522,"x":90,"y":552},{"type":"mousedown","time":23554,"x":90,"y":552},{"type":"mouseup","time":23704,"x":90,"y":552},{"time":23705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23773,"x":92,"y":552},{"type":"mousedown","time":23958,"x":184,"y":554},{"type":"mousemove","time":23974,"x":184,"y":554},{"type":"mouseup","time":24087,"x":184,"y":554},{"time":24088,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24176,"x":199,"y":554},{"type":"mousemove","time":24389,"x":292,"y":551},{"type":"mousemove","time":24553,"x":298,"y":550},{"type":"mousemove","time":24754,"x":548,"y":530},{"type":"mousemove","time":24954,"x":555,"y":540},{"type":"mousedown","time":25004,"x":560,"y":541},{"type":"mouseup","time":25089,"x":560,"y":542},{"time":25090,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25158,"x":561,"y":539},{"type":"mousemove","time":25365,"x":685,"y":531},{"type":"mousemove","time":25566,"x":729,"y":522},{"type":"mousemove","time":25777,"x":730,"y":543},{"type":"mousedown","time":25828,"x":730,"y":543},{"type":"mouseup","time":25989,"x":730,"y":543},{"time":25990,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26104,"x":730,"y":541},{"type":"mousemove","time":26312,"x":735,"y":512},{"type":"mousemove","time":26519,"x":752,"y":522},{"type":"mousemove","time":26724,"x":764,"y":518},{"type":"mousemove","time":26955,"x":761,"y":478},{"type":"mousemove","time":27155,"x":763,"y":504},{"type":"mousemove","time":27356,"x":765,"y":475},{"type":"mousedown","time":27471,"x":763,"y":439},{"type":"mousemove","time":27567,"x":763,"y":439},{"type":"mouseup","time":27622,"x":763,"y":439},{"time":27623,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27770,"x":763,"y":438},{"type":"mousemove","time":27979,"x":753,"y":352},{"type":"mousedown","time":28013,"x":753,"y":352},{"type":"mouseup","time":28100,"x":753,"y":351},{"time":28101,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28186,"x":753,"y":351},{"type":"mousemove","time":28288,"x":753,"y":351},{"type":"mousemove","time":28490,"x":759,"y":269},{"type":"mousedown","time":28532,"x":759,"y":269},{"type":"mouseup","time":28677,"x":759,"y":268},{"time":28678,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28736,"x":759,"y":268},{"type":"mousemove","time":28770,"x":758,"y":268},{"type":"mousedown","time":28959,"x":702,"y":253},{"type":"mousemove","time":28977,"x":701,"y":253},{"type":"mouseup","time":29103,"x":701,"y":253},{"time":29104,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29387,"x":698,"y":253},{"type":"mousedown","time":29596,"x":549,"y":245},{"type":"mousemove","time":29612,"x":549,"y":244},{"type":"mouseup","time":29718,"x":549,"y":244},{"time":29719,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29872,"x":542,"y":249},{"type":"mousemove","time":30075,"x":358,"y":371},{"type":"mousedown","time":30110,"x":358,"y":371},{"type":"mouseup","time":30225,"x":358,"y":371},{"time":30226,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":30280,"x":361,"y":371},{"type":"mousedown","time":30401,"x":415,"y":371},{"type":"mouseup","time":30520,"x":415,"y":371},{"time":30521,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":30544,"x":383,"y":371},{"type":"mousedown","time":30746,"x":259,"y":371},{"type":"mousemove","time":30780,"x":259,"y":371},{"type":"mouseup","time":30871,"x":259,"y":371},{"time":30872,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":30911,"x":259,"y":372},{"type":"mousedown","time":31096,"x":259,"y":463},{"type":"mousemove","time":31114,"x":259,"y":464},{"type":"mouseup","time":31255,"x":259,"y":464},{"time":31256,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31320,"x":214,"y":460},{"type":"mousedown","time":31462,"x":185,"y":460},{"type":"mousemove","time":31545,"x":185,"y":460},{"type":"mouseup","time":31575,"x":185,"y":460},{"time":31576,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31755,"x":396,"y":438},{"type":"mousemove","time":31970,"x":739,"y":383},{"type":"mousemove","time":32180,"x":709,"y":367},{"type":"mousedown","time":32289,"x":689,"y":359},{"type":"mousemove","time":32398,"x":689,"y":359},{"type":"mouseup","time":32413,"x":689,"y":359},{"time":32414,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32575,"x":687,"y":359},{"type":"mousemove","time":32790,"x":248,"y":110},{"type":"mousemove","time":32993,"x":249,"y":104},{"type":"mousemove","time":33205,"x":273,"y":114},{"type":"mousemove","time":33419,"x":274,"y":114},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":34730,"target":"select"},{"time":34731,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34747,"x":277,"y":137},{"type":"mousemove","time":34959,"x":277,"y":138},{"type":"mousemove","time":35166,"x":247,"y":217},{"type":"mousemove","time":35375,"x":231,"y":236},{"type":"mousedown","time":35457,"x":229,"y":239},{"type":"mouseup","time":35556,"x":229,"y":239},{"time":35557,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35611,"x":229,"y":239},{"type":"mousemove","time":35774,"x":233,"y":239},{"type":"mousemove","time":35989,"x":436,"y":257},{"type":"mousedown","time":36023,"x":436,"y":257},{"type":"mouseup","time":36140,"x":437,"y":257},{"time":36141,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36190,"x":437,"y":257},{"type":"mousemove","time":36340,"x":437,"y":257},{"type":"mousemove","time":36555,"x":738,"y":250},{"type":"mousedown","time":36721,"x":747,"y":245},{"type":"mousemove","time":36771,"x":747,"y":245},{"type":"mouseup","time":36840,"x":747,"y":245},{"time":36841,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36971,"x":747,"y":302},{"type":"mousemove","time":37183,"x":745,"y":362},{"type":"mousedown","time":37219,"x":745,"y":362},{"type":"mouseup","time":37326,"x":745,"y":362},{"time":37327,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37408,"x":745,"y":362},{"type":"mousemove","time":37499,"x":742,"y":368},{"type":"mousemove","time":37725,"x":730,"y":463},{"type":"mousedown","time":37742,"x":730,"y":463},{"type":"mouseup","time":37854,"x":730,"y":463},{"time":37855,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":37916,"x":728,"y":472},{"type":"mousedown","time":38125,"x":653,"y":545},{"type":"mousemove","time":38141,"x":652,"y":545},{"type":"mouseup","time":38254,"x":652,"y":545},{"time":38255,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38356,"x":698,"y":532},{"type":"mousedown","time":38566,"x":740,"y":528},{"type":"mousemove","time":38595,"x":740,"y":528},{"type":"mouseup","time":38727,"x":740,"y":528},{"time":38728,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38753,"x":739,"y":528},{"type":"mousemove","time":38965,"x":135,"y":510},{"type":"mousemove","time":39166,"x":102,"y":530},{"type":"mousedown","time":39263,"x":96,"y":545},{"type":"mouseup","time":39358,"x":95,"y":545},{"time":39359,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39410,"x":95,"y":545},{"type":"mousemove","time":39611,"x":175,"y":553},{"type":"mousedown","time":39646,"x":175,"y":553},{"type":"mouseup","time":39756,"x":175,"y":553},{"time":39757,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39818,"x":135,"y":511},{"type":"mousedown","time":39977,"x":109,"y":447},{"type":"mousemove","time":40042,"x":109,"y":447},{"type":"mouseup","time":40095,"x":109,"y":447},{"time":40096,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40248,"x":109,"y":447},{"type":"mousedown","time":40442,"x":141,"y":311},{"type":"mousemove","time":40499,"x":141,"y":311},{"type":"mouseup","time":40536,"x":141,"y":311},{"time":40537,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40699,"x":213,"y":331},{"type":"mousedown","time":40744,"x":215,"y":333},{"type":"mouseup","time":40835,"x":215,"y":333},{"time":40836,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40918,"x":246,"y":333},{"type":"mousedown","time":41045,"x":312,"y":337},{"type":"mouseup","time":41154,"x":312,"y":337},{"time":41155,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41192,"x":312,"y":337},{"type":"mousemove","time":41318,"x":313,"y":338},{"type":"mousemove","time":41525,"x":370,"y":407},{"type":"mousedown","time":41747,"x":384,"y":441},{"type":"mousemove","time":41763,"x":384,"y":441},{"type":"mouseup","time":41818,"x":384,"y":441},{"time":41819,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41981,"x":327,"y":351},{"type":"mousemove","time":42202,"x":289,"y":215},{"type":"mousemove","time":42313,"x":285,"y":213},{"type":"mousemove","time":42525,"x":145,"y":127},{"type":"mousemove","time":42736,"x":137,"y":115},{"type":"mousemove","time":42968,"x":132,"y":114},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":43852,"target":"select"},{"time":43853,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":43891,"x":210,"y":263},{"type":"mousemove","time":44115,"x":233,"y":283},{"type":"mousemove","time":44183,"x":236,"y":280},{"type":"mousemove","time":44386,"x":193,"y":347},{"type":"mousemove","time":44595,"x":174,"y":334},{"type":"mousedown","time":44817,"x":236,"y":315},{"type":"mousemove","time":44834,"x":236,"y":315},{"type":"mouseup","time":44867,"x":236,"y":315},{"time":44868,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45036,"x":297,"y":266},{"type":"mousedown","time":45110,"x":302,"y":260},{"type":"mouseup","time":45222,"x":302,"y":260},{"time":45223,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45245,"x":298,"y":260},{"type":"mousemove","time":45448,"x":129,"y":260},{"type":"mousedown","time":45545,"x":119,"y":260},{"type":"mouseup","time":45655,"x":119,"y":260},{"time":45656,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":45691,"x":119,"y":260},{"type":"mousemove","time":45751,"x":119,"y":260},{"type":"mousemove","time":45953,"x":107,"y":330},{"type":"mousedown","time":45990,"x":107,"y":330},{"type":"mouseup","time":46099,"x":107,"y":330},{"time":46100,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46309,"x":107,"y":331},{"type":"mousemove","time":46510,"x":95,"y":483},{"type":"mousedown","time":46584,"x":95,"y":484},{"type":"mouseup","time":46693,"x":95,"y":484},{"time":46694,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46749,"x":95,"y":484},{"type":"mousemove","time":46889,"x":95,"y":487},{"type":"mousemove","time":47090,"x":112,"y":569},{"type":"mousedown","time":47126,"x":112,"y":569},{"type":"mouseup","time":47310,"x":112,"y":569},{"time":47311,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":47480,"x":114,"y":569},{"type":"mousedown","time":47619,"x":190,"y":570},{"type":"mousemove","time":47755,"x":191,"y":570},{"type":"mouseup","time":47776,"x":191,"y":570},{"time":47777,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48005,"x":486,"y":570},{"type":"mousemove","time":48213,"x":665,"y":551},{"type":"mousemove","time":48417,"x":729,"y":534},{"type":"mousemove","time":48621,"x":746,"y":545},{"type":"mousedown","time":48660,"x":747,"y":546},{"type":"mouseup","time":48754,"x":747,"y":546},{"time":48755,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48835,"x":747,"y":525},{"type":"mousedown","time":48991,"x":755,"y":460},{"type":"mousemove","time":49042,"x":755,"y":460},{"type":"mouseup","time":49165,"x":755,"y":460},{"time":49166,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":49382,"x":755,"y":459},{"type":"mousedown","time":49600,"x":758,"y":395},{"type":"mousemove","time":49617,"x":758,"y":394},{"type":"mouseup","time":49721,"x":758,"y":394},{"time":49722,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":49824,"x":758,"y":384},{"type":"mousedown","time":50006,"x":758,"y":349},{"type":"mousemove","time":50024,"x":758,"y":349},{"type":"mouseup","time":50183,"x":758,"y":349},{"time":50184,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50407,"x":758,"y":344},{"type":"mousedown","time":50530,"x":758,"y":303},{"type":"mousemove","time":50626,"x":758,"y":303},{"type":"mouseup","time":50659,"x":758,"y":303},{"time":50660,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":50856,"x":758,"y":271},{"type":"mousemove","time":50927,"x":758,"y":271},{"type":"mouseup","time":51084,"x":758,"y":271},{"time":51085,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51169,"x":751,"y":271},{"type":"mousemove","time":51372,"x":692,"y":246},{"type":"mousedown","time":51418,"x":690,"y":246},{"type":"mouseup","time":51525,"x":690,"y":246},{"time":51526,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":51588,"x":690,"y":246},{"type":"mousemove","time":51709,"x":689,"y":246},{"type":"mousedown","time":51914,"x":405,"y":247},{"type":"mousemove","time":51932,"x":405,"y":247},{"type":"mouseup","time":52025,"x":405,"y":247},{"time":52026,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":52139,"x":356,"y":331},{"type":"mousedown","time":52255,"x":348,"y":341},{"type":"mousemove","time":52377,"x":348,"y":341},{"type":"mouseup","time":52437,"x":348,"y":341},{"time":52438,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":52621,"x":348,"y":340},{"type":"mousemove","time":52822,"x":361,"y":304},{"type":"mousemove","time":53025,"x":461,"y":316},{"type":"mousedown","time":53211,"x":500,"y":322},{"type":"mousemove","time":53235,"x":500,"y":322},{"type":"mouseup","time":53339,"x":500,"y":322},{"time":53340,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53448,"x":491,"y":355},{"type":"mousedown","time":53554,"x":481,"y":378},{"type":"mousemove","time":53667,"x":481,"y":378},{"type":"mouseup","time":53685,"x":481,"y":378},{"time":53686,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53890,"x":329,"y":374},{"type":"mousedown","time":53972,"x":321,"y":374},{"type":"mouseup","time":54104,"x":321,"y":374},{"time":54105,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":54145,"x":321,"y":374},{"type":"mousedown","time":54293,"x":321,"y":441},{"type":"mousemove","time":54390,"x":321,"y":441},{"type":"mouseup","time":54408,"x":321,"y":441},{"time":54409,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":54607,"x":315,"y":445},{"type":"mousemove","time":54819,"x":233,"y":478},{"type":"mousedown","time":54897,"x":227,"y":479},{"type":"mouseup","time":55028,"x":227,"y":479},{"time":55029,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":55069,"x":227,"y":479},{"type":"mousemove","time":55279,"x":518,"y":472},{"type":"mousedown","time":55418,"x":567,"y":489},{"type":"mousemove","time":55492,"x":567,"y":489},{"type":"mouseup","time":55563,"x":567,"y":489},{"time":55564,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":56180,"x":569,"y":479},{"type":"mousemove","time":56391,"x":516,"y":131},{"type":"mousemove","time":56593,"x":511,"y":132},{"type":"mousemove","time":56838,"x":518,"y":111},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"1","time":59860,"target":"select"},{"time":59861,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":59878,"x":510,"y":137},{"type":"mousemove","time":60084,"x":435,"y":185},{"type":"mousemove","time":60286,"x":365,"y":239},{"type":"mousedown","time":62504,"x":365,"y":239},{"type":"mouseup","time":62605,"x":365,"y":239},{"time":62606,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":62772,"x":363,"y":239},{"type":"mousemove","time":62987,"x":176,"y":252},{"type":"mousemove","time":63197,"x":151,"y":249},{"type":"mousedown","time":63367,"x":135,"y":247},{"type":"mousemove","time":63418,"x":134,"y":247},{"type":"mouseup","time":63457,"x":134,"y":247},{"time":63458,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":63826,"x":132,"y":256},{"type":"mousedown","time":64042,"x":109,"y":307},{"type":"mousemove","time":64074,"x":109,"y":307},{"type":"mouseup","time":64141,"x":109,"y":307},{"time":64142,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":64349,"x":108,"y":315},{"type":"mousedown","time":64576,"x":104,"y":368},{"type":"mousemove","time":64609,"x":104,"y":368},{"type":"mouseup","time":64697,"x":104,"y":368},{"time":64698,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":64918,"x":103,"y":374},{"type":"mousemove","time":65118,"x":93,"y":497},{"type":"mousemove","time":65329,"x":87,"y":540},{"type":"mousedown","time":65435,"x":90,"y":543},{"type":"mouseup","time":65526,"x":90,"y":543},{"time":65527,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":65580,"x":90,"y":543},{"type":"mousemove","time":65680,"x":92,"y":543},{"type":"mousedown","time":65881,"x":233,"y":543},{"type":"mousemove","time":65897,"x":234,"y":543},{"type":"mouseup","time":65962,"x":234,"y":543},{"time":65963,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":66121,"x":238,"y":543},{"type":"mousemove","time":66330,"x":628,"y":527},{"type":"mousemove","time":66536,"x":722,"y":524},{"type":"mousemove","time":66742,"x":757,"y":523},{"type":"mousedown","time":66781,"x":757,"y":523},{"type":"mouseup","time":66894,"x":757,"y":523},{"time":66895,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":66949,"x":757,"y":523},{"type":"mousemove","time":67034,"x":757,"y":522},{"type":"mousedown","time":67234,"x":757,"y":478},{"type":"mousemove","time":67251,"x":757,"y":478},{"type":"mouseup","time":67417,"x":757,"y":478},{"time":67418,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":67652,"x":757,"y":477},{"type":"mousedown","time":67857,"x":755,"y":367},{"type":"mousemove","time":67876,"x":755,"y":366},{"type":"mouseup","time":68011,"x":755,"y":366},{"time":68012,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":68091,"x":755,"y":338},{"type":"mousemove","time":68305,"x":751,"y":253},{"type":"mousedown","time":68343,"x":751,"y":253},{"type":"mouseup","time":68501,"x":751,"y":253},{"time":68502,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":68526,"x":723,"y":253},{"type":"mousedown","time":68659,"x":661,"y":253},{"type":"mousemove","time":68750,"x":661,"y":253},{"type":"mouseup","time":68767,"x":661,"y":253},{"time":68768,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":69036,"x":661,"y":252},{"type":"mousemove","time":69264,"x":445,"y":152},{"type":"mousemove","time":69616,"x":450,"y":152},{"type":"mousemove","time":69822,"x":529,"y":117},{"type":"mousemove","time":70050,"x":529,"y":115},{"type":"mousemove","time":70271,"x":529,"y":114},{"type":"mousemove","time":70537,"x":528,"y":114},{"type":"mousemove","time":70759,"x":528,"y":114},{"type":"mousemove","time":70964,"x":440,"y":120},{"type":"mousemove","time":71173,"x":260,"y":138},{"type":"mousemove","time":71377,"x":218,"y":114},{"type":"mousemove","time":71579,"x":195,"y":113},{"type":"mousemove","time":71727,"x":192,"y":113},{"type":"mousemove","time":71947,"x":137,"y":127},{"type":"mousemove","time":72159,"x":132,"y":118},{"type":"mousemove","time":72394,"x":132,"y":117},{"type":"mousemove","time":74652,"x":286,"y":111},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":76074,"target":"select"},{"time":76075,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":76092,"x":287,"y":102},{"type":"mousemove","time":76771,"x":287,"y":102},{"type":"mousemove","time":76976,"x":77,"y":380},{"type":"mousedown","time":77160,"x":66,"y":389},{"type":"mousemove","time":77208,"x":66,"y":389},{"type":"mouseup","time":77226,"x":66,"y":389},{"time":77227,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":77424,"x":75,"y":380},{"type":"mousemove","time":77632,"x":244,"y":262},{"type":"mousedown","time":77673,"x":244,"y":261},{"type":"mouseup","time":77802,"x":244,"y":261},{"time":77803,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":77847,"x":218,"y":294},{"type":"mousemove","time":78066,"x":157,"y":433},{"type":"mousemove","time":78273,"x":217,"y":370},{"type":"mousedown","time":78396,"x":222,"y":356},{"type":"mouseup","time":78481,"x":222,"y":356},{"time":78482,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":78528,"x":222,"y":356},{"type":"mousemove","time":78682,"x":222,"y":356},{"type":"mousemove","time":78901,"x":326,"y":347},{"type":"mousemove","time":79105,"x":344,"y":336},{"type":"mousedown","time":79218,"x":332,"y":333},{"type":"mouseup","time":79297,"x":332,"y":333},{"time":79298,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":79324,"x":332,"y":334},{"type":"mousemove","time":79535,"x":332,"y":375},{"type":"mousedown","time":79597,"x":332,"y":376},{"type":"mouseup","time":79724,"x":332,"y":376},{"time":79725,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":79749,"x":328,"y":376},{"type":"mousedown","time":79935,"x":253,"y":428},{"type":"mousemove","time":79987,"x":253,"y":428},{"type":"mouseup","time":80020,"x":253,"y":428},{"time":80021,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":80201,"x":246,"y":439},{"type":"mousedown","time":80407,"x":169,"y":525},{"type":"mousemove","time":80427,"x":169,"y":525},{"type":"mouseup","time":80547,"x":169,"y":525},{"time":80548,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":80635,"x":246,"y":525},{"type":"mousedown","time":80789,"x":334,"y":526},{"type":"mouseup","time":80867,"x":335,"y":526},{"time":80868,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":80916,"x":335,"y":526},{"type":"mousemove","time":81031,"x":335,"y":526},{"type":"mousemove","time":81237,"x":594,"y":531},{"type":"mousedown","time":81291,"x":601,"y":532},{"type":"mouseup","time":81418,"x":601,"y":532},{"time":81419,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":81442,"x":603,"y":532},{"type":"mousemove","time":81658,"x":713,"y":527},{"type":"mousedown","time":81854,"x":733,"y":528},{"type":"mousemove","time":81872,"x":733,"y":529},{"type":"mouseup","time":82030,"x":733,"y":529},{"time":82031,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":82077,"x":734,"y":524},{"type":"mousemove","time":82278,"x":756,"y":451},{"type":"mousedown","time":82321,"x":756,"y":449},{"type":"mouseup","time":82487,"x":755,"y":448},{"time":82488,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":82512,"x":755,"y":412},{"type":"mousedown","time":82674,"x":752,"y":321},{"type":"mousemove","time":82732,"x":752,"y":321},{"type":"mouseup","time":82811,"x":752,"y":321},{"time":82812,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":83007,"x":752,"y":321},{"type":"mousedown","time":83193,"x":766,"y":252},{"type":"mousemove","time":83213,"x":766,"y":252},{"type":"mouseup","time":83312,"x":766,"y":252},{"time":83313,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":83429,"x":646,"y":252},{"type":"mousedown","time":83495,"x":638,"y":252},{"type":"mouseup","time":83612,"x":638,"y":252},{"time":83613,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":83673,"x":638,"y":252},{"type":"mousemove","time":83893,"x":626,"y":247},{"type":"mousemove","time":84130,"x":465,"y":138},{"type":"mousemove","time":84489,"x":463,"y":138},{"type":"mousemove","time":84719,"x":397,"y":134},{"type":"mousemove","time":84938,"x":506,"y":116},{"type":"mousemove","time":85143,"x":508,"y":115},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"2","time":86781,"target":"select"},{"time":86782,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":86802,"x":420,"y":355},{"type":"mousemove","time":87005,"x":244,"y":340},{"type":"mousedown","time":87073,"x":243,"y":340},{"type":"mouseup","time":87168,"x":243,"y":340},{"time":87169,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":87210,"x":244,"y":340},{"type":"mousedown","time":87393,"x":353,"y":321},{"type":"mousemove","time":87413,"x":354,"y":321},{"type":"mouseup","time":87491,"x":354,"y":321},{"time":87492,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":87627,"x":354,"y":356},{"type":"mousedown","time":87774,"x":353,"y":374},{"type":"mousemove","time":87852,"x":353,"y":374},{"type":"mouseup","time":87878,"x":353,"y":374},{"time":87879,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":88037,"x":353,"y":375},{"type":"mousemove","time":88238,"x":342,"y":419},{"type":"mousedown","time":88279,"x":341,"y":423},{"type":"mouseup","time":88363,"x":341,"y":423},{"time":88364,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":88455,"x":341,"y":466},{"type":"mousedown","time":88587,"x":341,"y":488},{"type":"mousemove","time":88662,"x":341,"y":488},{"type":"mouseup","time":88741,"x":341,"y":488},{"time":88742,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":88765,"x":341,"y":488},{"type":"mousedown","time":88948,"x":242,"y":488},{"type":"mousemove","time":89006,"x":242,"y":488},{"type":"mouseup","time":89062,"x":242,"y":488},{"time":89063,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":89214,"x":415,"y":541},{"type":"mousedown","time":89358,"x":461,"y":544},{"type":"mousemove","time":89440,"x":461,"y":544},{"type":"mouseup","time":89519,"x":461,"y":544},{"time":89520,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":89699,"x":462,"y":544},{"type":"mousemove","time":89924,"x":702,"y":536},{"type":"mousedown","time":90126,"x":760,"y":537},{"type":"mousemove","time":90144,"x":760,"y":537},{"type":"mouseup","time":90267,"x":760,"y":537},{"time":90268,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":90352,"x":760,"y":430},{"type":"mousedown","time":90436,"x":760,"y":416},{"type":"mouseup","time":90524,"x":760,"y":416},{"time":90525,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":90585,"x":760,"y":416},{"type":"mousemove","time":90667,"x":760,"y":407},{"type":"mousemove","time":90873,"x":756,"y":228},{"type":"mousedown","time":90913,"x":756,"y":227},{"type":"mouseup","time":91062,"x":756,"y":227},{"time":91063,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":91121,"x":756,"y":227},{"type":"mousemove","time":91283,"x":734,"y":227},{"type":"mousemove","time":91501,"x":168,"y":223},{"type":"mousemove","time":91704,"x":135,"y":228},{"type":"mousedown","time":91743,"x":135,"y":228},{"type":"mouseup","time":91865,"x":135,"y":228},{"time":91866,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":91929,"x":135,"y":228},{"type":"mousemove","time":92070,"x":118,"y":270},{"type":"mousedown","time":92231,"x":96,"y":333},{"type":"mousemove","time":92295,"x":96,"y":334},{"type":"mouseup","time":92314,"x":96,"y":334},{"time":92315,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":92484,"x":96,"y":341},{"type":"mousemove","time":92690,"x":75,"y":524},{"type":"mousedown","time":92828,"x":72,"y":554},{"type":"mousemove","time":92910,"x":72,"y":554},{"type":"mouseup","time":92949,"x":72,"y":554},{"time":92950,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":93158,"x":75,"y":554},{"type":"mousedown","time":93363,"x":205,"y":558},{"type":"mousemove","time":93383,"x":206,"y":558},{"type":"mouseup","time":93470,"x":206,"y":558},{"time":93471,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":93602,"x":206,"y":551},{"type":"mousedown","time":93790,"x":193,"y":442},{"type":"mousemove","time":93850,"x":193,"y":442},{"type":"mouseup","time":93903,"x":193,"y":442},{"time":93904,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":94076,"x":238,"y":431},{"type":"mousedown","time":94240,"x":251,"y":424},{"type":"mousemove","time":94301,"x":251,"y":424},{"type":"mouseup","time":94367,"x":251,"y":424},{"time":94368,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":94514,"x":251,"y":470},{"type":"mousedown","time":94609,"x":251,"y":476},{"type":"mouseup","time":94730,"x":251,"y":476},{"time":94731,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":94760,"x":251,"y":476},{"type":"mousemove","time":94987,"x":252,"y":471},{"type":"mousemove","time":95195,"x":250,"y":217},{"type":"mousemove","time":95403,"x":324,"y":182},{"type":"mousemove","time":95609,"x":315,"y":121},{"type":"mousemove","time":95819,"x":561,"y":163},{"type":"mousemove","time":96020,"x":564,"y":143},{"type":"mousemove","time":96227,"x":537,"y":119},{"type":"mousemove","time":99312,"x":305,"y":142},{"type":"mousemove","time":99521,"x":286,"y":128},{"type":"mousemove","time":99784,"x":275,"y":116},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":100729,"target":"select"},{"time":100730,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":100761,"x":221,"y":133},{"type":"mousemove","time":100979,"x":137,"y":113},{"type":"mousemove","time":101213,"x":136,"y":112},{"type":"mousemove","time":101414,"x":140,"y":165},{"type":"mousemove","time":101643,"x":131,"y":250},{"type":"mousemove","time":101844,"x":127,"y":131},{"type":"mousemove","time":102055,"x":130,"y":119},{"type":"mousemove","time":102272,"x":129,"y":128},{"type":"mousemove","time":102492,"x":123,"y":383},{"type":"mousemove","time":102705,"x":123,"y":383},{"type":"mousemove","time":102758,"x":123,"y":383},{"type":"mousedown","time":102822,"x":123,"y":382},{"type":"mouseup","time":102964,"x":123,"y":381},{"time":102965,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":103011,"x":123,"y":381},{"type":"mousemove","time":103219,"x":127,"y":277},{"type":"mousedown","time":103330,"x":117,"y":274},{"type":"mousemove","time":103422,"x":117,"y":274},{"type":"mouseup","time":103493,"x":117,"y":274},{"time":103494,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":103633,"x":203,"y":251},{"type":"mousedown","time":103719,"x":216,"y":251},{"type":"mouseup","time":103834,"x":216,"y":251},{"time":103835,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":103873,"x":216,"y":251},{"type":"mousemove","time":104121,"x":216,"y":262},{"type":"mousemove","time":104327,"x":214,"y":390},{"type":"mousedown","time":104375,"x":214,"y":389},{"type":"mouseup","time":104473,"x":214,"y":389},{"time":104474,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":104562,"x":214,"y":389},{"type":"mousemove","time":104711,"x":213,"y":391},{"type":"mousedown","time":104866,"x":175,"y":440},{"type":"mousemove","time":104941,"x":175,"y":441},{"type":"mouseup","time":104982,"x":175,"y":441},{"time":104983,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":105175,"x":161,"y":561},{"type":"mousemove","time":105229,"x":161,"y":561},{"type":"mouseup","time":105319,"x":161,"y":561},{"time":105320,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":105397,"x":152,"y":557},{"type":"mousedown","time":105535,"x":83,"y":540},{"type":"mousemove","time":105619,"x":82,"y":540},{"type":"mouseup","time":105656,"x":82,"y":540},{"time":105657,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":105831,"x":720,"y":540},{"type":"mousemove","time":106062,"x":796,"y":540},{"type":"mousemove","time":106247,"x":774,"y":552},{"type":"mousedown","time":106380,"x":762,"y":542},{"type":"mousemove","time":106466,"x":762,"y":542},{"type":"mouseup","time":106522,"x":762,"y":542},{"time":106523,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":106670,"x":762,"y":445},{"type":"mousedown","time":106713,"x":762,"y":445},{"type":"mousemove","time":106883,"x":762,"y":429},{"type":"mouseup","time":106906,"x":762,"y":429},{"time":106907,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":106991,"x":762,"y":384},{"type":"mousemove","time":107100,"x":762,"y":382},{"type":"mouseup","time":107178,"x":762,"y":367},{"time":107179,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":107303,"x":762,"y":367},{"type":"mousemove","time":107379,"x":762,"y":367},{"type":"mousedown","time":107591,"x":753,"y":281},{"type":"mousemove","time":107630,"x":753,"y":281},{"type":"mouseup","time":107737,"x":753,"y":281},{"time":107738,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":107890,"x":747,"y":279},{"type":"mousemove","time":108108,"x":149,"y":70},{"type":"mousemove","time":108317,"x":117,"y":91},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":109827,"target":"select"},{"time":109828,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":109850,"x":129,"y":185},{"type":"mousemove","time":110073,"x":189,"y":488},{"type":"mousemove","time":110312,"x":169,"y":518},{"type":"mousemove","time":110458,"x":168,"y":518},{"type":"mousedown","time":110503,"x":167,"y":518},{"type":"mouseup","time":110698,"x":167,"y":518},{"time":110699,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":110742,"x":167,"y":518},{"type":"mousemove","time":110817,"x":166,"y":518},{"type":"mousedown","time":110860,"x":166,"y":518},{"type":"mousemove","time":111054,"x":165,"y":518},{"type":"mouseup","time":111152,"x":165,"y":518},{"time":111153,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":111347,"x":160,"y":518},{"type":"mousedown","time":111472,"x":126,"y":523},{"type":"mousemove","time":111572,"x":126,"y":523},{"type":"mouseup","time":111661,"x":125,"y":519},{"time":111662,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":111779,"x":125,"y":445},{"type":"mousemove","time":111849,"x":125,"y":445},{"type":"mouseup","time":111871,"x":125,"y":445},{"time":111872,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":112013,"x":125,"y":445},{"type":"mousemove","time":112220,"x":166,"y":254},{"type":"mousemove","time":112424,"x":136,"y":244},{"type":"mousedown","time":112468,"x":136,"y":244},{"type":"mouseup","time":112581,"x":136,"y":244},{"time":112582,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":112628,"x":171,"y":244},{"type":"mousedown","time":112753,"x":246,"y":244},{"type":"mousemove","time":112836,"x":246,"y":244},{"type":"mouseup","time":112856,"x":246,"y":244},{"time":112857,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":113010,"x":250,"y":244},{"type":"mousemove","time":113224,"x":748,"y":280},{"type":"mousemove","time":113441,"x":747,"y":243},{"type":"mousedown","time":113483,"x":747,"y":242},{"type":"mouseup","time":113586,"x":747,"y":241},{"time":113587,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":113645,"x":747,"y":241},{"type":"mousemove","time":113762,"x":747,"y":243},{"type":"mousemove","time":113966,"x":763,"y":302},{"type":"mousedown","time":114166,"x":738,"y":428},{"type":"mousemove","time":114186,"x":738,"y":429},{"type":"mouseup","time":114276,"x":738,"y":429},{"time":114277,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":114481,"x":737,"y":450},{"type":"mousedown","time":114621,"x":739,"y":490},{"type":"mousemove","time":114704,"x":739,"y":490},{"type":"mouseup","time":114724,"x":739,"y":490},{"time":114725,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":114916,"x":686,"y":540},{"type":"mousedown","time":114966,"x":685,"y":540},{"type":"mouseup","time":115072,"x":685,"y":540},{"time":115073,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":115121,"x":651,"y":532},{"type":"mousedown","time":115274,"x":550,"y":439},{"type":"mousemove","time":115339,"x":550,"y":439},{"type":"mouseup","time":115411,"x":550,"y":439},{"time":115412,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":115562,"x":542,"y":439},{"type":"mousedown","time":115710,"x":337,"y":434},{"type":"mousemove","time":115791,"x":337,"y":434},{"type":"mouseup","time":115817,"x":337,"y":434},{"time":115818,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":115965,"x":336,"y":426},{"type":"mousemove","time":116183,"x":150,"y":92},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":117451,"target":"select"},{"time":117452,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":117480,"x":277,"y":137},{"type":"mousemove","time":117689,"x":297,"y":121},{"type":"mousemove","time":117943,"x":298,"y":119},{"type":"mousemove","time":117964,"x":298,"y":119},{"type":"mousemove","time":118180,"x":283,"y":118},{"type":"mousemove","time":118407,"x":282,"y":118},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":119396,"target":"select"},{"time":119397,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":119420,"x":143,"y":102},{"type":"mousemove","time":119625,"x":108,"y":108},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":120576,"target":"select"},{"time":120577,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":120601,"x":99,"y":234},{"type":"mousedown","time":120777,"x":66,"y":414},{"type":"mousemove","time":120821,"x":66,"y":414},{"type":"mouseup","time":120906,"x":66,"y":414},{"time":120907,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":121090,"x":66,"y":422},{"type":"mousemove","time":121299,"x":70,"y":517},{"type":"mousedown","time":121457,"x":75,"y":541},{"type":"mouseup","time":121536,"x":75,"y":542},{"time":121537,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":121586,"x":75,"y":542},{"type":"mousemove","time":121701,"x":83,"y":542},{"type":"mousedown","time":121858,"x":199,"y":549},{"type":"mouseup","time":121937,"x":200,"y":549},{"time":121938,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":121984,"x":200,"y":549},{"type":"mousemove","time":122197,"x":432,"y":549},{"type":"mousedown","time":122241,"x":432,"y":549},{"type":"mouseup","time":122345,"x":432,"y":549},{"time":122346,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":122400,"x":432,"y":530},{"type":"mousedown","time":122589,"x":414,"y":419},{"type":"mousemove","time":122617,"x":413,"y":419},{"type":"mouseup","time":122702,"x":413,"y":419},{"time":122703,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":122858,"x":406,"y":420},{"type":"mousedown","time":123102,"x":316,"y":345},{"type":"mousemove","time":123123,"x":316,"y":345},{"type":"mouseup","time":123184,"x":316,"y":345},{"time":123185,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":123360,"x":315,"y":345},{"type":"mousemove","time":123569,"x":110,"y":234},{"type":"mousedown","time":123634,"x":108,"y":234},{"type":"mouseup","time":123751,"x":108,"y":234},{"time":123752,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":123779,"x":132,"y":234},{"type":"mousedown","time":123936,"x":375,"y":234},{"type":"mousemove","time":124021,"x":376,"y":234},{"type":"mouseup","time":124042,"x":376,"y":234},{"time":124043,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":124176,"x":377,"y":234},{"type":"mousemove","time":124388,"x":732,"y":266},{"type":"mousemove","time":124600,"x":764,"y":258},{"type":"mousedown","time":124642,"x":764,"y":258},{"type":"mouseup","time":124718,"x":764,"y":257},{"time":124719,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":124801,"x":764,"y":257},{"type":"mousemove","time":124896,"x":763,"y":267},{"type":"mousedown","time":125053,"x":754,"y":375},{"type":"mousemove","time":125133,"x":754,"y":375},{"type":"mouseup","time":125158,"x":754,"y":375},{"time":125159,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":125303,"x":754,"y":376},{"type":"mousemove","time":125521,"x":751,"y":461},{"type":"mousedown","time":125570,"x":751,"y":461},{"type":"mouseup","time":125614,"x":751,"y":461},{"time":125615,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":125764,"x":751,"y":461},{"type":"mousemove","time":125981,"x":737,"y":527},{"type":"mousedown","time":126025,"x":737,"y":527},{"type":"mouseup","time":126132,"x":737,"y":527},{"time":126133,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":126186,"x":725,"y":527},{"type":"mousedown","time":126326,"x":626,"y":528},{"type":"mousemove","time":126421,"x":625,"y":528},{"type":"mouseup","time":126443,"x":625,"y":528},{"time":126444,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":126622,"x":556,"y":447},{"type":"mousedown","time":126668,"x":556,"y":446},{"type":"mouseup","time":126775,"x":555,"y":446},{"time":126776,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":126851,"x":555,"y":446},{"type":"mousemove","time":127001,"x":555,"y":446},{"type":"mousemove","time":127211,"x":442,"y":184},{"type":"mousemove","time":127412,"x":511,"y":132},{"type":"mousemove","time":127640,"x":535,"y":123},{"type":"mousemove","time":127846,"x":549,"y":115},{"type":"mousemove","time":128076,"x":503,"y":103},{"type":"mousemove","time":128246,"x":503,"y":103},{"type":"mousemove","time":128461,"x":495,"y":93},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":129600,"target":"select"},{"time":129601,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":129622,"x":476,"y":125},{"type":"mousemove","time":130211,"x":472,"y":126},{"type":"mousemove","time":130411,"x":249,"y":253},{"type":"mousemove","time":130634,"x":239,"y":317},{"type":"mousemove","time":130845,"x":235,"y":324},{"type":"mousedown","time":131005,"x":235,"y":324},{"type":"mouseup","time":131092,"x":235,"y":324},{"time":131093,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":131121,"x":234,"y":324},{"type":"mousedown","time":131347,"x":93,"y":344},{"type":"mousemove","time":131388,"x":93,"y":344},{"type":"mouseup","time":131436,"x":93,"y":344},{"time":131437,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":131547,"x":100,"y":338},{"type":"mousemove","time":131768,"x":246,"y":247},{"type":"mousedown","time":131868,"x":256,"y":239},{"type":"mouseup","time":131955,"x":256,"y":239},{"time":131956,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":132020,"x":256,"y":239},{"type":"mousemove","time":132113,"x":255,"y":239},{"type":"mousemove","time":132334,"x":118,"y":230},{"type":"mousedown","time":132381,"x":117,"y":230},{"type":"mouseup","time":132493,"x":117,"y":230},{"time":132494,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":132544,"x":150,"y":230},{"type":"mousemove","time":132766,"x":728,"y":284},{"type":"mousemove","time":132966,"x":733,"y":241},{"type":"mousedown","time":133030,"x":733,"y":241},{"type":"mouseup","time":133139,"x":733,"y":241},{"time":133140,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":133188,"x":733,"y":246},{"type":"mousedown","time":133370,"x":737,"y":359},{"type":"mousemove","time":133393,"x":737,"y":359},{"type":"mouseup","time":133454,"x":737,"y":359},{"time":133455,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":133607,"x":737,"y":360},{"type":"mousemove","time":133818,"x":717,"y":494},{"type":"mousedown","time":134057,"x":732,"y":527},{"type":"mousemove","time":134077,"x":732,"y":527},{"type":"mouseup","time":134161,"x":732,"y":527},{"time":134162,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":134279,"x":661,"y":534},{"type":"mousedown","time":134353,"x":639,"y":544},{"type":"mouseup","time":134439,"x":639,"y":544},{"time":134440,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":134505,"x":639,"y":544},{"type":"mousemove","time":134620,"x":633,"y":544},{"type":"mousemove","time":134822,"x":141,"y":516},{"type":"mousedown","time":135042,"x":111,"y":529},{"type":"mousemove","time":135061,"x":111,"y":529},{"type":"mouseup","time":135102,"x":111,"y":529},{"time":135103,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":135324,"x":184,"y":453},{"type":"mousemove","time":135531,"x":567,"y":111},{"type":"mousemove","time":135744,"x":572,"y":101},{"type":"mousemove","time":135948,"x":527,"y":115},{"type":"mousemove","time":136153,"x":527,"y":115},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"0","time":137351,"target":"select"},{"time":137352,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":137395,"x":288,"y":128},{"type":"mousemove","time":137603,"x":288,"y":128},{"type":"mousemove","time":137794,"x":289,"y":127},{"type":"mousemove","time":138012,"x":271,"y":116},{"type":"mousemove","time":138243,"x":271,"y":115},{"type":"mousemove","time":138419,"x":271,"y":115},{"type":"mousemove","time":138622,"x":181,"y":113},{"type":"mousemove","time":138822,"x":158,"y":117},{"type":"mousemove","time":139065,"x":142,"y":121},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":139963,"target":"select"},{"time":139964,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":139986,"x":239,"y":101},{"type":"mousedown","time":140124,"x":284,"y":104},{"type":"mousemove","time":140192,"x":284,"y":104},{"type":"mouseup","time":140256,"x":284,"y":104},{"time":140257,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":140303,"x":284,"y":104},{"type":"mousedown","time":140487,"x":282,"y":125},{"type":"mousemove","time":140556,"x":282,"y":125},{"type":"mouseup","time":140672,"x":282,"y":125},{"time":140673,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":140963,"x":282,"y":123},{"type":"mousemove","time":141170,"x":283,"y":120},{"type":"mousemove","time":142062,"x":281,"y":155},{"type":"mousemove","time":142278,"x":94,"y":330},{"type":"mousedown","time":142381,"x":80,"y":340},{"type":"mouseup","time":142487,"x":80,"y":340},{"time":142488,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":142533,"x":80,"y":340},{"type":"mousemove","time":142627,"x":81,"y":340},{"type":"mousedown","time":142800,"x":234,"y":361},{"type":"mousemove","time":142868,"x":235,"y":362},{"type":"mouseup","time":142891,"x":235,"y":362},{"time":142892,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":143029,"x":241,"y":362},{"type":"mousedown","time":143208,"x":357,"y":362},{"type":"mousemove","time":143230,"x":357,"y":362},{"type":"mouseup","time":143338,"x":357,"y":362},{"time":143339,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":143430,"x":260,"y":460},{"type":"mousedown","time":143590,"x":198,"y":535},{"type":"mousemove","time":143663,"x":198,"y":535},{"type":"mouseup","time":143704,"x":198,"y":535},{"time":143705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":143868,"x":202,"y":535},{"type":"mousemove","time":144089,"x":539,"y":519},{"type":"mousedown","time":144132,"x":539,"y":519},{"type":"mouseup","time":144237,"x":539,"y":520},{"time":144238,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":144309,"x":636,"y":499},{"type":"mousemove","time":144511,"x":752,"y":434},{"type":"mousedown","time":144597,"x":752,"y":434},{"type":"mouseup","time":144734,"x":752,"y":434},{"time":144735,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":144761,"x":752,"y":444},{"type":"mousemove","time":144985,"x":752,"y":519},{"type":"mousedown","time":145007,"x":752,"y":519},{"type":"mouseup","time":145162,"x":752,"y":519},{"time":145163,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":145190,"x":752,"y":509},{"type":"mousemove","time":145413,"x":741,"y":262},{"type":"mousedown","time":145489,"x":740,"y":257},{"type":"mousemove","time":145627,"x":740,"y":257},{"type":"mouseup","time":145672,"x":739,"y":257},{"time":145673,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":145808,"x":644,"y":257},{"type":"mousemove","time":145830,"x":644,"y":257},{"type":"mouseup","time":145901,"x":644,"y":257},{"time":145902,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":146131,"x":640,"y":257},{"type":"mousemove","time":146360,"x":241,"y":228},{"type":"mousedown","time":146454,"x":230,"y":227},{"type":"mouseup","time":146573,"x":230,"y":227},{"time":146574,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":146624,"x":230,"y":227},{"type":"mousemove","time":146819,"x":226,"y":227},{"type":"mousemove","time":147023,"x":109,"y":245},{"type":"mousedown","time":147069,"x":109,"y":245},{"type":"mouseup","time":147155,"x":109,"y":245},{"time":147156,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":147257,"x":109,"y":245},{"type":"mousemove","time":147473,"x":242,"y":148},{"type":"mousemove","time":147689,"x":252,"y":119},{"type":"mousemove","time":147898,"x":279,"y":108},{"type":"mousemove","time":148130,"x":279,"y":108},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":148953,"target":"select"},{"time":148954,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":149004,"x":276,"y":213},{"type":"mousemove","time":149223,"x":243,"y":356},{"type":"mousedown","time":149379,"x":234,"y":375},{"type":"mousemove","time":149479,"x":234,"y":375},{"type":"mouseup","time":149502,"x":234,"y":375},{"time":149503,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":149693,"x":364,"y":378},{"type":"mousedown","time":149767,"x":373,"y":378},{"type":"mouseup","time":149842,"x":373,"y":378},{"time":149843,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":149938,"x":373,"y":378},{"type":"mousemove","time":150035,"x":382,"y":377},{"type":"mousedown","time":150203,"x":513,"y":359},{"type":"mousemove","time":150277,"x":513,"y":359},{"type":"mouseup","time":150321,"x":513,"y":359},{"time":150322,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":150495,"x":628,"y":359},{"type":"mousedown","time":150543,"x":628,"y":359},{"type":"mouseup","time":150638,"x":628,"y":359},{"time":150639,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":150914,"x":614,"y":346},{"type":"mousemove","time":151117,"x":80,"y":82},{"type":"mousemove","time":151349,"x":80,"y":83},{"type":"mousemove","time":151509,"x":80,"y":86},{"type":"mousemove","time":151712,"x":116,"y":111},{"type":"mousemove","time":151918,"x":119,"y":133},{"type":"mousemove","time":152150,"x":111,"y":147},{"type":"mousedown","time":152296,"x":108,"y":158},{"type":"mouseup","time":152389,"x":108,"y":158},{"time":152390,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":152439,"x":108,"y":158},{"type":"mousemove","time":152579,"x":117,"y":158},{"type":"mousemove","time":152785,"x":358,"y":144},{"type":"mousemove","time":152997,"x":369,"y":156},{"type":"mousedown","time":153091,"x":371,"y":159},{"type":"mouseup","time":153205,"x":371,"y":159},{"time":153206,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":153232,"x":378,"y":159},{"type":"mousedown","time":153472,"x":569,"y":159},{"type":"mousemove","time":153513,"x":569,"y":159},{"type":"mouseup","time":153608,"x":569,"y":159},{"time":153609,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":153661,"x":563,"y":159},{"type":"mousemove","time":153868,"x":438,"y":135},{"type":"mousemove","time":154087,"x":248,"y":99},{"type":"mousemove","time":154330,"x":247,"y":95},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"2","time":155393,"target":"select"},{"time":155394,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":155417,"x":204,"y":204},{"type":"mousemove","time":155821,"x":208,"y":198},{"type":"mousemove","time":156029,"x":172,"y":115},{"type":"mousemove","time":156232,"x":124,"y":106},{"type":"mousemove","time":156447,"x":119,"y":108},{"type":"mousemove","time":156680,"x":120,"y":113},{"type":"mousemove","time":156902,"x":113,"y":311},{"type":"mousedown","time":157006,"x":112,"y":330},{"type":"mouseup","time":157094,"x":112,"y":330},{"time":157095,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":157170,"x":112,"y":330},{"type":"mousemove","time":157283,"x":120,"y":330},{"type":"mousedown","time":157436,"x":250,"y":340},{"type":"mouseup","time":157528,"x":250,"y":341},{"time":157529,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":157583,"x":250,"y":341},{"type":"mousedown","time":157758,"x":370,"y":230},{"type":"mouseup","time":157831,"x":370,"y":230},{"time":157832,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":157877,"x":370,"y":230},{"type":"mousemove","time":157999,"x":380,"y":230},{"type":"mousemove","time":158208,"x":727,"y":255},{"type":"mousedown","time":158353,"x":739,"y":255},{"type":"mousemove","time":158449,"x":739,"y":255},{"type":"mouseup","time":158475,"x":739,"y":255},{"time":158476,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":158654,"x":739,"y":385},{"type":"mousedown","time":158701,"x":739,"y":385},{"type":"mouseup","time":158767,"x":739,"y":385},{"time":158768,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":158917,"x":739,"y":386},{"type":"mousemove","time":159126,"x":700,"y":523},{"type":"mousemove","time":159329,"x":742,"y":529},{"type":"mousedown","time":159400,"x":745,"y":528},{"type":"mouseup","time":159513,"x":745,"y":528},{"time":159514,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":159542,"x":725,"y":528},{"type":"mousedown","time":159684,"x":629,"y":547},{"type":"mousemove","time":159771,"x":629,"y":547},{"type":"mouseup","time":159813,"x":629,"y":547},{"time":159814,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":159976,"x":181,"y":387},{"type":"mousemove","time":160222,"x":174,"y":387},{"type":"mousedown","time":160428,"x":53,"y":386},{"type":"mousemove","time":160451,"x":53,"y":386},{"type":"mouseup","time":160493,"x":53,"y":386},{"time":160494,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":160667,"x":87,"y":324},{"type":"mousemove","time":160896,"x":258,"y":143},{"type":"mousemove","time":161129,"x":294,"y":119},{"type":"mousemove","time":161354,"x":289,"y":110},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":162311,"target":"select"},{"time":162312,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":162335,"x":289,"y":99},{"type":"mousemove","time":162557,"x":222,"y":267},{"type":"mousemove","time":162770,"x":219,"y":286},{"type":"mousedown","time":162906,"x":231,"y":309},{"type":"mouseup","time":162979,"x":231,"y":309},{"time":162980,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":163030,"x":231,"y":309},{"type":"mousemove","time":163153,"x":231,"y":312},{"type":"mousedown","time":163321,"x":245,"y":371},{"type":"mousemove","time":163395,"x":245,"y":371},{"type":"mouseup","time":163443,"x":245,"y":371},{"time":163444,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":163597,"x":240,"y":489},{"type":"mousedown","time":163700,"x":240,"y":491},{"type":"mousemove","time":163798,"x":240,"y":491},{"type":"mouseup","time":163821,"x":240,"y":491},{"time":163822,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":164018,"x":235,"y":505},{"type":"mousedown","time":164180,"x":222,"y":536},{"type":"mousemove","time":164248,"x":222,"y":536},{"type":"mouseup","time":164271,"x":222,"y":536},{"time":164272,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":164469,"x":392,"y":274},{"type":"mousedown","time":164649,"x":416,"y":242},{"type":"mousemove","time":164672,"x":416,"y":241},{"type":"mouseup","time":164741,"x":416,"y":241},{"time":164742,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":164884,"x":700,"y":296},{"type":"mousemove","time":165095,"x":730,"y":303},{"type":"mousedown","time":165224,"x":764,"y":318},{"type":"mouseup","time":165323,"x":765,"y":318},{"time":165324,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":165383,"x":765,"y":318},{"type":"mousemove","time":165631,"x":691,"y":557},{"type":"mousedown","time":165682,"x":691,"y":557},{"type":"mouseup","time":165871,"x":691,"y":557},{"time":165872,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":165907,"x":643,"y":507},{"type":"mousemove","time":166140,"x":154,"y":94},{"type":"mousemove","time":166373,"x":155,"y":94},{"type":"mousemove","time":166592,"x":167,"y":90},{"type":"mousemove","time":167035,"x":167,"y":90},{"type":"mousemove","time":167245,"x":134,"y":113},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":168280,"target":"select"},{"time":168281,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":168337,"x":142,"y":125},{"type":"mousemove","time":168547,"x":140,"y":221},{"type":"mousedown","time":168741,"x":112,"y":264},{"type":"mousemove","time":168765,"x":112,"y":264},{"type":"mouseup","time":168841,"x":112,"y":264},{"time":168842,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":168978,"x":103,"y":288},{"type":"mousemove","time":169181,"x":155,"y":280},{"type":"mousemove","time":169382,"x":113,"y":345},{"type":"mousedown","time":169431,"x":113,"y":345},{"type":"mouseup","time":169485,"x":113,"y":345},{"time":169486,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":169583,"x":113,"y":345},{"type":"mousemove","time":169697,"x":113,"y":345},{"type":"mousemove","time":169911,"x":162,"y":90},{"type":"mousemove","time":170136,"x":163,"y":88},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"3","time":170998,"target":"select"},{"time":170999,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":171024,"x":131,"y":127},{"type":"mousemove","time":171228,"x":125,"y":118},{"type":"mousemove","time":171461,"x":123,"y":111},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":172809,"target":"select"},{"time":172810,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":172835,"x":217,"y":92},{"type":"mousemove","time":173058,"x":264,"y":107},{"type":"mousemove","time":173264,"x":336,"y":123},{"type":"mousemove","time":173476,"x":176,"y":152},{"type":"mousemove","time":173684,"x":157,"y":152},{"type":"mousedown","time":173754,"x":157,"y":152},{"type":"mouseup","time":173874,"x":157,"y":152},{"time":173875,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":173926,"x":165,"y":152},{"type":"mousedown","time":174145,"x":331,"y":161},{"type":"mousemove","time":174168,"x":331,"y":161},{"type":"mouseup","time":174270,"x":331,"y":161},{"time":174271,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":174443,"x":334,"y":161},{"type":"mousemove","time":174671,"x":530,"y":139},{"type":"mousedown","time":174722,"x":532,"y":139},{"type":"mouseup","time":174856,"x":533,"y":140},{"time":174857,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":174929,"x":533,"y":140},{"type":"mousemove","time":174954,"x":533,"y":141},{"type":"mousedown","time":175099,"x":533,"y":160},{"type":"mouseup","time":175192,"x":533,"y":160},{"time":175193,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":175223,"x":529,"y":160},{"type":"mousedown","time":175474,"x":409,"y":172},{"type":"mousemove","time":175517,"x":409,"y":172},{"type":"mouseup","time":175561,"x":409,"y":172},{"time":175562,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":175890,"x":406,"y":172},{"type":"mousemove","time":176105,"x":195,"y":197},{"type":"mousedown","time":176154,"x":194,"y":197},{"type":"mousemove","time":176325,"x":194,"y":197},{"type":"mouseup","time":176389,"x":194,"y":197},{"time":176390,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":176419,"x":195,"y":196},{"type":"mousedown","time":176591,"x":215,"y":181},{"type":"mousemove","time":176663,"x":215,"y":181},{"type":"mouseup","time":176747,"x":215,"y":181},{"time":176748,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":177041,"x":215,"y":181},{"type":"mousemove","time":177253,"x":511,"y":148},{"type":"mousemove","time":177476,"x":208,"y":251},{"type":"mousemove","time":177678,"x":168,"y":271},{"type":"mousemove","time":177882,"x":160,"y":269},{"type":"mousedown","time":178099,"x":111,"y":265},{"type":"mousemove","time":178122,"x":111,"y":265},{"type":"mouseup","time":178241,"x":111,"y":265},{"time":178242,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":178327,"x":116,"y":265},{"type":"mousemove","time":178545,"x":221,"y":264},{"type":"mousedown","time":178718,"x":256,"y":267},{"type":"mousemove","time":178795,"x":256,"y":267},{"type":"mouseup","time":178820,"x":256,"y":267},{"time":178821,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":178973,"x":256,"y":270},{"type":"mousedown","time":179137,"x":250,"y":306},{"type":"mousemove","time":179210,"x":250,"y":306},{"type":"mouseup","time":179235,"x":250,"y":306},{"time":179236,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":179410,"x":437,"y":306},{"type":"mousedown","time":179463,"x":437,"y":306},{"type":"mouseup","time":179510,"x":437,"y":306},{"time":179511,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":179646,"x":437,"y":306},{"type":"mousemove","time":179697,"x":431,"y":317},{"type":"mousemove","time":179934,"x":273,"y":385},{"type":"mousedown","time":179979,"x":273,"y":385},{"type":"mouseup","time":180030,"x":272,"y":385},{"time":180031,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":180139,"x":369,"y":385},{"type":"mousedown","time":180274,"x":432,"y":385},{"type":"mousemove","time":180375,"x":432,"y":385},{"type":"mouseup","time":180399,"x":432,"y":385},{"time":180400,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":180601,"x":400,"y":451},{"type":"mousedown","time":180652,"x":400,"y":451},{"type":"mouseup","time":180784,"x":400,"y":451},{"time":180785,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":180814,"x":398,"y":451},{"type":"mousedown","time":181022,"x":259,"y":457},{"type":"mousemove","time":181071,"x":259,"y":457},{"type":"mouseup","time":181151,"x":259,"y":457},{"time":181152,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":181220,"x":268,"y":462},{"type":"mousedown","time":181471,"x":298,"y":506},{"type":"mousemove","time":181520,"x":298,"y":506},{"type":"mouseup","time":181545,"x":298,"y":506},{"time":181546,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":181846,"x":291,"y":506},{"type":"mousedown","time":182068,"x":82,"y":506},{"type":"mousemove","time":182091,"x":82,"y":506},{"type":"mouseup","time":182137,"x":82,"y":506},{"time":182138,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":182431,"x":91,"y":497},{"type":"mousemove","time":182648,"x":755,"y":509},{"type":"mousemove","time":182861,"x":772,"y":489},{"type":"mousemove","time":183010,"x":772,"y":492},{"type":"mousemove","time":183210,"x":761,"y":521},{"type":"mousedown","time":183262,"x":760,"y":523},{"type":"mouseup","time":183396,"x":760,"y":523},{"time":183397,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":183449,"x":760,"y":522},{"type":"mousedown","time":183639,"x":753,"y":421},{"type":"mousemove","time":183663,"x":753,"y":421},{"type":"mouseup","time":183712,"x":753,"y":421},{"time":183713,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":183925,"x":748,"y":410},{"type":"mousemove","time":184136,"x":279,"y":94},{"type":"mousemove","time":184359,"x":289,"y":91},{"type":"mousemove","time":184604,"x":295,"y":91},{"type":"mousemove","time":184880,"x":295,"y":91},{"type":"mousemove","time":185455,"x":295,"y":91},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"4","time":186757,"target":"select"},{"time":186758,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":186782,"x":277,"y":121},{"type":"mousemove","time":187675,"x":276,"y":122},{"type":"mousemove","time":187896,"x":222,"y":234},{"type":"mousedown","time":188046,"x":221,"y":234},{"type":"mousemove","time":188114,"x":221,"y":234},{"type":"mouseup","time":188138,"x":221,"y":234},{"time":188139,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":188323,"x":221,"y":241},{"type":"mousedown","time":188513,"x":210,"y":354},{"type":"mousemove","time":188539,"x":210,"y":354},{"type":"mouseup","time":188641,"x":210,"y":354},{"time":188642,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":188744,"x":199,"y":354},{"type":"mousedown","time":188922,"x":110,"y":382},{"type":"mousemove","time":188996,"x":110,"y":382},{"type":"mouseup","time":189076,"x":110,"y":382},{"time":189077,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":189303,"x":107,"y":396},{"type":"mousemove","time":189511,"x":104,"y":510},{"type":"mousemove","time":189724,"x":100,"y":535},{"type":"mousedown","time":189832,"x":96,"y":561},{"type":"mouseup","time":189925,"x":96,"y":561},{"time":189926,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":189951,"x":96,"y":561},{"type":"mousemove","time":190171,"x":330,"y":526},{"type":"mousedown","time":190364,"x":613,"y":537},{"type":"mousemove","time":190396,"x":614,"y":537},{"type":"mouseup","time":190472,"x":614,"y":537},{"time":190473,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":190620,"x":747,"y":370},{"type":"mousedown","time":190724,"x":750,"y":368},{"type":"mouseup","time":190840,"x":750,"y":368},{"time":190841,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":190886,"x":750,"y":368},{"type":"mousemove","time":190910,"x":750,"y":367},{"type":"mousemove","time":191132,"x":627,"y":230},{"type":"mousemove","time":191228,"x":618,"y":219},{"type":"mousemove","time":191442,"x":498,"y":103},{"type":"mousedown","time":191643,"x":498,"y":103},{"type":"mouseup","time":191714,"x":498,"y":103},{"time":191715,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":191816,"x":498,"y":104},{"type":"mousemove","time":192045,"x":498,"y":112},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"1","time":192767,"target":"select"},{"time":192768,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":192824,"x":516,"y":115},{"type":"mousemove","time":193025,"x":385,"y":367},{"type":"mousedown","time":193074,"x":385,"y":367},{"type":"mouseup","time":193175,"x":385,"y":367},{"time":193176,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":193233,"x":346,"y":389},{"type":"mousemove","time":193434,"x":62,"y":434},{"type":"mousedown","time":193487,"x":60,"y":434},{"type":"mouseup","time":193563,"x":60,"y":434},{"time":193564,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":193657,"x":199,"y":340},{"type":"mousedown","time":193826,"x":351,"y":278},{"type":"mousemove","time":193905,"x":351,"y":278},{"type":"mouseup","time":193955,"x":351,"y":278},{"time":193956,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":194111,"x":421,"y":450},{"type":"mousemove","time":194318,"x":471,"y":505},{"type":"mousedown","time":194557,"x":514,"y":512},{"type":"mousemove","time":194605,"x":514,"y":512},{"type":"mouseup","time":194675,"x":514,"y":512},{"time":194676,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":194811,"x":515,"y":512},{"type":"mousemove","time":195015,"x":786,"y":545},{"type":"mousedown","time":195066,"x":786,"y":545},{"type":"mouseup","time":195147,"x":786,"y":545},{"time":195148,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":195237,"x":784,"y":454},{"type":"mousedown","time":195351,"x":776,"y":406},{"type":"mouseup","time":195423,"x":776,"y":406},{"time":195424,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":195505,"x":776,"y":406},{"type":"mousemove","time":195590,"x":759,"y":393},{"type":"mousemove","time":195835,"x":494,"y":126},{"type":"mousedown","time":195990,"x":494,"y":126},{"type":"mousemove","time":196162,"x":494,"y":125},{"type":"mouseup","time":196235,"x":494,"y":125},{"time":196236,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":196342,"x":493,"y":125},{"type":"mousemove","time":196595,"x":502,"y":122},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"3","time":197810,"target":"select"},{"time":197811,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":197840,"x":477,"y":220},{"type":"mousedown","time":197893,"x":466,"y":238},{"type":"mouseup","time":198006,"x":466,"y":238},{"time":198007,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":198065,"x":510,"y":260},{"type":"mousedown","time":198239,"x":579,"y":363},{"type":"mousemove","time":198312,"x":579,"y":364},{"type":"mouseup","time":198343,"x":579,"y":364},{"time":198344,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":198477,"x":586,"y":365},{"type":"mousedown","time":198656,"x":773,"y":395},{"type":"mousemove","time":198708,"x":773,"y":395},{"type":"mouseup","time":198757,"x":773,"y":395},{"time":198758,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":198938,"x":735,"y":557},{"type":"mousemove","time":199310,"x":730,"y":553},{"type":"mousedown","time":199427,"x":739,"y":493},{"type":"mousemove","time":199562,"x":739,"y":493},{"type":"mouseup","time":199588,"x":739,"y":493},{"time":199589,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":199839,"x":675,"y":519},{"type":"mousedown","time":200025,"x":735,"y":533},{"type":"mousemove","time":200049,"x":736,"y":533},{"type":"mouseup","time":200204,"x":736,"y":533},{"time":200205,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":200264,"x":223,"y":515},{"type":"mousedown","time":200475,"x":83,"y":515},{"type":"mousemove","time":200527,"x":83,"y":515},{"type":"mouseup","time":200605,"x":83,"y":515},{"time":200606,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":200771,"x":85,"y":507},{"type":"mousedown","time":200988,"x":114,"y":394},{"type":"mousemove","time":201014,"x":114,"y":393},{"type":"mouseup","time":201064,"x":114,"y":393},{"time":201065,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":201226,"x":122,"y":366},{"type":"mousemove","time":201429,"x":137,"y":106},{"type":"mousemove","time":201667,"x":131,"y":104},{"type":"mousemove","time":201924,"x":129,"y":110},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":202822,"target":"select"},{"time":202823,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":202850,"x":280,"y":124},{"type":"mousemove","time":203091,"x":306,"y":114},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":203779,"target":"select"},{"time":203780,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":203806,"x":247,"y":223},{"type":"mousedown","time":203855,"x":238,"y":234},{"type":"mouseup","time":203909,"x":238,"y":234},{"time":203910,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":204009,"x":114,"y":241},{"type":"mousedown","time":204121,"x":73,"y":242},{"type":"mouseup","time":204224,"x":73,"y":242},{"time":204225,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":204279,"x":73,"y":294},{"type":"mousemove","time":204480,"x":73,"y":380},{"type":"mousedown","time":204631,"x":73,"y":380},{"type":"mouseup","time":204762,"x":73,"y":380},{"time":204763,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":204791,"x":79,"y":380},{"type":"mousedown","time":204965,"x":278,"y":396},{"type":"mousemove","time":204991,"x":279,"y":396},{"type":"mouseup","time":205066,"x":279,"y":396},{"time":205067,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":205192,"x":277,"y":523},{"type":"mousedown","time":205272,"x":277,"y":524},{"type":"mouseup","time":205426,"x":277,"y":524},{"time":205427,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":205478,"x":277,"y":524},{"type":"mousemove","time":205563,"x":278,"y":524},{"type":"mousemove","time":205767,"x":691,"y":469},{"type":"mousemove","time":205994,"x":747,"y":454},{"type":"mousedown","time":206046,"x":749,"y":453},{"type":"mouseup","time":206164,"x":749,"y":453},{"time":206165,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":206247,"x":749,"y":453},{"type":"mousemove","time":206274,"x":748,"y":453},{"type":"mousemove","time":206475,"x":307,"y":7},{"type":"mousemove","time":206701,"x":287,"y":80},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"5","time":207908,"target":"select"},{"time":207909,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":207938,"x":162,"y":140},{"type":"mousemove","time":208157,"x":138,"y":146},{"type":"mousemove","time":208357,"x":137,"y":145},{"type":"mousemove","time":208589,"x":125,"y":112},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":209671,"target":"select"},{"time":209672,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":209698,"x":196,"y":99},{"type":"mousemove","time":209931,"x":277,"y":112},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":210814,"target":"select"},{"time":210815,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":210854,"x":463,"y":129},{"type":"mousemove","time":211083,"x":503,"y":122},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"0","time":213364,"target":"select"},{"time":213365,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":213400,"x":340,"y":256},{"type":"mousedown","time":213600,"x":188,"y":350},{"type":"mousemove","time":213626,"x":188,"y":350},{"type":"mouseup","time":213748,"x":188,"y":350},{"time":213749,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":213854,"x":259,"y":354},{"type":"mousedown","time":213934,"x":269,"y":354},{"type":"mouseup","time":214069,"x":269,"y":354},{"time":214070,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":214102,"x":279,"y":354},{"type":"mousemove","time":214320,"x":236,"y":350},{"type":"mousedown","time":214442,"x":210,"y":349},{"type":"mousemove","time":214569,"x":210,"y":349},{"type":"mouseup","time":214630,"x":210,"y":349},{"time":214631,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":214670,"x":232,"y":286},{"type":"mousedown","time":214853,"x":246,"y":240},{"type":"mousemove","time":214931,"x":246,"y":240},{"type":"mouseup","time":215039,"x":246,"y":240},{"time":215040,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":215070,"x":239,"y":240},{"type":"mousedown","time":215266,"x":124,"y":240},{"type":"mousemove","time":215290,"x":124,"y":240},{"type":"mouseup","time":215415,"x":124,"y":240},{"time":215416,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":215503,"x":139,"y":324},{"type":"mousemove","time":215711,"x":121,"y":391},{"type":"mousemove","time":215931,"x":103,"y":444},{"type":"mousemove","time":216146,"x":71,"y":533},{"type":"mousedown","time":216195,"x":71,"y":533},{"type":"mouseup","time":216318,"x":71,"y":533},{"time":216319,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":216349,"x":111,"y":534},{"type":"mousedown","time":216488,"x":266,"y":551},{"type":"mouseup","time":216582,"x":267,"y":551},{"time":216583,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":216639,"x":267,"y":551},{"type":"mousemove","time":216730,"x":267,"y":551},{"type":"mousedown","time":216874,"x":377,"y":406},{"type":"mousemove","time":216971,"x":377,"y":405},{"type":"mouseup","time":217001,"x":377,"y":405},{"time":217002,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":217182,"x":424,"y":421},{"type":"mousemove","time":217404,"x":677,"y":529},{"type":"mousemove","time":217608,"x":728,"y":524},{"type":"mousedown","time":217633,"x":728,"y":524},{"type":"mouseup","time":217769,"x":728,"y":524},{"time":217770,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":217835,"x":740,"y":477},{"type":"mousedown","time":217943,"x":742,"y":437},{"type":"mouseup","time":218076,"x":742,"y":437},{"time":218077,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":218128,"x":742,"y":437},{"type":"mousemove","time":218243,"x":739,"y":437},{"type":"mousedown","time":218422,"x":512,"y":392},{"type":"mousemove","time":218449,"x":510,"y":392},{"type":"mouseup","time":218528,"x":510,"y":392},{"time":218529,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":218653,"x":499,"y":395},{"type":"mousemove","time":218870,"x":270,"y":415},{"type":"mousemove","time":219082,"x":223,"y":425},{"type":"mousedown","time":219135,"x":222,"y":425},{"type":"mouseup","time":219244,"x":222,"y":425},{"time":219245,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":219336,"x":222,"y":425},{"type":"mousemove","time":219414,"x":205,"y":426},{"type":"mousedown","time":219540,"x":155,"y":441},{"type":"mousemove","time":219647,"x":155,"y":441},{"type":"mouseup","time":219704,"x":155,"y":441},{"time":219705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":219922,"x":168,"y":441},{"type":"mousedown","time":219977,"x":170,"y":441},{"type":"mouseup","time":220112,"x":170,"y":441},{"time":220113,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":220152,"x":200,"y":441},{"type":"mousemove","time":220361,"x":258,"y":440},{"type":"mousedown","time":220416,"x":258,"y":440},{"type":"mouseup","time":220524,"x":258,"y":440},{"time":220525,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":220566,"x":293,"y":433},{"type":"mousemove","time":220817,"x":725,"y":329},{"type":"mousemove","time":220878,"x":782,"y":277},{"type":"mousedown","time":220998,"x":750,"y":267},{"type":"mouseup","time":221111,"x":750,"y":267},{"time":221112,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":221165,"x":750,"y":267},{"type":"mousemove","time":221259,"x":749,"y":266},{"type":"mousemove","time":221493,"x":405,"y":94},{"type":"mousemove","time":221739,"x":399,"y":93},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"6","time":222649,"target":"select"},{"time":222650,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":222682,"x":266,"y":237},{"type":"mousemove","time":222917,"x":185,"y":318},{"type":"mousemove","time":223211,"x":186,"y":318},{"type":"mousemove","time":223413,"x":256,"y":343},{"type":"mousedown","time":223612,"x":285,"y":337},{"type":"mousemove","time":223647,"x":285,"y":337},{"type":"mouseup","time":223728,"x":285,"y":337},{"time":223729,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":223902,"x":285,"y":340},{"type":"mousedown","time":224060,"x":260,"y":386},{"type":"mousemove","time":224139,"x":260,"y":386},{"type":"mouseup","time":224164,"x":260,"y":386},{"time":224165,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":224229,"x":249,"y":386},{"type":"mousedown","time":224381,"x":204,"y":386},{"type":"mousemove","time":224469,"x":204,"y":386},{"type":"mouseup","time":224496,"x":204,"y":386},{"time":224497,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":224681,"x":210,"y":312},{"type":"mousedown","time":224845,"x":210,"y":312},{"type":"mouseup","time":224961,"x":210,"y":312},{"time":224962,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":225005,"x":250,"y":312},{"type":"mousemove","time":225238,"x":412,"y":306},{"type":"mousedown","time":225370,"x":469,"y":306},{"type":"mousemove","time":225478,"x":469,"y":306},{"type":"mouseup","time":225529,"x":469,"y":306},{"time":225530,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":225696,"x":466,"y":251},{"type":"mousedown","time":225750,"x":466,"y":251},{"type":"mouseup","time":225870,"x":444,"y":250},{"time":225871,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":225900,"x":281,"y":254},{"type":"mousemove","time":226128,"x":175,"y":251},{"type":"mousemove","time":226340,"x":135,"y":261},{"type":"mousedown","time":226423,"x":134,"y":262},{"type":"mouseup","time":226509,"x":134,"y":262},{"time":226510,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":226546,"x":134,"y":308},{"type":"mousemove","time":226813,"x":122,"y":553},{"type":"mousemove","time":227006,"x":105,"y":594},{"type":"mousemove","time":227227,"x":112,"y":546},{"type":"mousedown","time":227293,"x":118,"y":537},{"type":"mouseup","time":227447,"x":118,"y":536},{"time":227448,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":227493,"x":221,"y":536},{"type":"mousedown","time":227655,"x":430,"y":536},{"type":"mousemove","time":227742,"x":431,"y":536},{"type":"mouseup","time":227796,"x":431,"y":536},{"time":227797,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":227965,"x":710,"y":532},{"type":"mousemove","time":228181,"x":761,"y":533},{"type":"mousedown","time":228294,"x":758,"y":536},{"type":"mousemove","time":228403,"x":758,"y":536},{"type":"mouseup","time":228480,"x":758,"y":536},{"time":228481,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":228630,"x":758,"y":421},{"type":"mousedown","time":228684,"x":758,"y":421},{"type":"mouseup","time":228740,"x":758,"y":421},{"time":228741,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":228832,"x":758,"y":421},{"type":"mousemove","time":228937,"x":758,"y":418},{"type":"mousedown","time":229186,"x":765,"y":277},{"type":"mousemove","time":229248,"x":765,"y":277},{"type":"mouseup","time":229327,"x":765,"y":277},{"time":229328,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":229394,"x":751,"y":281},{"type":"mousedown","time":229587,"x":668,"y":292},{"type":"mousemove","time":229672,"x":668,"y":292},{"type":"mouseup","time":229700,"x":668,"y":292},{"time":229701,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":229880,"x":567,"y":362},{"type":"mousedown","time":229978,"x":551,"y":374},{"type":"mousemove","time":230098,"x":550,"y":374},{"type":"mouseup","time":230159,"x":550,"y":374},{"time":230160,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":230264,"x":548,"y":358},{"type":"mousemove","time":230475,"x":540,"y":327},{"type":"mousedown","time":230686,"x":537,"y":303},{"type":"mousemove","time":230714,"x":537,"y":303},{"type":"mouseup","time":230773,"x":537,"y":303},{"time":230774,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":230988,"x":522,"y":291},{"type":"mousemove","time":231199,"x":293,"y":136},{"type":"mousemove","time":231431,"x":365,"y":85},{"type":"mousemove","time":231654,"x":324,"y":95},{"type":"mousemove","time":231892,"x":292,"y":108},{"type":"mousemove","time":232083,"x":292,"y":108},{"type":"mousemove","time":232339,"x":292,"y":108},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":233095,"target":"select"},{"time":233096,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":233135,"x":165,"y":130},{"type":"mousemove","time":233340,"x":136,"y":118},{"type":"mousemove","time":233594,"x":128,"y":112},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":234342,"target":"select"},{"time":234343,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":234372,"x":265,"y":131},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":235742,"target":"select"},{"time":235743,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":235776,"x":397,"y":128},{"type":"mousemove","time":235977,"x":515,"y":137},{"type":"mousemove","time":236229,"x":513,"y":120},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"2","time":236969,"target":"select"},{"time":236970,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":237009,"x":444,"y":278},{"type":"mousedown","time":237096,"x":359,"y":383},{"type":"mouseup","time":237197,"x":359,"y":383},{"time":237198,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":237288,"x":359,"y":383},{"type":"mousemove","time":237488,"x":354,"y":382},{"type":"mousemove","time":237694,"x":214,"y":300},{"type":"mousedown","time":237813,"x":214,"y":299},{"type":"mouseup","time":237923,"x":214,"y":299},{"time":237924,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":237981,"x":214,"y":299},{"type":"mousemove","time":238094,"x":214,"y":292},{"type":"mousemove","time":238305,"x":281,"y":139},{"type":"mousemove","time":238529,"x":284,"y":121},{"type":"mousemove","time":238754,"x":284,"y":118},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":239757,"target":"select"},{"time":239758,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":239788,"x":272,"y":336},{"type":"mousemove","time":240005,"x":208,"y":301},{"type":"mousedown","time":240149,"x":207,"y":299},{"type":"mousemove","time":240259,"x":207,"y":299},{"type":"mouseup","time":240288,"x":207,"y":299},{"time":240289,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":240479,"x":269,"y":350},{"type":"mousedown","time":240571,"x":280,"y":356},{"type":"mouseup","time":240690,"x":280,"y":356},{"time":240691,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":240728,"x":248,"y":273},{"type":"mousemove","time":240949,"x":249,"y":143},{"type":"mousemove","time":241164,"x":273,"y":119},{"type":"mousemove","time":241367,"x":275,"y":105},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":242274,"target":"select"},{"time":242275,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":242302,"x":137,"y":107},{"type":"mousemove","time":242518,"x":114,"y":119},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":243748,"target":"select"},{"time":243749,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":243780,"x":198,"y":326},{"type":"mousemove","time":243995,"x":211,"y":309},{"type":"mousemove","time":244220,"x":179,"y":319},{"type":"mousedown","time":244401,"x":182,"y":323},{"type":"mousemove","time":244435,"x":183,"y":322},{"type":"mouseup","time":244512,"x":183,"y":322},{"time":244513,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":244651,"x":211,"y":312},{"type":"mousedown","time":244765,"x":213,"y":312},{"type":"mouseup","time":244851,"x":213,"y":312},{"time":244852,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":244888,"x":213,"y":300},{"type":"mousemove","time":245127,"x":274,"y":167},{"type":"mousemove","time":245344,"x":261,"y":97},{"type":"mousemove","time":245583,"x":261,"y":95},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"7","time":246497,"target":"select"},{"time":246498,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":246526,"x":232,"y":355},{"type":"mousemove","time":246760,"x":230,"y":369},{"type":"mousemove","time":247102,"x":227,"y":371},{"type":"mousedown","time":247254,"x":215,"y":371},{"type":"mouseup","time":247333,"x":215,"y":371},{"time":247334,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":247389,"x":215,"y":371},{"type":"mousemove","time":247599,"x":113,"y":339},{"type":"mousedown","time":247654,"x":113,"y":339},{"type":"mouseup","time":247706,"x":113,"y":339},{"time":247707,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":247815,"x":145,"y":300},{"type":"mousedown","time":247992,"x":168,"y":269},{"type":"mousemove","time":248017,"x":168,"y":269},{"type":"mouseup","time":248145,"x":168,"y":269},{"time":248146,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":248228,"x":171,"y":273},{"type":"mousemove","time":248472,"x":245,"y":598},{"type":"mousemove","time":248576,"x":287,"y":584},{"type":"mousedown","time":248706,"x":297,"y":543},{"type":"mousemove","time":248816,"x":297,"y":543},{"type":"mouseup","time":248849,"x":297,"y":543},{"time":248850,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":249036,"x":490,"y":488},{"type":"mousemove","time":249260,"x":544,"y":488},{"type":"mousemove","time":249463,"x":739,"y":505},{"type":"mousemove","time":249679,"x":746,"y":494},{"type":"mousedown","time":249860,"x":758,"y":475},{"type":"mousemove","time":249949,"x":758,"y":475},{"type":"mouseup","time":250000,"x":758,"y":475},{"time":250001,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":250163,"x":742,"y":530},{"type":"mousemove","time":250389,"x":742,"y":530},{"type":"mousedown","time":250417,"x":742,"y":530},{"type":"mouseup","time":250524,"x":742,"y":530},{"time":250525,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":250568,"x":742,"y":458},{"type":"mousedown","time":250805,"x":742,"y":232},{"type":"mousemove","time":250859,"x":742,"y":232},{"type":"mouseup","time":250972,"x":742,"y":232},{"time":250973,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":251005,"x":705,"y":237},{"type":"mousedown","time":251184,"x":566,"y":260},{"type":"mousemove","time":251212,"x":566,"y":260},{"type":"mouseup","time":251269,"x":566,"y":260},{"time":251270,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":251406,"x":517,"y":384},{"type":"mousemove","time":251433,"x":517,"y":385},{"type":"mouseup","time":251517,"x":517,"y":385},{"time":251518,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":251649,"x":581,"y":79},{"type":"mousemove","time":251866,"x":544,"y":87},{"type":"mousemove","time":252078,"x":509,"y":110},{"type":"mousemove","time":252308,"x":509,"y":111},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"0","time":253236,"target":"select"},{"time":253237,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":253266,"x":415,"y":327},{"type":"mousedown","time":253320,"x":409,"y":349},{"type":"mouseup","time":253440,"x":409,"y":349},{"time":253441,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":253474,"x":411,"y":344},{"type":"mousedown","time":253674,"x":510,"y":251},{"type":"mousemove","time":253701,"x":510,"y":251},{"type":"mouseup","time":253809,"x":510,"y":251},{"time":253810,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":253916,"x":162,"y":270},{"type":"mousedown","time":254133,"x":97,"y":253},{"type":"mousemove","time":254160,"x":97,"y":253},{"type":"mouseup","time":254241,"x":97,"y":253},{"time":254242,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":254373,"x":113,"y":253},{"type":"mousedown","time":254497,"x":120,"y":253},{"type":"mouseup","time":254615,"x":120,"y":253},{"time":254616,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":254676,"x":120,"y":253},{"type":"mousemove","time":254706,"x":120,"y":291},{"type":"mousedown","time":254925,"x":116,"y":402},{"type":"mousemove","time":254959,"x":116,"y":403},{"type":"mouseup","time":255012,"x":116,"y":403},{"time":255013,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":255162,"x":116,"y":407},{"type":"mousemove","time":255380,"x":115,"y":532},{"type":"mousedown","time":255472,"x":115,"y":545},{"type":"mouseup","time":255556,"x":115,"y":545},{"time":255557,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":255596,"x":125,"y":545},{"type":"mousedown","time":255781,"x":285,"y":552},{"type":"mousemove","time":255815,"x":286,"y":552},{"type":"mouseup","time":255871,"x":286,"y":552},{"time":255872,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":256029,"x":324,"y":549},{"type":"mousemove","time":256256,"x":717,"y":548},{"type":"mousemove","time":256480,"x":738,"y":555},{"type":"mousedown","time":256537,"x":738,"y":555},{"type":"mouseup","time":256646,"x":738,"y":555},{"time":256647,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":256688,"x":740,"y":540},{"type":"mousedown","time":256874,"x":740,"y":405},{"type":"mousemove","time":256923,"x":740,"y":404},{"type":"mouseup","time":257001,"x":740,"y":404},{"time":257002,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":257150,"x":735,"y":404},{"type":"mousedown","time":257318,"x":634,"y":402},{"type":"mouseup","time":257396,"x":633,"y":402},{"time":257397,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":257457,"x":633,"y":402},{"type":"mousemove","time":257660,"x":549,"y":111},{"type":"mousemove","time":257875,"x":512,"y":85},{"type":"mousemove","time":258157,"x":497,"y":117},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"1","time":260161,"target":"select"},{"time":260162,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":260190,"x":362,"y":322},{"type":"mousedown","time":260216,"x":362,"y":322},{"type":"mouseup","time":260250,"x":362,"y":322},{"time":260251,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":260406,"x":362,"y":322},{"type":"mousemove","time":260505,"x":357,"y":309},{"type":"mousedown","time":260648,"x":306,"y":229},{"type":"mouseup","time":260741,"x":306,"y":229},{"time":260742,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":260776,"x":246,"y":229},{"type":"mousedown","time":261006,"x":127,"y":229},{"type":"mousemove","time":261037,"x":126,"y":229},{"type":"mouseup","time":261093,"x":126,"y":229},{"time":261094,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":261263,"x":107,"y":441},{"type":"mousedown","time":261369,"x":107,"y":441},{"type":"mouseup","time":261427,"x":107,"y":441},{"time":261428,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":261517,"x":107,"y":441},{"type":"mousemove","time":261636,"x":119,"y":448},{"type":"mousemove","time":261874,"x":286,"y":515},{"type":"mousedown","time":261935,"x":286,"y":516},{"type":"mouseup","time":262044,"x":286,"y":516},{"time":262045,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":262086,"x":338,"y":516},{"type":"mousemove","time":262309,"x":692,"y":516},{"type":"mousemove","time":262523,"x":741,"y":517},{"type":"mousedown","time":262614,"x":745,"y":518},{"type":"mouseup","time":262730,"x":745,"y":518},{"time":262731,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":262765,"x":683,"y":289},{"type":"mousemove","time":263093,"x":103,"y":10},{"type":"mousemove","time":263309,"x":131,"y":98},{"type":"mousemove","time":263527,"x":131,"y":109},{"type":"mousemove","time":263747,"x":131,"y":109},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":264507,"target":"select"},{"time":264508,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":264540,"x":294,"y":119},{"type":"mousemove","time":264756,"x":281,"y":110},{"type":"valuechange","selector":"#main_dim_data_edge_cases>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":265697,"target":"select"},{"time":265698,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":265730,"x":249,"y":211},{"type":"mousemove","time":265942,"x":163,"y":341},{"type":"mousedown","time":266006,"x":163,"y":341},{"type":"mouseup","time":266147,"x":163,"y":341},{"time":266148,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":266218,"x":163,"y":341},{"type":"mousemove","time":266252,"x":134,"y":341},{"type":"mousedown","time":266312,"x":123,"y":341},{"type":"mouseup","time":266461,"x":123,"y":341},{"time":266462,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":266495,"x":194,"y":294},{"type":"mousedown","time":266620,"x":261,"y":250},{"type":"mousemove","time":266740,"x":261,"y":250},{"type":"mousemove","time":266947,"x":74,"y":245},{"type":"mousemove","time":267152,"x":118,"y":310},{"type":"mouseup","time":267269,"x":124,"y":316},{"time":267270,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":267357,"x":124,"y":306},{"type":"mousedown","time":267564,"x":115,"y":270},{"type":"mousemove","time":267593,"x":115,"y":269},{"type":"mouseup","time":267682,"x":115,"y":269},{"time":267683,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":267806,"x":415,"y":375},{"type":"mousemove","time":268029,"x":558,"y":524},{"type":"mousedown","time":268127,"x":544,"y":524},{"type":"mousemove","time":268236,"x":544,"y":524},{"type":"mouseup","time":268264,"x":544,"y":524},{"time":268265,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":268466,"x":701,"y":502},{"type":"mousedown","time":268686,"x":712,"y":515},{"type":"mousemove","time":268713,"x":712,"y":515},{"type":"mouseup","time":268827,"x":712,"y":515},{"time":268828,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":268932,"x":733,"y":493},{"type":"mousedown","time":269063,"x":737,"y":487},{"type":"mousemove","time":269174,"x":737,"y":487},{"type":"mouseup","time":269224,"x":737,"y":487},{"time":269225,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":269441,"x":737,"y":485},{"type":"mousemove","time":269696,"x":762,"y":238},{"type":"mousedown","time":269895,"x":754,"y":257},{"type":"mousemove","time":269925,"x":754,"y":257},{"type":"mouseup","time":270135,"x":754,"y":257},{"time":270136,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":270335,"x":750,"y":257},{"type":"mousemove","time":270572,"x":587,"y":238},{"type":"mousedown","time":270631,"x":587,"y":238},{"type":"mouseup","time":270717,"x":587,"y":238},{"time":270718,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":270818,"x":587,"y":238}],"scrollY":1480,"scrollX":0,"timestamp":1748968335095}] \ No newline at end of file diff --git a/test/runTest/actions/matrix_application.json b/test/runTest/actions/matrix_application.json new file mode 100644 index 0000000000..10618b611f --- /dev/null +++ b/test/runTest/actions/matrix_application.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":837,"x":747,"y":202},{"type":"mousemove","time":1037,"x":557,"y":172},{"type":"mousemove","time":1237,"x":445,"y":144},{"type":"mousemove","time":1447,"x":440,"y":142},{"type":"mousemove","time":1654,"x":398,"y":143},{"type":"mousemove","time":1863,"x":368,"y":149},{"type":"mousemove","time":2069,"x":366,"y":149},{"type":"mousemove","time":2270,"x":423,"y":149},{"type":"mousemove","time":2470,"x":454,"y":145},{"type":"mousemove","time":2671,"x":464,"y":141},{"type":"mousemove","time":2878,"x":466,"y":141},{"type":"mousemove","time":3111,"x":472,"y":139},{"type":"mousedown","time":3471,"x":472,"y":139},{"type":"mousemove","time":3479,"x":472,"y":139},{"type":"mousemove","time":3691,"x":400,"y":143},{"type":"mousemove","time":3901,"x":369,"y":145},{"type":"mousemove","time":4111,"x":357,"y":145},{"type":"mousemove","time":4387,"x":357,"y":145},{"type":"mousemove","time":4598,"x":392,"y":147},{"type":"mousemove","time":4813,"x":449,"y":147},{"type":"mousemove","time":5013,"x":516,"y":140},{"type":"mouseup","time":5228,"x":517,"y":139},{"time":5229,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5236,"x":491,"y":139},{"type":"mousemove","time":5437,"x":361,"y":144},{"type":"mousemove","time":5637,"x":342,"y":146},{"type":"mousemove","time":5846,"x":331,"y":144},{"type":"mousedown","time":6122,"x":331,"y":144},{"type":"mousemove","time":6129,"x":331,"y":144},{"type":"mousemove","time":6343,"x":405,"y":146},{"type":"mousemove","time":6546,"x":427,"y":143},{"type":"mousemove","time":6752,"x":427,"y":143},{"type":"mousemove","time":6959,"x":343,"y":149},{"type":"mousemove","time":7163,"x":333,"y":151},{"type":"mouseup","time":7319,"x":333,"y":151},{"time":7320,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7327,"x":337,"y":154},{"type":"mousemove","time":7531,"x":431,"y":244},{"type":"mousemove","time":7736,"x":453,"y":257},{"type":"mousemove","time":7937,"x":453,"y":258},{"type":"mousemove","time":8053,"x":451,"y":260},{"type":"mousemove","time":8253,"x":407,"y":290},{"type":"mousemove","time":8462,"x":400,"y":295},{"type":"mousemove","time":8670,"x":469,"y":347},{"type":"mousemove","time":8879,"x":471,"y":348},{"type":"mousemove","time":8987,"x":470,"y":348},{"type":"mousemove","time":9196,"x":466,"y":342},{"type":"mousemove","time":9411,"x":466,"y":342}],"scrollY":403,"scrollX":0,"timestamp":1748966383025},{"name":"Action 2","ops":[{"type":"mousemove","time":564,"x":745,"y":364},{"type":"mousemove","time":776,"x":481,"y":430},{"type":"mousemove","time":979,"x":342,"y":470},{"type":"mousemove","time":1188,"x":332,"y":471},{"type":"mousemove","time":1397,"x":107,"y":536},{"type":"mousemove","time":1597,"x":60,"y":560},{"type":"mousemove","time":1797,"x":76,"y":563},{"type":"mousemove","time":2004,"x":77,"y":562},{"type":"mousemove","time":2229,"x":81,"y":560},{"type":"mousedown","time":2548,"x":81,"y":560},{"type":"mousemove","time":2556,"x":81,"y":560},{"type":"mousemove","time":2767,"x":205,"y":553},{"type":"mousemove","time":3006,"x":359,"y":547},{"type":"mousemove","time":3223,"x":360,"y":548},{"type":"mousemove","time":3747,"x":360,"y":548},{"type":"mousemove","time":3947,"x":390,"y":547},{"type":"mousemove","time":4174,"x":440,"y":549},{"type":"mousemove","time":4380,"x":489,"y":546},{"type":"mousemove","time":4590,"x":495,"y":546},{"type":"mouseup","time":4870,"x":495,"y":546},{"time":4871,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4880,"x":505,"y":543},{"type":"mousemove","time":5081,"x":529,"y":537},{"type":"mousemove","time":5291,"x":534,"y":550},{"type":"mousemove","time":5498,"x":535,"y":547},{"type":"mousemove","time":5704,"x":535,"y":546},{"type":"mousedown","time":5948,"x":535,"y":546},{"type":"mousemove","time":5956,"x":535,"y":546},{"type":"mousemove","time":6163,"x":426,"y":549},{"type":"mousemove","time":6375,"x":393,"y":551},{"type":"mousemove","time":6575,"x":370,"y":552},{"type":"mousemove","time":6782,"x":316,"y":556},{"type":"mousemove","time":6991,"x":303,"y":557},{"type":"mousemove","time":7080,"x":302,"y":557},{"type":"mousemove","time":7280,"x":201,"y":569},{"type":"mousemove","time":7488,"x":146,"y":577},{"type":"mousemove","time":7690,"x":145,"y":578},{"type":"mouseup","time":7787,"x":145,"y":578},{"time":7788,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7897,"x":393,"y":488},{"type":"mousemove","time":8103,"x":631,"y":418},{"type":"mousemove","time":8310,"x":709,"y":395},{"type":"mousemove","time":8513,"x":735,"y":383},{"type":"mousedown","time":8662,"x":743,"y":379},{"type":"mousemove","time":8724,"x":743,"y":379},{"type":"mousemove","time":9813,"x":735,"y":350},{"type":"mousemove","time":10021,"x":735,"y":350}],"scrollY":3109.5,"scrollX":0,"timestamp":1748966404699},{"name":"Action 3","ops":[{"type":"mousemove","time":421,"x":745,"y":207},{"type":"mousemove","time":633,"x":501,"y":58},{"type":"mousemove","time":837,"x":386,"y":151},{"type":"mousemove","time":1037,"x":437,"y":56},{"type":"mousemove","time":1248,"x":461,"y":36},{"type":"mousemove","time":1453,"x":463,"y":23},{"type":"mousemove","time":1653,"x":465,"y":18},{"type":"mousemove","time":1866,"x":466,"y":18},{"type":"mousedown","time":2013,"x":466,"y":18},{"type":"mousemove","time":2021,"x":465,"y":18},{"type":"mousemove","time":2234,"x":425,"y":21},{"type":"mousemove","time":2444,"x":406,"y":24},{"type":"mousemove","time":2664,"x":381,"y":25},{"type":"mousemove","time":2879,"x":367,"y":25},{"type":"mousemove","time":3097,"x":375,"y":23},{"type":"mousemove","time":3314,"x":404,"y":18},{"type":"mousemove","time":3514,"x":428,"y":12},{"type":"mouseup","time":3620,"x":428,"y":12},{"time":3621,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3629,"x":413,"y":15},{"type":"mousemove","time":3849,"x":329,"y":19},{"type":"mousedown","time":4271,"x":329,"y":19},{"type":"mousemove","time":4279,"x":329,"y":19},{"type":"mousemove","time":4491,"x":404,"y":11},{"type":"mousemove","time":4704,"x":424,"y":11},{"type":"mousemove","time":4909,"x":437,"y":10},{"type":"mousemove","time":5127,"x":415,"y":13},{"type":"mousemove","time":5327,"x":304,"y":22},{"type":"mousemove","time":5559,"x":275,"y":26},{"type":"mouseup","time":5711,"x":275,"y":26},{"time":5712,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5771,"x":376,"y":82},{"type":"mousemove","time":5992,"x":478,"y":141},{"type":"mousemove","time":6196,"x":393,"y":19},{"type":"mousemove","time":6401,"x":378,"y":17},{"type":"mousemove","time":6618,"x":522,"y":151},{"type":"mousemove","time":6835,"x":581,"y":141},{"type":"mousemove","time":7048,"x":582,"y":152},{"type":"mousemove","time":7254,"x":553,"y":155},{"type":"mousemove","time":7458,"x":535,"y":156},{"type":"mousemove","time":7667,"x":493,"y":171},{"type":"mousemove","time":7875,"x":466,"y":105},{"type":"mousemove","time":8090,"x":377,"y":22},{"type":"mousemove","time":8298,"x":392,"y":13},{"type":"mousemove","time":8528,"x":411,"y":13},{"type":"mousemove","time":8747,"x":403,"y":14},{"type":"mousemove","time":8960,"x":390,"y":14}],"scrollY":1897,"scrollX":0,"timestamp":1748966428492},{"name":"Action 4","ops":[{"type":"mousemove","time":89,"x":728,"y":495},{"type":"mousemove","time":289,"x":689,"y":564},{"type":"mousemove","time":489,"x":682,"y":577},{"type":"mousemove","time":689,"x":686,"y":580},{"type":"mousemove","time":897,"x":687,"y":581},{"type":"mousedown","time":1169,"x":687,"y":581},{"type":"mousemove","time":1178,"x":687,"y":581},{"type":"mousemove","time":1381,"x":620,"y":539},{"type":"mousemove","time":1597,"x":581,"y":525},{"type":"mousemove","time":1722,"x":580,"y":525},{"type":"mousemove","time":1922,"x":397,"y":495},{"type":"mousemove","time":2123,"x":320,"y":494},{"type":"mousemove","time":2326,"x":293,"y":491},{"type":"mousemove","time":2543,"x":474,"y":506},{"type":"mousemove","time":2749,"x":615,"y":536},{"type":"mousemove","time":2956,"x":653,"y":543},{"type":"mouseup","time":3187,"x":653,"y":543},{"time":3188,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3197,"x":616,"y":508},{"type":"mousemove","time":3400,"x":96,"y":202},{"type":"mousemove","time":3606,"x":119,"y":189},{"type":"mousemove","time":3812,"x":136,"y":189},{"type":"valuechange","selector":"#main_grid_layout>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":4665,"target":"select"},{"time":4666,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4706,"x":204,"y":248},{"type":"mousemove","time":4911,"x":651,"y":535},{"type":"mousemove","time":5117,"x":680,"y":556},{"type":"mousemove","time":5331,"x":680,"y":557},{"type":"mousemove","time":5540,"x":680,"y":557},{"type":"mousemove","time":5740,"x":648,"y":556},{"type":"mousedown","time":5987,"x":648,"y":556},{"type":"mousemove","time":5996,"x":648,"y":556},{"type":"mousemove","time":6200,"x":536,"y":542},{"type":"mousemove","time":6406,"x":478,"y":539},{"type":"mousemove","time":6608,"x":427,"y":537},{"type":"mousemove","time":6814,"x":415,"y":536},{"type":"mousemove","time":7022,"x":484,"y":545},{"type":"mousemove","time":7225,"x":627,"y":547},{"type":"mousemove","time":7429,"x":676,"y":557},{"type":"mousemove","time":7630,"x":677,"y":557},{"type":"mouseup","time":7754,"x":677,"y":557},{"time":7755,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7763,"x":682,"y":547},{"type":"mousemove","time":7964,"x":749,"y":438}],"scrollY":3606,"scrollX":0,"timestamp":1749038158325}] \ No newline at end of file diff --git a/test/runTest/actions/matrix_application2.json b/test/runTest/actions/matrix_application2.json new file mode 100644 index 0000000000..bfe797f896 --- /dev/null +++ b/test/runTest/actions/matrix_application2.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":604,"x":740,"y":10},{"type":"mousemove","time":804,"x":504,"y":229},{"type":"mousemove","time":1010,"x":450,"y":267},{"type":"mousemove","time":1538,"x":450,"y":267},{"type":"mousemove","time":1738,"x":341,"y":286},{"type":"mousemove","time":1943,"x":337,"y":286},{"type":"mousemove","time":2187,"x":335,"y":286},{"type":"mousemove","time":2388,"x":139,"y":134},{"type":"mousemove","time":2588,"x":94,"y":96},{"type":"mousemove","time":2794,"x":89,"y":85},{"type":"mousemove","time":3004,"x":322,"y":227},{"type":"mousemove","time":3204,"x":309,"y":226},{"type":"mousemove","time":3404,"x":315,"y":238},{"type":"mousedown","time":3620,"x":315,"y":238},{"type":"mousemove","time":3635,"x":315,"y":238},{"type":"mousemove","time":3836,"x":405,"y":336},{"type":"mousemove","time":4038,"x":403,"y":335},{"type":"mousemove","time":4244,"x":401,"y":334},{"type":"mousemove","time":4454,"x":362,"y":386},{"type":"mousemove","time":4655,"x":417,"y":454},{"type":"mousemove","time":4860,"x":418,"y":456},{"type":"mousemove","time":4888,"x":417,"y":457},{"type":"mousemove","time":5088,"x":449,"y":440},{"type":"mouseup","time":5278,"x":449,"y":440},{"time":5279,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5292,"x":484,"y":428},{"type":"mousedown","time":5471,"x":624,"y":356},{"type":"mousemove","time":5502,"x":624,"y":356},{"type":"mouseup","time":5570,"x":624,"y":356},{"time":5571,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5837,"x":624,"y":355},{"type":"mousemove","time":6038,"x":557,"y":224},{"type":"mousemove","time":6238,"x":593,"y":224},{"type":"mousemove","time":6444,"x":593,"y":224},{"type":"mousedown","time":6594,"x":593,"y":224},{"type":"mousemove","time":6606,"x":593,"y":227},{"type":"mousemove","time":6818,"x":595,"y":280},{"type":"mousemove","time":7022,"x":594,"y":338},{"type":"mousemove","time":7229,"x":594,"y":377},{"type":"mousemove","time":7421,"x":594,"y":378},{"type":"mousemove","time":7621,"x":594,"y":455},{"type":"mousemove","time":7828,"x":593,"y":465},{"type":"mouseup","time":8061,"x":593,"y":465},{"time":8062,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8074,"x":597,"y":455},{"type":"mousemove","time":8278,"x":612,"y":132},{"type":"mousemove","time":8487,"x":598,"y":149},{"type":"mousedown","time":8604,"x":588,"y":172},{"type":"mousemove","time":8695,"x":588,"y":172},{"type":"mouseup","time":8704,"x":588,"y":172},{"time":8705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8954,"x":585,"y":172},{"type":"mousemove","time":9154,"x":533,"y":172},{"type":"mousedown","time":9204,"x":533,"y":172},{"type":"mouseup","time":9354,"x":533,"y":172},{"time":9355,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9471,"x":532,"y":172},{"type":"mousemove","time":9671,"x":200,"y":368},{"type":"mousemove","time":9871,"x":188,"y":330},{"type":"mousemove","time":10071,"x":236,"y":286},{"type":"mousemove","time":10271,"x":242,"y":283},{"type":"mousedown","time":10404,"x":253,"y":281},{"type":"mousemove","time":10478,"x":253,"y":281},{"type":"mouseup","time":10628,"x":253,"y":281},{"time":10629,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10871,"x":253,"y":283},{"type":"mousemove","time":11072,"x":223,"y":314},{"type":"mousemove","time":11279,"x":231,"y":317},{"type":"mousedown","time":11322,"x":232,"y":317},{"type":"mouseup","time":11454,"x":232,"y":317},{"time":11455,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11495,"x":232,"y":317},{"type":"mousemove","time":11554,"x":230,"y":317},{"type":"mousemove","time":11754,"x":202,"y":317},{"type":"mousemove","time":11954,"x":209,"y":264},{"type":"mousemove","time":12163,"x":209,"y":255},{"type":"mousedown","time":12171,"x":209,"y":255},{"type":"mouseup","time":12270,"x":209,"y":255},{"time":12271,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12384,"x":209,"y":255},{"type":"mousemove","time":12521,"x":207,"y":255},{"type":"mousemove","time":12729,"x":170,"y":271},{"type":"mousemove","time":12937,"x":169,"y":362},{"type":"mousemove","time":13138,"x":169,"y":361},{"type":"mousedown","time":13262,"x":175,"y":358},{"type":"mousemove","time":13344,"x":175,"y":358},{"type":"mouseup","time":13387,"x":175,"y":358},{"time":13388,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13537,"x":174,"y":358},{"type":"mousemove","time":13738,"x":149,"y":370},{"type":"mousedown","time":13755,"x":149,"y":370},{"type":"mouseup","time":13937,"x":149,"y":370},{"time":13938,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13965,"x":149,"y":370},{"type":"mousemove","time":14037,"x":150,"y":370},{"type":"mousemove","time":14238,"x":419,"y":334},{"type":"mousemove","time":14446,"x":441,"y":326},{"type":"mousedown","time":14629,"x":441,"y":326},{"type":"mousemove","time":14640,"x":442,"y":326},{"type":"mousemove","time":14849,"x":464,"y":419},{"type":"mousemove","time":15062,"x":464,"y":437},{"type":"mouseup","time":15212,"x":464,"y":437},{"time":15213,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15224,"x":406,"y":371},{"type":"mousemove","time":15433,"x":255,"y":226},{"type":"mousemove","time":15638,"x":216,"y":173},{"type":"mousemove","time":15845,"x":215,"y":172},{"type":"mousedown","time":15972,"x":215,"y":172},{"type":"mousemove","time":15984,"x":215,"y":172},{"type":"mousemove","time":16185,"x":248,"y":206},{"type":"mousemove","time":16388,"x":276,"y":219},{"type":"mousemove","time":16588,"x":254,"y":213},{"type":"mousemove","time":16789,"x":215,"y":208},{"type":"mousemove","time":16995,"x":211,"y":206},{"type":"mouseup","time":17113,"x":211,"y":206},{"time":17114,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17126,"x":252,"y":241},{"type":"mousemove","time":17330,"x":317,"y":252},{"type":"mousemove","time":17537,"x":387,"y":258},{"type":"mousemove","time":17746,"x":387,"y":274},{"type":"mousedown","time":17905,"x":387,"y":274},{"type":"mousemove","time":17917,"x":387,"y":274},{"type":"mousemove","time":18117,"x":261,"y":243},{"type":"mousemove","time":18322,"x":209,"y":185},{"type":"mousemove","time":18530,"x":206,"y":157},{"type":"mousemove","time":18738,"x":236,"y":236},{"type":"mousemove","time":18938,"x":445,"y":310},{"type":"mousemove","time":19139,"x":481,"y":333},{"type":"mousemove","time":19351,"x":517,"y":410},{"type":"mousemove","time":19562,"x":523,"y":418},{"type":"mousemove","time":19588,"x":525,"y":419},{"type":"mousemove","time":19789,"x":603,"y":508},{"type":"mousemove","time":19999,"x":579,"y":474},{"type":"mousemove","time":20213,"x":576,"y":464},{"type":"mouseup","time":20262,"x":576,"y":464},{"time":20263,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20275,"x":589,"y":455},{"type":"mousedown","time":20430,"x":654,"y":394},{"type":"mousemove","time":20480,"x":654,"y":394},{"type":"mouseup","time":20504,"x":654,"y":394},{"time":20505,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20689,"x":651,"y":394},{"type":"mousemove","time":20896,"x":102,"y":84},{"type":"mousemove","time":21106,"x":308,"y":143},{"type":"mousemove","time":21314,"x":358,"y":271},{"type":"mousemove","time":21529,"x":348,"y":266},{"type":"mousedown","time":21596,"x":348,"y":266},{"type":"mousemove","time":21607,"x":351,"y":268},{"type":"mousemove","time":21814,"x":459,"y":337},{"type":"mouseup","time":22130,"x":460,"y":337},{"time":22131,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22143,"x":408,"y":282},{"type":"mousemove","time":22349,"x":97,"y":52},{"type":"mousemove","time":22554,"x":77,"y":73},{"type":"mousedown","time":22600,"x":77,"y":74},{"type":"mouseup","time":22704,"x":77,"y":74},{"time":22705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22761,"x":77,"y":74},{"type":"mousedown","time":23362,"x":77,"y":74},{"type":"mouseup","time":23487,"x":77,"y":74},{"time":23488,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23656,"x":78,"y":74},{"type":"mousemove","time":23856,"x":158,"y":73},{"type":"mousedown","time":23996,"x":175,"y":75},{"type":"mousemove","time":24062,"x":175,"y":75},{"type":"mouseup","time":24120,"x":175,"y":75},{"time":24121,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24271,"x":113,"y":80},{"type":"mousedown","time":24413,"x":97,"y":80},{"type":"mousemove","time":24479,"x":97,"y":80},{"type":"mouseup","time":24537,"x":97,"y":80},{"time":24538,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24888,"x":99,"y":80},{"type":"mousemove","time":25088,"x":173,"y":76},{"type":"mousedown","time":25171,"x":180,"y":75},{"type":"mouseup","time":25254,"x":180,"y":75},{"time":25255,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25298,"x":180,"y":75},{"type":"mousedown","time":25480,"x":96,"y":77},{"type":"mousemove","time":25513,"x":96,"y":77},{"type":"mouseup","time":25587,"x":96,"y":77},{"time":25588,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25655,"x":96,"y":77},{"type":"mousemove","time":25870,"x":402,"y":163},{"type":"mousemove","time":26072,"x":433,"y":174},{"type":"mousemove","time":26280,"x":359,"y":324},{"type":"mousemove","time":26405,"x":369,"y":322},{"type":"mousemove","time":26613,"x":636,"y":318},{"type":"mousemove","time":26829,"x":603,"y":346},{"type":"mousedown","time":26971,"x":603,"y":346},{"type":"mousemove","time":26984,"x":603,"y":346},{"type":"mousemove","time":27184,"x":666,"y":390},{"type":"mousemove","time":27388,"x":669,"y":406},{"type":"mousemove","time":27595,"x":669,"y":406},{"type":"mouseup","time":27630,"x":669,"y":406},{"time":27631,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27643,"x":641,"y":376},{"type":"mousemove","time":27845,"x":331,"y":139},{"type":"mousemove","time":28055,"x":315,"y":101},{"type":"mousemove","time":28264,"x":322,"y":75},{"type":"mousedown","time":28321,"x":323,"y":73},{"type":"mouseup","time":28437,"x":323,"y":73},{"time":28438,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28481,"x":323,"y":73},{"type":"mousemove","time":28490,"x":323,"y":73},{"type":"mousemove","time":28698,"x":385,"y":216},{"type":"mousemove","time":28904,"x":391,"y":294},{"type":"mousemove","time":29105,"x":371,"y":301},{"type":"mousemove","time":29313,"x":385,"y":325},{"type":"mousemove","time":29521,"x":377,"y":321},{"type":"mousemove","time":29722,"x":376,"y":361},{"type":"mousemove","time":29931,"x":381,"y":347},{"type":"mousemove","time":30146,"x":379,"y":346},{"type":"mousedown","time":30338,"x":379,"y":346},{"type":"mousemove","time":30350,"x":379,"y":346},{"type":"mousemove","time":30551,"x":447,"y":417},{"type":"mousemove","time":30755,"x":477,"y":432},{"type":"mousemove","time":30956,"x":492,"y":442},{"type":"mouseup","time":31280,"x":492,"y":442},{"time":31281,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":31295,"x":469,"y":387},{"type":"mousemove","time":31497,"x":225,"y":144},{"type":"mousemove","time":31705,"x":233,"y":97},{"type":"mousemove","time":31906,"x":334,"y":77},{"type":"mousedown","time":32021,"x":335,"y":73},{"type":"mousemove","time":32113,"x":335,"y":73},{"type":"mouseup","time":32164,"x":335,"y":73},{"time":32165,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32221,"x":335,"y":75},{"type":"mousemove","time":32430,"x":499,"y":335},{"type":"mousemove","time":32638,"x":542,"y":364},{"type":"mousemove","time":32839,"x":603,"y":461},{"type":"mousemove","time":33046,"x":610,"y":462},{"type":"mousedown","time":33055,"x":610,"y":462},{"type":"mouseup","time":33154,"x":610,"y":462},{"time":33155,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":33271,"x":610,"y":460},{"type":"mousedown","time":33471,"x":610,"y":392},{"type":"mousemove","time":33483,"x":610,"y":392},{"type":"mouseup","time":33571,"x":610,"y":392},{"time":33572,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":33638,"x":610,"y":392},{"type":"mouseup","time":33737,"x":610,"y":392},{"time":33738,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":34730,"x":610,"y":392},{"type":"mouseup","time":34830,"x":610,"y":392},{"time":34831,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":34904,"x":610,"y":392},{"type":"mouseup","time":35004,"x":610,"y":392},{"time":35005,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":35897,"x":607,"y":409},{"type":"mousedown","time":36054,"x":604,"y":501},{"type":"mousemove","time":36100,"x":604,"y":501},{"type":"mouseup","time":36163,"x":604,"y":501},{"time":36164,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36356,"x":604,"y":501},{"type":"mousedown","time":36555,"x":598,"y":467},{"type":"mousemove","time":36568,"x":598,"y":467},{"type":"mouseup","time":36654,"x":598,"y":467},{"time":36655,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":36721,"x":598,"y":467},{"type":"mouseup","time":36821,"x":598,"y":467},{"time":36822,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":36955,"x":598,"y":467},{"type":"mousemove","time":37157,"x":614,"y":465},{"type":"mousemove","time":37364,"x":612,"y":461},{"type":"mousemove","time":37571,"x":633,"y":453},{"type":"mousemove","time":37772,"x":653,"y":454},{"type":"mousedown","time":37790,"x":653,"y":454},{"type":"mouseup","time":37921,"x":653,"y":454},{"time":37922,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":38257,"x":653,"y":454},{"type":"mousemove","time":38464,"x":635,"y":518},{"type":"mousemove","time":38672,"x":654,"y":472},{"type":"mousemove","time":38872,"x":648,"y":477},{"type":"mousedown","time":38955,"x":654,"y":482},{"type":"mouseup","time":39037,"x":654,"y":482},{"time":39038,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39080,"x":654,"y":482},{"type":"mousemove","time":39289,"x":387,"y":520},{"type":"mousedown","time":39405,"x":361,"y":520},{"type":"mousemove","time":39497,"x":361,"y":520},{"type":"mouseup","time":39522,"x":361,"y":520},{"time":39523,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":39588,"x":361,"y":520},{"type":"mouseup","time":39704,"x":361,"y":520},{"time":39705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39855,"x":362,"y":520},{"type":"mousemove","time":40055,"x":367,"y":518},{"type":"mousemove","time":40261,"x":403,"y":449},{"type":"mousemove","time":40472,"x":425,"y":458},{"type":"mousedown","time":40654,"x":450,"y":483},{"type":"mousemove","time":40717,"x":450,"y":483},{"type":"mouseup","time":40780,"x":450,"y":483},{"time":40781,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40888,"x":450,"y":483},{"type":"mousemove","time":41098,"x":510,"y":269},{"type":"mousemove","time":41272,"x":510,"y":265},{"type":"mousemove","time":41473,"x":452,"y":110},{"type":"mousemove","time":41673,"x":453,"y":95},{"type":"mousemove","time":41873,"x":628,"y":222},{"type":"mousemove","time":42073,"x":449,"y":298},{"type":"mousemove","time":42273,"x":482,"y":393},{"type":"mousedown","time":42332,"x":482,"y":396},{"type":"mouseup","time":42438,"x":482,"y":397},{"time":42439,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42483,"x":482,"y":378},{"type":"mousemove","time":42688,"x":475,"y":141},{"type":"mousemove","time":42890,"x":456,"y":101},{"type":"mousemove","time":43101,"x":446,"y":77},{"type":"mousedown","time":43181,"x":444,"y":72},{"type":"mouseup","time":43254,"x":444,"y":72},{"time":43255,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":43316,"x":444,"y":72},{"type":"mousemove","time":43522,"x":424,"y":201},{"type":"mousemove","time":43722,"x":426,"y":410},{"type":"mousemove","time":43923,"x":586,"y":392},{"type":"mousemove","time":44136,"x":633,"y":346},{"type":"mousemove","time":44339,"x":664,"y":295},{"type":"mousedown","time":44362,"x":664,"y":295},{"type":"mouseup","time":44487,"x":664,"y":295},{"time":44488,"delay":400,"type":"screenshot-auto"}],"scrollY":111,"scrollX":0,"timestamp":1748966463125},{"name":"Action 2","ops":[{"type":"mousemove","time":425,"x":540,"y":112},{"type":"mousemove","time":633,"x":478,"y":146},{"type":"mousemove","time":841,"x":374,"y":201},{"type":"mousemove","time":1041,"x":372,"y":197},{"type":"mousedown","time":1307,"x":372,"y":197},{"type":"mousemove","time":1323,"x":373,"y":199},{"type":"mousemove","time":1524,"x":383,"y":224},{"type":"mousemove","time":1724,"x":390,"y":228},{"type":"mousemove","time":1924,"x":468,"y":229},{"type":"mousemove","time":2124,"x":498,"y":227},{"type":"mousemove","time":2324,"x":566,"y":230},{"type":"mousemove","time":2531,"x":561,"y":231},{"type":"mousemove","time":2557,"x":557,"y":232},{"type":"mousemove","time":2758,"x":468,"y":333},{"type":"mousemove","time":2970,"x":450,"y":344},{"type":"mousemove","time":3174,"x":438,"y":295},{"type":"mousemove","time":3374,"x":380,"y":197},{"type":"mousemove","time":3582,"x":380,"y":197},{"type":"mouseup","time":3799,"x":380,"y":197},{"time":3800,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3812,"x":392,"y":197},{"type":"mousemove","time":4018,"x":446,"y":188},{"type":"mousedown","time":4332,"x":446,"y":188},{"type":"mousemove","time":4344,"x":446,"y":189},{"type":"mousemove","time":4555,"x":450,"y":206},{"type":"mousemove","time":4757,"x":459,"y":221},{"type":"mousemove","time":4958,"x":322,"y":372},{"type":"mousemove","time":5158,"x":358,"y":299},{"type":"mousemove","time":5366,"x":167,"y":373},{"type":"mousemove","time":5574,"x":396,"y":295},{"type":"mousemove","time":5774,"x":718,"y":250},{"type":"mousemove","time":5975,"x":799,"y":229},{"type":"mousemove","time":6225,"x":790,"y":226},{"type":"mousemove","time":6433,"x":748,"y":235},{"type":"mousemove","time":6649,"x":751,"y":235},{"type":"mouseup","time":6766,"x":751,"y":235},{"time":6767,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6780,"x":598,"y":236},{"type":"mousemove","time":6984,"x":160,"y":324},{"type":"mousedown","time":7117,"x":139,"y":329},{"type":"mousemove","time":7200,"x":139,"y":329},{"type":"mouseup","time":7384,"x":115,"y":330},{"time":7385,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7516,"x":112,"y":330},{"type":"mousedown","time":7667,"x":112,"y":330},{"type":"mousemove","time":7774,"x":112,"y":330},{"type":"mouseup","time":7850,"x":100,"y":330},{"time":7851,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7983,"x":100,"y":329},{"type":"mousedown","time":8067,"x":100,"y":329},{"type":"mousemove","time":8076,"x":102,"y":329},{"type":"mousemove","time":8284,"x":177,"y":374},{"type":"mouseup","time":8417,"x":177,"y":374},{"time":8418,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8427,"x":191,"y":374},{"type":"mousemove","time":8635,"x":347,"y":403},{"type":"mousemove","time":8843,"x":329,"y":271},{"type":"mousemove","time":9050,"x":339,"y":313},{"type":"mousedown","time":9267,"x":339,"y":313},{"type":"mousemove","time":9281,"x":341,"y":314},{"type":"mousemove","time":9489,"x":587,"y":500},{"type":"mousemove","time":9691,"x":639,"y":524},{"type":"mousemove","time":9905,"x":732,"y":584},{"type":"mousemove","time":10025,"x":672,"y":581},{"type":"mousemove","time":10226,"x":240,"y":496},{"type":"mousemove","time":10434,"x":260,"y":481},{"type":"mousemove","time":10492,"x":263,"y":477},{"type":"mousemove","time":10695,"x":181,"y":72},{"type":"mousemove","time":10923,"x":348,"y":-1},{"type":"mousemove","time":11018,"x":477,"y":6},{"type":"mousemove","time":11229,"x":610,"y":107},{"type":"mousemove","time":11439,"x":1,"y":347},{"type":"mousemove","time":11741,"x":20,"y":453},{"type":"mousemove","time":11942,"x":89,"y":416},{"type":"mousemove","time":12142,"x":106,"y":399},{"type":"mousemove","time":12349,"x":108,"y":397},{"type":"mouseup","time":12450,"x":108,"y":397},{"time":12451,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12464,"x":129,"y":392},{"type":"mousedown","time":12602,"x":161,"y":389},{"type":"mousemove","time":12667,"x":161,"y":389},{"type":"mousemove","time":12875,"x":274,"y":389},{"type":"mousemove","time":13076,"x":346,"y":408},{"type":"mouseup","time":13194,"x":350,"y":408},{"time":13195,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13284,"x":350,"y":408},{"type":"mousedown","time":13467,"x":367,"y":410},{"type":"mousemove","time":13582,"x":368,"y":410},{"type":"mouseup","time":13641,"x":368,"y":410},{"time":13642,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":13950,"x":368,"y":410},{"type":"mousemove","time":13963,"x":374,"y":410},{"type":"mousemove","time":14171,"x":605,"y":438},{"type":"mousemove","time":14384,"x":604,"y":439},{"type":"mouseup","time":14591,"x":604,"y":439},{"time":14592,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14606,"x":617,"y":419},{"type":"mousemove","time":14807,"x":516,"y":328},{"type":"mousemove","time":15009,"x":422,"y":235},{"type":"mousemove","time":15216,"x":417,"y":227},{"type":"mousedown","time":15301,"x":417,"y":227},{"type":"mousemove","time":15314,"x":418,"y":228},{"type":"mousemove","time":15522,"x":490,"y":310},{"type":"mousemove","time":15724,"x":506,"y":413},{"type":"mousemove","time":15924,"x":505,"y":421},{"type":"mousemove","time":16125,"x":503,"y":418},{"type":"mouseup","time":16201,"x":503,"y":418},{"time":16202,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16213,"x":549,"y":386},{"type":"mousedown","time":16418,"x":666,"y":308},{"type":"mousemove","time":16431,"x":666,"y":308},{"type":"mouseup","time":16524,"x":666,"y":308},{"time":16525,"delay":400,"type":"screenshot-auto"}],"scrollY":111,"scrollX":0,"timestamp":1748966552406}] \ No newline at end of file From 069e686018663fb25216b39283194a2a464a2805 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 18:56:35 +0800 Subject: [PATCH 35/49] test: temporarily change zrender dep. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index adc3cb2583..e9f7c5f7f2 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "tslib": "2.3.0", - "zrender": "github:ecomfe/zrender#v6" + "zrender": "github:ecomfe/zrender#feat/ignore-host-silent" }, "devDependencies": { "@babel/code-frame": "7.10.4", From 09cdac9f0a2c1f570ce832844da42b674489ce8a Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 21:00:46 +0800 Subject: [PATCH 36/49] feat(matrix): Add clipPath for text. --- src/component/matrix/MatrixView.ts | 63 +++++++++++++++++------------- test/matrix3.html | 12 ++++++ 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index 7cef55cb39..e652a01d63 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -32,7 +32,7 @@ import { subPixelOptimize } from 'zrender/src/graphic/helper/subPixelOptimize'; import { Group, Text, Rect, Line, XY, setTooltipConfig, expandOrShrinkRect } from '../../util/graphic'; import { ListIterator } from '../../util/model'; import { clone, retrieve2 } from 'zrender/src/core/util'; -import { createNaNRectLike } from '../../coord/matrix/matrixCoordHelper'; +import { invert } from 'zrender/src/core/matrix'; import { MatrixBodyCorner, MatrixBodyOrCornerKind } from '../../coord/matrix/MatrixBodyCorner'; import { setLabelStyle } from '../../label/labelStyle'; @@ -271,7 +271,6 @@ function createMatrixCell( (cellOption && cellOption.itemStyle) ? zrCellDefault.special : zrCellDefault.normal ); const tooltipOptionShow = tooltipOption && tooltipOption.show; - let labelShown = false; const cellRect = createMatrixRect(shape, _tmpCellItemStyleModel.getItemStyle(), z2); group.add(cellRect); @@ -286,28 +285,39 @@ function createMatrixCell( const text = textValue + ''; _tmpCellLabelModel.option = cellOption ? cellOption.label : null; _tmpCellLabelModel.parentModel = parentLabelModel; - if (_tmpCellLabelModel.getShallow('show')) { - labelShown = true; - - BoundingRect.copy(_tmpContentRect, shape); - const lineWidth = cellRect.style?.lineWidth || 0; - // `lineWidth` is half outside half inside the bounding rect. - expandOrShrinkRect(_tmpContentRect, lineWidth / 2, true, true); - - setLabelStyle( - cellRect, - // Currently do not support other states (`emphasis`, `select`, `blur`) - {normal: _tmpCellLabelModel}, - { - defaultText: text, - autoOverflowArea: true, - // By default based on boundingRect. But boundingRect contains borderWidth, - // and borderWidth is half outside the cell. Thus specific `layoutRect` explicitly. - layoutRect: clone(cellRect.shape) - }, - ); - cellText = cellRect.getTextContent(); + + setLabelStyle( + cellRect, + // Currently do not support other states (`emphasis`, `select`, `blur`) + {normal: _tmpCellLabelModel}, + { + defaultText: text, + autoOverflowArea: true, + // By default based on boundingRect. But boundingRect contains borderWidth, + // and borderWidth is half outside the cell. Thus specific `layoutRect` explicitly. + layoutRect: clone(cellRect.shape) + }, + ); + cellText = cellRect.getTextContent(); + if (cellText) { + cellText.z2 = z2 + 1; + + const style = cellText.style; + if (style && (style.overflow && style.overflow !== 'none' && style.lineOverflow)) { + // `overflow: 'break'/'breakAll'/'truncate'` does not guarantee prevention of overflow + // when space is insufficient. Use a `clipPath` in such case. + const clipShape = {} as RectLike; + BoundingRect.copy(clipShape, shape); + // `lineWidth` is half outside half inside the bounding rect. + expandOrShrinkRect(clipShape, (cellRect.style?.lineWidth || 0) / 2, true, true); + cellRect.updateInnerText(); + cellText.getLocalTransform(_tmpInnerTextTrans); + invert(_tmpInnerTextTrans, _tmpInnerTextTrans); + BoundingRect.applyTransform(clipShape, clipShape, _tmpInnerTextTrans); + cellText.setClipPath(new Rect({shape: clipShape})); + } } + setTooltipConfig({ // At least for text overflow. el: cellRect, componentModel: matrixModel, @@ -319,13 +329,13 @@ function createMatrixCell( }); } + // Set silent if (cellText) { - cellText.z2 = z2 + 1; let labelSilent = _tmpCellLabelModel.get('silent'); // auto, tooltip of text cells need silient: false, but non-text cells // do not need a special cursor in most cases. if (labelSilent == null) { - labelSilent = !(tooltipOptionShow && labelShown); + labelSilent = !tooltipOptionShow; } cellText.silent = labelSilent; cellText.ignoreHostSilent = true; @@ -343,8 +353,7 @@ function createMatrixCell( const _tmpCellModel = new Model(); const _tmpCellItemStyleModel = new Model(); const _tmpCellLabelModel = new Model(); -const _tmpContentRect = createNaNRectLike(); - +const _tmpInnerTextTrans: number[] = []; function createMatrixRect( shape: RectShape, style: ItemStyleProps, z2: number diff --git a/test/matrix3.html b/test/matrix3.html index 68ad46a081..5c7a33bc62 100644 --- a/test/matrix3.html +++ b/test/matrix3.html @@ -1006,6 +1006,7 @@ var LARGE_TEXT_EN = `Thus, we have to figure out what kind of exist brexit isMoreover, for many years, people with brexit have been viewed as different. It is disappointing that, for me, the emergent rise of brexit may change my life. By contrasts, what could we do anyway to ahchieve brexit. Chiefly, rebecca West told us that, the trouble about man is twofold. He cannot learn truths which are too complicated; he forgets truths which are too simple. That solved my problem. It is important to note that If we think about it from a different point of view, for example, friedrich Nietzsche stated in his book that, i have forgotten my umbrella.  That aroused my imagination. Similarly, with these questions, let's take a closer look to brexit. Therefore, we generally say, if we grasped the key of the problem of brexit, everthing else will be easily solved. With respect to We have been thinking about brexit for a long time.Above all, under this inevitable circumstance situation, we have to solve brexit. It is important to note that As this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. Above all, does the government provide help for brexit? Besides, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Similarly, the evidence presented in this assignment has shown that, in particular, david Mamet concluded that, it's only words.. unless they're true. That inspired me. However, brexit has become increasingly evident among teens according to many scientists, however, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. In addition, for me, the emergent rise of brexit may change my life. On the other hand, brexit continues to be a relevant controversial issue in society today, as In comparison, what should we do to give rise to brexit, and what should we do to prevent brexit's happening. Alternatively, the problem lies in the difference of people raised by brexit, on the contrary, what is the key point of brexit? To look at another way, the evidence presented in this assignment has shown that, to look at another way, for brexit, the key point is how to express brexit. Furthermore, why does brexit happen? It is important to note that What is the key point of brexit? Chiefly, according to Orson Scott Card, eko brushed a tear from her eye, and Immo jeered at her, but father held up a hand. "Never mock a tender heart," he said. That solved my problem. To look at another way, if we think about it from a different point of view, alternatively, what is the key point of brexit? Furthermore, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. It is important to note that We have been thinking about brexit for a long time.Since that is so, for me, the emergent rise of brexit may change my life. Thus, we have to face an embarrassing situation, that is, in that case, now, solving brexit is becoming a more and more significant issue, in fact, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. To look at another way, when facing this difficult choice of brexit, i rarely slept well. By contrasts, if we think about it from a different point of view, as in the following example, rebecca McClanahan argued that, the shortest distance between two points is always under construction. Which enlighten me. With respect to If brexit takes place in our daliy life, we have to consider its consequence. In that case, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Besides, as far as I know, on the other hand, in general, however, brexit continues to be a relevant controversial issue in society today, as Thus, under this inevitable circumstance situation, we have to solve brexit. It is disappointing that, as far as I know, it is disappointing that, the problem lies in the difference of people raised by brexit, similarly, after seeing this evidence, there is no way we can agree with brexit. In other words, we have been thinking about brexit for a long time.In comparison, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. On the other hand, brexit has become increasingly evident among teens according to many scientists, alternatively, more current studies of people who take brexit heavely show no evidence of emotional damage. On the one hand, from my piont of view, brexit means a lot to me, and In particular, rebecca West told us that, the trouble about man is twofold. He cannot learn truths which are too complicated; he forgets truths which are too simple. Which enlighten me. In particular, according to Ljupka Cvetanova, smile and the world will laugh at you. That inspired me. In addition, several studies of the effects of brexit on the human systems have failed to demonstrate these effects. Namely, steve Wozniak told us that, never trust a computer you can't throw out a window. That solved my problem. Besides, why does brexit happen? Since that is so, for me, the emergent rise of brexit may change my life. On the one hand, in my opinion, in fact, what is the key point of brexit? Above all, when facing this difficult choice of brexit, i rarely slept well. Another way of viewing this is, modern vehement arguments against brexit alone become suspect. As in the following example, according to David Mamet, it's only words.. unless they're true. That solved my problem. Similarly, why does brexit happen? Another way of viewing this is, we have to face an embarrassing situation, that is, in that case, brexit continues to be a relevant controversial issue in society today, as By contrasts, in general, on the one hand, we generally say, if we grasped the key of the problem of brexit, everthing else will be easily solved. In fact, now, solving brexit is becoming a more and more significant issue, above all, with these questions, let's take a closer look to brexit. However, what is the key point of brexit? In comparison, brexit is a common condition among civilians in today’s society, so, thus, after seeing this evidence, there is no way we can agree with brexit. With respect to we have to face an embarrassing situation, that is, it is disappointing that, what could we do anyway to ahchieve brexit. It is important to note that When facing this difficult choice of brexit, i rarely slept well. In particular, kamand Kojouri showed us that, what you choose also chooses you. That solved my problem. By contrasts, some people may disagree with brexit, by contrasts, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. Since that is so, more current studies of people who take brexit heavely show no evidence of emotional damage. Alternatively, for brexit, the key point is how to express brexit. Since that is so, what if brexit happens, and what if brexit does not happen. Since that is so, every person has to face these problems caused by brexit, when facing these problems, in fact, more current studies of people who take brexit heavely show no evidence of emotional damage. To look at another way, we have to face an embarrassing situation, that is, in that case, the problem lies in the difference of people raised by brexit, similarly, is inevitable. In fact, for me, the emergent rise of brexit may change my life. In comparison, what should we do to give rise to brexit, and what should we do to prevent brexit's happening. It is important to note that As this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. It is important to note that brexit has become increasingly evident among teens according to many scientists, that is to say, james Thurber mentioned that, all human beings should try to learn before they die what they are running from, and to, and why. That solved my problem. Similarly, the consequece of brexit is of great significance to me, and to many other people. Chiefly, to quote from William Penn, all Excess is ill: but Drunkenness is of the worst Sort. It spoils Health, dismounts the Mind, and unmans Men: it reveals Secrets, is Quarrelsome, lascivious, impudent, dangerous and Mad. In fine, he that is drunk is not a Man: because he is so long void of Reason, that distinguishes a Man from a Beast. That inspired me. Therefore, with these questions, let's take a closer look to brexit. Since that is so, as far as I know, thus, for me, the emergent rise of brexit may change my life. Thus, for brexit, the key point is how to express brexit. Above all, now, solving brexit is becoming a more and more significant issue, in fact, in my opinion, furthermore, is inevitable. Namely, plato, symposium / Phaedrus stated in his book that, there is truth in wine and children. That inspired me. Above all, as this subject continues to be looked down on people must realize that brexit are becoming more common in today’s world. In fact, what is the key point of brexit? On the one hand, but these are not the most urgent issue, a more pressing issue about brexit is, alternatively, the problem lies in the difference of people raised by brexit, on the other hand, but these are not the most urgent issue, a more pressing issue about brexit is, in addition, the problem lies in the difference of people raised by brexit, thus, if brexit takes place in our daliy life, we have to consider its consequence. Another possibility is, to a certain extent, brexit is right. That is to say, james Thurber showed us that, all human beings should try to learn before they die what they are running from, and to, and why. Which enlighten me. In fact, in my opinion, namely, to quote from Comte de Lautréamont, we say sound things when we do not strive to say to say extraordinary ones. Which brought a new way of thinking it. `; var _xDataAItem = {value: 'a'}; var _xData = [ + {value: 'e', size: 20}, _xDataAItem, {value: LARGE_TEXT_EN}, // 'c' @@ -1019,12 +1020,23 @@ value: 'The quick brown fox jumps over the lazy dog.', label: {color: 'red'}, }; + var _bodyCellCase2 = { + coord: ['a', 'r'], + value: 'The quick {aa|brown} fox {bb|jumps} over the lazy dog.', + label: { + rich: { + aa: {fontSize: 26, color: '#fff', textBorderWidth: 3, textBorderColor: 'green'}, + bb: {color: 'green', backgroundColor: 'rgba(200,200,0,0.3)', padding: 10}, + } + }, + }; var _bodyData = [ { coord: ['a', 's'], value: LARGE_TEXT_EN }, _bodyCellCase1, + _bodyCellCase2, ]; var dates = ["2010", '2011', '2012', '2013']; From 469ad350310a6374db414b539669497c86724542 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 9 Jun 2025 21:55:26 +0800 Subject: [PATCH 37/49] fix(test infra): fix test bug introduced in 8005bc3d41a24d06e7508d47e7b480f172ab6b1b --- test/lib/config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/lib/config.js b/test/lib/config.js index caa9826d66..bc49eefb45 100644 --- a/test/lib/config.js +++ b/test/lib/config.js @@ -55,6 +55,8 @@ var ecDistPath; if (params.__ECDIST__ && !params.__CASE_FRAME__) { ecDistPath = ({ + 'dist': '../dist/echarts', + 'dist/echarts.simple': '../dist/echarts.simple', 'webpack-req-ec': '../../echarts-boilerplate/echarts-webpack/dist/webpack-req-ec', 'webpack-req-eclibec': '../../echarts-boilerplate/echarts-webpack/dist/webpack-req-eclibec', 'webpackold-req-ec': '../../echarts-boilerplate/echarts-webpackold/dist/webpackold-req-ec', @@ -91,7 +93,7 @@ decorateGlobalRequire(); } - async function decorateGlobalRequire() { + function decorateGlobalRequire() { if (window.__ECHARTS__DEFAULT__THEME__ == null) { return; } From 1a9faac34a380557b0df7a34985f1a67098dc4f5 Mon Sep 17 00:00:00 2001 From: 100pah Date: Tue, 10 Jun 2025 16:18:29 +0800 Subject: [PATCH 38/49] chore: fix comment and TS type error. --- src/chart/heatmap/HeatmapSeries.ts | 6 +++--- src/coord/matrix/Matrix.ts | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/chart/heatmap/HeatmapSeries.ts b/src/chart/heatmap/HeatmapSeries.ts index 02e67d709a..84b0a756c5 100644 --- a/src/chart/heatmap/HeatmapSeries.ts +++ b/src/chart/heatmap/HeatmapSeries.ts @@ -51,16 +51,16 @@ export interface HeatmapStateOption { label?: SeriesLabelOption } -interface FunnelStatesMixin { +interface HeatmapStatesMixin { emphasis?: DefaultStatesMixinEmphasis } export interface HeatmapDataItemOption extends HeatmapStateOption, - StatesOptionMixin { + StatesOptionMixin { value: HeatmapDataValue } export interface HeatmapSeriesOption - extends SeriesOption, FunnelStatesMixin>, + extends SeriesOption, HeatmapStatesMixin>, HeatmapStateOption, SeriesOnCartesianOptionMixin, SeriesOnGeoOptionMixin, diff --git a/src/coord/matrix/Matrix.ts b/src/coord/matrix/Matrix.ts index 487996fd81..b974403e3a 100644 --- a/src/coord/matrix/Matrix.ts +++ b/src/coord/matrix/Matrix.ts @@ -171,10 +171,11 @@ class Matrix implements CoordinateSystem, CoordinateSystemMaster { * - The returned `out.rect` and `out.matrixXYLocatorRange` is always an object or an 2d-array, * but never be null/undefined. If it cannot be located or invalid, `NaN` is in their * corresponding number props. - * - Do not provide `out.contentRect` currently until real requirements come, because it's - * allowed to input non-leaf dimension x/y, which determines a rect covering multiple cells - * (even not merged), in which case the padding and borderWidth can not be determined to - * make a contentRect. Therefore only return `out.rect` in any case for consistency. + * - Do not provide `out.contentRect`, because it's allowed to input non-leaf dimension x/y or + * a range of x/y, which determines a rect covering multiple cells (even not merged), in which + * case the padding and borderWidth can not be determined to make a contentRect. Therefore only + * return `out.rect` in any case for consistency. The caller is responsible for adding space to + * avoid covering cell borders, if necessary. */ dataToLayout( data: MatrixCoordRangeOption[], From b2ac4939fe4db8f6e647df02740d58584528d71f Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 11 Jun 2025 02:06:46 +0800 Subject: [PATCH 39/49] feat(roam): Change cursor style to indicate the draggable area. (effect geo/insideDataZoom/treemap/sankey/graph roam) --- src/component/helper/RoamController.ts | 28 ++++++++++++++---- src/component/helper/interactionMutex.ts | 36 +++++++++++++++--------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts index e316edb32a..70cdd0c5d3 100644 --- a/src/component/helper/RoamController.ts +++ b/src/component/helper/RoamController.ts @@ -180,7 +180,7 @@ class RoamController extends Eventful { const x = e.offsetX; const y = e.offsetY; - // Only check on mosedown, but not mousemove. + // To determine dragging start, only by checking on mosedown, but not mousemove. // Mouse can be out of target when mouse moving. if (this.pointerChecker && this.pointerChecker(e, x, y)) { this._x = x; @@ -190,10 +190,9 @@ class RoamController extends Eventful { } private _mousemoveHandler(e: ZRElementEvent) { - if (!this._dragging - || !isAvailableBehavior('moveOnMouseMove', e, this._opt) - || e.gestureEvent === 'pinch' - || interactionMutex.isTaken(this._zr, 'globalPan') + const zr = this._zr; + if (e.gestureEvent === 'pinch' + || interactionMutex.isTaken(zr, 'globalPan') ) { return; } @@ -201,6 +200,25 @@ class RoamController extends Eventful { const x = e.offsetX; const y = e.offsetY; + if (!this._dragging + || !isAvailableBehavior('moveOnMouseMove', e, this._opt) + ) { + if (this.pointerChecker && this.pointerChecker(e, x, y) + // This `grab` cursor style should take the lowest precedence. If the hovring element already + // have a cursor, zrender will set it to be non-'default' before entering this handler. + && e.event && e.event.zrCursorStyle === 'default' + ) { + // To indicate users that this area is draggable, otherwise users probably cannot kwown + // that when hovering out of the shape but still inside the bounding rect. + zr.setCursorStyle('grab'); + // Do not need to set the cursor back, because in the current impl, zr is responsible + // for setting the cursor on each mousemove. + } + return; + } + + zr.setCursorStyle('grabbing'); + const oldX = this._x; const oldY = this._y; diff --git a/src/component/helper/interactionMutex.ts b/src/component/helper/interactionMutex.ts index 149e6d7f77..85695e749c 100644 --- a/src/component/helper/interactionMutex.ts +++ b/src/component/helper/interactionMutex.ts @@ -17,19 +17,30 @@ * under the License. */ -// @ts-nocheck +import { ZRenderType } from 'zrender/src/zrender'; import * as echarts from '../../core/echarts'; import { noop } from 'zrender/src/core/util'; +import { makeInner } from '../../util/model'; -const ATTR = '\0_ec_interaction_mutex'; +type InteractionMutexResource = { + globalPan: string +}; +const inner = makeInner(); -export function take(zr, resourceKey, userKey) { - const store = getStore(zr); - store[resourceKey] = userKey; +export function take( + zr: ZRenderType, + resourceKey: keyof InteractionMutexResource, + userKey: InteractionMutexResource[keyof InteractionMutexResource] +) { + inner(zr)[resourceKey] = userKey; } -export function release(zr, resourceKey, userKey) { - const store = getStore(zr); +export function release( + zr: ZRenderType, + resourceKey: keyof InteractionMutexResource, + userKey: InteractionMutexResource[keyof InteractionMutexResource] +) { + const store = inner(zr); const uKey = store[resourceKey]; if (uKey === userKey) { @@ -37,12 +48,11 @@ export function release(zr, resourceKey, userKey) { } } -export function isTaken(zr, resourceKey) { - return !!getStore(zr)[resourceKey]; -} - -function getStore(zr) { - return zr[ATTR] || (zr[ATTR] = {}); +export function isTaken( + zr: ZRenderType, + resourceKey: keyof InteractionMutexResource, +) { + return !!inner(zr)[resourceKey]; } /** From 10b4528c1483604e6570fcb215e88180c87fce05 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 12 Jun 2025 03:07:15 +0800 Subject: [PATCH 40/49] test(infra): testHelper support multiple inner range in select input. --- test/build/mktest-tpl.html | 4 ---- test/lib/testHelper.js | 33 ++++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/test/build/mktest-tpl.html b/test/build/mktest-tpl.html index 4c6d69697a..fd18c5999f 100644 --- a/test/build/mktest-tpl.html +++ b/test/build/mktest-tpl.html @@ -61,10 +61,6 @@ // 'theme/dark.js', // auto register if load. ], function (echarts /*, data */) { - // // Data can be fetched by: - // $.getJSON('./data/nutrients.json', function (data) { - // }); - var option = { xAxis: {}, yAxis: {}, diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js index fe6af2aee8..c22dfb097c 100644 --- a/test/lib/testHelper.js +++ b/test/lib/testHelper.js @@ -138,7 +138,10 @@ * type: 'range', * // ... Other properties of `range` input except `onchange` and `text`. * // When this option is not selected, the range input will be disabled. - * }} + * }}, + * // If more than one options have internal `input`, `id` (option id) must be specified. + * // It can be visited by `onchange() { if (this.optionId) {...} }`. + * {text: 'd', id: 'some_option_id', input: {...}} * ], * optionIndex: 0, // Optional. Or `valueIndex`. The initial value index. * // By default, the first option. @@ -154,7 +157,7 @@ * // They are the same: `oninput` `input` * // `onchange` `change` `onchanged` `changed` * // `onselect` `select` (capital insensitive) - * onchange: function () { console.log(this.value); } + * onchange: function () { console.log(this.value, this.optionId); } * }, * { * // Group inputs. Only one group can be displayed at a time with in a group set. @@ -904,6 +907,7 @@ var _rangeInputListener; var _rangeInputEl; var _rangeInputValueEl; + var _opSuffix = internallyForceDef && internallyForceDef.id || ''; dealInitRangeInput(); @@ -948,7 +952,7 @@ updateRangeInputViewValue(_currVal); dispatchRangeInputChangedEvent(); }, - op: btnName + op: btnName + _opSuffix })); } createRangeInputDeltaBtn('decrease', -_step); @@ -964,7 +968,7 @@ updateRangeInputViewValue(_currVal); dispatchRangeInputChangedEvent(); }, - op: 'slide', + op: 'slide' + _opSuffix, createRecordArgs: function () { return [+this.value]; }, @@ -1055,6 +1059,7 @@ ' options: [', ' {text?: string, value: any},', ' {text?: string, input: {type: "range", ...}},', + ' {text?: string, id: "some_option_id", input: {type: "range", ...}},', ' ...,', ' ],', ' onchange() { ... },', @@ -1131,6 +1136,7 @@ // (value can be null/undefined/array/object/... everything). // Convinient but might cause ambiguity when a value happens to be {text, value}, but rarely happen. if (inputDefine.options) { + var innerInputCount = 0; for (var optionIdx = 0; optionIdx < inputDefine.options.length; optionIdx++) { var optionDef = inputDefine.options[optionIdx]; assert(isObject(optionDef), [ @@ -1148,8 +1154,17 @@ selectCtx._optionList.push({ value: optionDef.value, input: optionDef.input, + id: optionDef.id, text: text }); + if (optionDef.input) { + innerInputCount++; + } + assert(innerInputCount < 2 || optionDef.id != null, [ + errMsgPrefix + ' If more than one inner input in a select,' + + ' option id must be specified. ', + _SAMPLE_SELECT_DEFINITION + ].join('\n')); } } else if (inputDefine.values) { @@ -1182,6 +1197,7 @@ } var rangeInputCreated = createRangeInput(optionDef.input, { text: '', + id: optionDef.id, onchange: function () { if (selectCtx._disabled) { return; } triggerUserSelectChangedEvent(); @@ -1281,7 +1297,9 @@ else if (inputDefine.hasOwnProperty('value')) { var found = false; for (var idx = 0; idx < selectCtx._optionList.length; idx++) { - if (!selectCtx._optionList[idx].input && selectCtx._optionList[idx].value === inputDefine.value) { + if (!selectCtx._optionList[idx].input + && selectCtx._optionList[idx].value === inputDefine.value + ) { found = true; initOptionIdx = idx; } @@ -1311,7 +1329,8 @@ function triggerUserSelectChangedEvent() { var optionIdx = getSelectInputOptionIndex(); var value = getSelectInputValueByOptionIndex(optionIdx); - var target = {value: value}; + var optionId = selectCtx._optionList[optionIdx].id; + var target = {value: value, optionId: optionId}; _selectListener.call(target, {target: target}); } @@ -1790,7 +1809,7 @@ theme = window.__ECHARTS__DEFAULT__THEME__; } if (theme) { - require([`theme/${theme}`]); + require(['theme/' + theme]); } var chart = echarts.init(dom, theme, { From 1465e18d96f71110ae60f8e14496bcdc05c4b41c Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 12 Jun 2025 15:52:31 +0800 Subject: [PATCH 41/49] feat(matrix): use tokens for color --- src/coord/matrix/MatrixModel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coord/matrix/MatrixModel.ts b/src/coord/matrix/MatrixModel.ts index 810efc7dc2..411f1d2f92 100644 --- a/src/coord/matrix/MatrixModel.ts +++ b/src/coord/matrix/MatrixModel.ts @@ -30,6 +30,7 @@ import Matrix from './Matrix'; import { MatrixDim, MatrixXYLocator } from './MatrixDim'; import { MatrixBodyCorner } from './MatrixBodyCorner'; import { CoordinateSystemHostModel } from '../CoordinateSystem'; +import tokens from '../../visual/tokens'; export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin { @@ -213,7 +214,7 @@ export interface MatrixTooltipFormatterParams { const defaultLabelOption: LabelOption = { show: true, - color: '#333', + color: tokens.color.secondary, // overflow: 'truncate', overflow: 'break', lineOverflow: 'truncate', @@ -225,7 +226,7 @@ function makeDefaultCellItemStyleOption(isCorner: boolean) { return { color: 'none', borderWidth: 1, - borderColor: isCorner ? 'none' : '#ccc', + borderColor: isCorner ? 'none' : tokens.color.borderTint, }; }; const defaultDimOption: MatrixDimensionOption = { @@ -235,7 +236,7 @@ const defaultDimOption: MatrixDimensionOption = { silent: undefined, dividerLineStyle: { width: 1, - color: '#aaa', + color: tokens.color.border, }, }; const defaultBodyOption: MatrixBodyOption = { @@ -262,7 +263,7 @@ const defaultMatrixOption: MatrixOption = { corner: defaultCornerOption, backgroundStyle: { color: 'none', - borderColor: '#aaa', + borderColor: tokens.color.axisLine, borderWidth: 1, }, }; From 72cf70d2e3f84e7bb8fd2c5f975c2adf6bfb53f5 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 12 Jun 2025 15:58:28 +0800 Subject: [PATCH 42/49] test: Support suffix to range input --- test/lib/testHelper.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js index c22dfb097c..80cfeb16ca 100644 --- a/test/lib/testHelper.js +++ b/test/lib/testHelper.js @@ -108,6 +108,7 @@ * max: 100, // Optional * value: 30, // Optional. Must be a number. * step: 1, // Optional + * suffix: '%', // Optional. e.g., '%' means the number is displayed as '33%' * disabled: false, // Optional. * prevent: { // Optional. * recordInputs: false, // Optional. @@ -996,7 +997,7 @@ function updateRangeInputViewValue(newVal) { _rangeInputEl.value = +newVal; - _rangeInputValueEl.innerHTML = encodeHTML(newVal + ''); + _rangeInputValueEl.innerHTML = encodeHTML(newVal + '' + (inputDefine.suffix || '')); } function resetRangeInputWrapperCSS(wrapperEl, disabled) { wrapperEl.className = 'test-inputs-slider' From 0b9b08a0f9ff4d8a3c3ebcd10446eb53411b5fb2 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 12 Jun 2025 16:22:43 +0800 Subject: [PATCH 43/49] test: Add resize cb. --- test/lib/draggable.js | 8 +++++++- test/lib/testHelper.js | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/test/lib/draggable.js b/test/lib/draggable.js index c5bf49d8cd..01741ef2a3 100644 --- a/test/lib/draggable.js +++ b/test/lib/draggable.js @@ -39,12 +39,18 @@ * @param {boolean} [opt.lockY=false] * @param {number} [opt.throttle=false] * @param {Function} [opt.onDrag] + * @param {Function} [opt.onResize] * @return {type} description */ init: function (mainEl, chart, opt) { opt = opt || {}; - var onDrag = opt.onDrag || $.proxy(chart.resize, chart); + var onDrag = opt.onDrag || function () { + chart.resize(); + if (opt.onResize) { + opt.onResize(); + } + } var onDragThrottled = chart ? onDrag : function () {}; if (opt.throttle) { diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js index 80cfeb16ca..0b748e53cf 100644 --- a/test/lib/testHelper.js +++ b/test/lib/testHelper.js @@ -66,6 +66,7 @@ * @param {boolean} [opt.notMerge] Optional. `chart.setOption(option, {norMerge});` * @param {boolean} [opt.lazyUpdate] Optional. `chart.setOption(option, {lazyUpdate});` * @param {boolean} [opt.autoResize=true] Optional. Enable chart auto response to window resize. + * @param {Function} [opt.onResize] Optional. Available when `opt.autoResize` or `opt.draggable` is true. * @param {string} [opt.renderer] Optional. 'canvas' or 'svg'. DO NOT set it in formmal test cases; * leave it controlled by __ECHARTS__DEFAULT__RENDERER__ for visual testing. * @@ -1826,7 +1827,7 @@ + '' ); } - window.draggable.init(dom, chart, {throttle: 70}); + window.draggable.init(dom, chart, {throttle: 70, onResize: opt.onResize}); } option && chart.setOption(option, { @@ -1836,7 +1837,7 @@ var isAutoResize = opt.autoResize == null ? true : opt.autoResize; if (isAutoResize) { - testHelper.resizable(chart); + testHelper.resizable(chart, {onResize: opt.onResize}); } return chart; @@ -2010,7 +2011,8 @@ } } - testHelper.resizable = function (chart) { + testHelper.resizable = function (chart, opt) { + opt = opt || {}; var dom = chart.getDom(); var width = dom.clientWidth; var height = dom.clientHeight; @@ -2024,6 +2026,10 @@ } width = newWidth; height = newHeight; + + if (opt.onResize) { + opt.onResize(); + } } } if (window.attachEvent) { From 0661c799d34b3cf5b83109dacf1df0b317650cee Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 12 Jun 2025 17:19:08 +0800 Subject: [PATCH 44/49] fix(graph): #16904 introcuded percentage string here, such as '33%'. But it was based on canvas width/height, which is not reasonable - the unit may incorrect, and it is unpredictable if the `View['_rect']` is not calculated based on the current canvas rect. Therefore the percentage value is changed to based on `View['_rect'].width/height` since v6. Under this definition, users can use '0%' to map the top-left of `View['_rect']` to the center of `View['_viewRect']`. --- src/action/roamHelper.ts | 4 +- src/chart/graph/createView.ts | 7 +- src/chart/sankey/SankeyView.ts | 2 +- src/chart/tree/TreeView.ts | 2 +- src/coord/View.ts | 59 ++++-- src/util/number.ts | 8 +- src/util/types.ts | 2 + test/graph-layout-roam.html | 338 +++++++++++++++++++++++++++++++++ 8 files changed, 396 insertions(+), 26 deletions(-) create mode 100644 test/graph-layout-roam.html diff --git a/src/action/roamHelper.ts b/src/action/roamHelper.ts index f14c8ccfbe..c82e443b16 100644 --- a/src/action/roamHelper.ts +++ b/src/action/roamHelper.ts @@ -58,7 +58,7 @@ export function updateCenterAndZoom( point[0] -= payload.dx; point[1] -= payload.dy; - view.setCenter(getCenterCoord(view, point), api); + view.setCenter(getCenterCoord(view, point)); } if (zoom != null) { if (zoomLimit) { @@ -81,7 +81,7 @@ export function updateCenterAndZoom( view.updateTransform(); // Get the new center - view.setCenter(getCenterCoord(view, point), api); + view.setCenter(getCenterCoord(view, point)); view.setZoom(zoom * previousZoom); } diff --git a/src/chart/graph/createView.ts b/src/chart/graph/createView.ts index 90b7b2b530..e753823715 100644 --- a/src/chart/graph/createView.ts +++ b/src/chart/graph/createView.ts @@ -79,9 +79,6 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA const bbWidth = max[0] - min[0]; const bbHeight = max[1] - min[1]; - const viewWidth = viewRect.width; - const viewHeight = viewRect.height; - const viewCoordSys = new View(); viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); @@ -89,11 +86,11 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA min[0], min[1], bbWidth, bbHeight ); viewCoordSys.setViewRect( - viewRect.x, viewRect.y, viewWidth, viewHeight + viewRect.x, viewRect.y, viewRect.width, viewRect.height ); // Update roam info - viewCoordSys.setCenter(seriesModel.get('center'), api); + viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel}); viewCoordSys.setZoom(seriesModel.get('zoom')); viewList.push(viewCoordSys); diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts index eb78454852..2ae4189695 100644 --- a/src/chart/sankey/SankeyView.ts +++ b/src/chart/sankey/SankeyView.ts @@ -376,7 +376,7 @@ class SankeyView extends ChartView { viewCoordSys.setBoundingRect(0, 0, width, height); - viewCoordSys.setCenter(seriesModel.get('center'), api); + viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel: seriesModel.ecModel}); viewCoordSys.setZoom(seriesModel.get('zoom')); this._mainGroup.x = layoutInfo.x; diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index 86110326fe..cd82fd8e63 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -257,7 +257,7 @@ class TreeView extends ChartView { viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]); - viewCoordSys.setCenter(seriesModel.get('center'), api); + viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel: seriesModel.ecModel}); viewCoordSys.setZoom(seriesModel.get('zoom')); // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group diff --git a/src/coord/View.ts b/src/coord/View.ts index 43d02ee8d3..fdbb6beb08 100644 --- a/src/coord/View.ts +++ b/src/coord/View.ts @@ -30,7 +30,9 @@ import { CoordinateSystemMaster, CoordinateSystem } from './CoordinateSystem'; import GlobalModel from '../model/Global'; import { ParsedModelFinder, ParsedModelFinderKnown } from '../util/model'; import { parsePercent } from '../util/number'; -import type ExtensionAPI from '../core/ExtensionAPI'; +import { RoamOptionMixin } from '../util/types'; +import { clone } from 'zrender/src/core/util'; +import ExtensionAPI from '../core/ExtensionAPI'; const v2ApplyTransform = vector.applyTransform; @@ -65,17 +67,20 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy /** * This is a user specified point on the source, which will be * located to the center of the `View['_viewRect']`. - * The unit this the same as `View['_rect']`. + * The unit and the origin of this point is the same as that of `[View['_rect']`. */ private _center: number[]; + private _centerOption: RoamOptionMixin['center']; + private _zoom: number; /** * The rect of the source, where the measure is used by "data" and "center". * Has nothing to do with roam/zoom. * The unit is defined by the source. For example, - * for geo source the unit is lat/lng, - * for SVG source the unit is the same as the width/height defined in SVG. + * - for geo source the unit is lat/lng, + * - for SVG source the unit is the same as the width/height defined in SVG. + * - for series.graph/series.tree/series.sankey the uiit is px. */ private _rect: BoundingRect; /** @@ -91,6 +96,7 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy setBoundingRect(x: number, y: number, width: number, height: number): BoundingRect { this._rect = new BoundingRect(x, y, width, height); + this._updateCenterAndZoom(); return this._rect; } @@ -126,16 +132,33 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy } /** - * Set center of view + * [NOTICE] + * The definition of this center has always been irrelevant to some other series center like + * 'series-pie.center' - this center is a point on the same coord sys as `View['_rect'].x/y`, + * rather than canvas viewport, and the unit is not necessarily pixel (e.g., in geo case). + * @see {View['_center']} for details. */ - setCenter(centerCoord: (number | string)[], api: ExtensionAPI): void { - if (!centerCoord) { - return; + setCenter( + centerCoord: RoamOptionMixin['center'], + opt?: { + // Only for backward compat. + ecModel?: GlobalModel + api?: ExtensionAPI + } + ): void { + // #16904 introcuded percentage string here, such as '33%'. But it was based on canvas + // width/height, which is not reasonable - the unit may incorrect, and it is unpredictable if + // the `View['_rect']` is not calculated based on the current canvas rect. Therefore the percentage + // value is changed to based on `View['_rect'].width/height` since v6. Under this definition, users + // can use '0%' to map the top-left of `View['_rect']` to the center of `View['_viewRect']`. + if (opt && opt.api && opt.ecModel && opt.ecModel.getShallow('legacyViewCoordSysCenterBase') && centerCoord) { + centerCoord = [ + parsePercent(centerCoord[0], opt.api.getWidth()), + parsePercent(centerCoord[1], opt.api.getWidth()) + ]; } - this._center = [ - parsePercent(centerCoord[0], api.getWidth()), - parsePercent(centerCoord[1], api.getHeight()) - ]; + + this._centerOption = clone(centerCoord); this._updateCenterAndZoom(); } @@ -181,9 +204,19 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy } /** - * Remove roam + * Ensure this method is idempotent, since it should be called when + * every relevant prop (e.g. _centerOption/_zoom/_rect/_viewRect) changed. */ private _updateCenterAndZoom(): void { + const centerOption = this._centerOption; + const rect = this._rect; + if (centerOption && rect) { + this._center = [ + parsePercent(centerOption[0], rect.width, rect.x), + parsePercent(centerOption[1], rect.height, rect.y) + ]; + } + // Must update after view transform updated const rawTransformMatrix = this._rawTransformable.getLocalTransform(); const roamTransform = this._roamTransformable; diff --git a/src/util/number.ts b/src/util/number.ts index 10053afc56..85807cb3c3 100644 --- a/src/util/number.ts +++ b/src/util/number.ts @@ -112,7 +112,7 @@ export const parsePercent = parsePositionOption; * @see {parsePositionSizeOption} and also accept a string preset. * @see {PositionSizeOption} */ -export function parsePositionOption(option: unknown, percentBase: number): number { +export function parsePositionOption(option: unknown, percentBase: number, percentOffset?: number): number { switch (option) { case 'center': case 'middle': @@ -127,7 +127,7 @@ export function parsePositionOption(option: unknown, percentBase: number): numbe option = '100%'; break; } - return parsePositionSizeOption(option, percentBase); + return parsePositionSizeOption(option, percentBase, percentOffset); } /** @@ -136,10 +136,10 @@ export function parsePositionOption(option: unknown, percentBase: number): numbe * (But allow JS type coercion (`+option`) due to backward compatibility) * @see {PositionSizeOption} */ -export function parsePositionSizeOption(option: unknown, percentBase: number): number { +export function parsePositionSizeOption(option: unknown, percentBase: number, percentOffset?: number): number { if (zrUtil.isString(option)) { if (_trim(option).match(/%$/)) { - return parseFloat(option) / 100 * percentBase; + return parseFloat(option) / 100 * percentBase + (percentOffset || 0); } return parseFloat(option); } diff --git a/src/util/types.ts b/src/util/types.ts index c763f5e21b..62c37ee365 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -708,6 +708,8 @@ export type ECUnitOption = { useUTC?: boolean hoverLayerThreshold?: number + legacyViewCoordSysCenterBase?: boolean + [key: string]: ComponentOption | ComponentOption[] | Dictionary | unknown stateAnimation?: AnimationOption diff --git a/test/graph-layout-roam.html b/test/graph-layout-roam.html new file mode 100644 index 0000000000..d1e5524090 --- /dev/null +++ b/test/graph-layout-roam.html @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file From 237badb878b0be472a578207ca5947539b0a6d10 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 16 Jun 2025 00:48:56 +0800 Subject: [PATCH 45/49] feat: (1) Support `preserveAspect` `preserveAspectAlign` `preserveAspectVerticalAlign` for series.map/geo/series.graph in box layout (lay out by left/right/top/bottom/width/height). (2) Support `clip` on geo/series.map (3) Support `roamTigger: 'global' | 'selfRect'` to switch roam area to global on only self bounding rect. (4) Support cursor style change when hovering the roaming enabled area to hint users. (5) Fix that center and zoom option does not work in series.sankey, and no relevant test case. (the roam is introduced in impl in #20321, due v6). (6) Fix the percent base of `center` (such as `center: ['40%', '50%']`) for all View coord sys based components/series (geo/series.map/series.graph/series.tree/series.sankey). #16904 introcuded percentage string here, such as '33%'. But it was based on canvas width/height, which is not reasonable - the unit may incorrect, and it is unpredictable if the `View['_rect']` is not calculated based on the current canvas rect. Therefore the percentage value is changed to based on `View['_rect'].width/height` since v6. Under this definition, users can use '0%' to map the top-left of `View['_rect']` to the center of `View['_viewRect']`. (7) Enhance the behavior for roaming area overlapping - the upper one has higher precedence. (8) Fix some `scaleLimit` missing. (9) Some refactor and clarification of `RoamController` and `roamHelper`. --- src/action/roamHelper.ts | 92 ---- src/chart/graph/GraphSeries.ts | 6 +- src/chart/graph/GraphView.ts | 36 +- src/chart/graph/Thumbnail.ts | 42 +- src/chart/graph/createView.ts | 9 +- src/chart/graph/install.ts | 4 +- src/chart/sankey/SankeySeries.ts | 1 + src/chart/sankey/SankeyView.ts | 29 +- src/chart/sankey/install.ts | 10 +- src/chart/tree/TreeSeries.ts | 1 + src/chart/tree/TreeView.ts | 15 +- src/chart/tree/treeAction.ts | 11 +- src/chart/treemap/TreemapSeries.ts | 1 + src/chart/treemap/TreemapView.ts | 44 +- src/component/axis/axisBreakHelper.ts | 2 +- src/component/dataZoom/roams.ts | 29 +- src/component/geo/install.ts | 6 +- src/component/helper/MapDraw.ts | 72 ++-- src/component/helper/RoamController.ts | 305 +++++++++++--- src/component/helper/cursorHelper.ts | 57 ++- src/component/helper/roamHelper.ts | 159 +++++-- src/component/matrix/MatrixView.ts | 3 + src/coord/View.ts | 49 +-- src/coord/geo/Geo.ts | 7 +- src/coord/geo/GeoModel.ts | 10 +- src/coord/geo/geoCreator.ts | 14 +- src/core/CoordinateSystem.ts | 10 +- src/core/echarts.ts | 5 +- src/model/Component.ts | 11 +- src/util/layout.ts | 78 +++- src/util/model.ts | 13 + src/util/types.ts | 33 ++ test/build/mktest-tpl.html | 2 +- test/geo-map-roam.html | 556 +++++++++++++++++++++++++ test/geo-svg-demo.html | 1 + test/graph-layout-roam.html | 416 ++++++++++++------ test/matrix.html | 13 +- test/matrix3.html | 22 +- test/sankey-roam.html | 220 +++++++++- test/tmp-base.html | 2 +- test/tree-roam.html | 321 +++++++++++++- test/treemap-simple2.html | 103 +++++ 42 files changed, 2289 insertions(+), 531 deletions(-) delete mode 100644 src/action/roamHelper.ts create mode 100644 test/geo-map-roam.html diff --git a/src/action/roamHelper.ts b/src/action/roamHelper.ts deleted file mode 100644 index c82e443b16..0000000000 --- a/src/action/roamHelper.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* -* 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 type Geo from '../coord/geo/Geo'; -import type View from '../coord/View'; -import type ExtensionAPI from '../core/ExtensionAPI'; -import type { Payload } from '../util/types'; - -export interface RoamPayload extends Payload { - dx: number - dy: number - zoom: number - originX: number - originY: number -} - -function getCenterCoord(view: View, point: number[]) { - // Use projected coord as center because it's linear. - return (view as Geo).pointToProjected - ? (view as Geo).pointToProjected(point) - : view.pointToData(point); -} - -export function updateCenterAndZoom( - view: View, - payload: RoamPayload, - zoomLimit?: { - min?: number, - max?: number - }, - api?: ExtensionAPI -) { - const previousZoom = view.getZoom(); - const center = view.getCenter(); - let zoom = payload.zoom; - - const point = (view as Geo).projectedToPoint - ? (view as Geo).projectedToPoint(center) - : view.dataToPoint(center); - - if (payload.dx != null && payload.dy != null) { - point[0] -= payload.dx; - point[1] -= payload.dy; - - view.setCenter(getCenterCoord(view, point)); - } - if (zoom != null) { - if (zoomLimit) { - const zoomMin = zoomLimit.min || 0; - const zoomMax = zoomLimit.max || Infinity; - zoom = Math.max( - Math.min(previousZoom * zoom, zoomMax), - zoomMin - ) / previousZoom; - } - - // Zoom on given point(originX, originY) - view.scaleX *= zoom; - view.scaleY *= zoom; - const fixX = (payload.originX - view.x) * (zoom - 1); - const fixY = (payload.originY - view.y) * (zoom - 1); - - view.x -= fixX; - view.y -= fixY; - - view.updateTransform(); - // Get the new center - view.setCenter(getCenterCoord(view, point)); - view.setZoom(zoom * previousZoom); - } - - return { - center: view.getCenter(), - zoom: view.getZoom() - }; -} diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts index 1c3c8fe628..8762238e49 100644 --- a/src/chart/graph/GraphSeries.ts +++ b/src/chart/graph/GraphSeries.ts @@ -43,7 +43,8 @@ import { GraphEdgeItemObject, OptionDataValueNumeric, CallbackDataParams, - DefaultEmphasisFocus + DefaultEmphasisFocus, + PreserveAspectMixin } from '../../util/types'; import SeriesModel from '../../model/Series'; import Graph from '../../data/Graph'; @@ -144,7 +145,8 @@ export interface GraphSeriesOption SeriesOnGeoOptionMixin, SeriesOnSingleOptionMixin, SymbolOptionMixin, RoamOptionMixin, - BoxLayoutOptionMixin { + BoxLayoutOptionMixin, + PreserveAspectMixin { type?: 'graph' diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index 614a0043be..3dab5ebb8c 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -25,7 +25,6 @@ import { updateViewOnPan, RoamControllerHost } from '../../component/helper/roamHelper'; -import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import * as graphic from '../../util/graphic'; import adjustEdge from './adjustEdge'; import {getNodeGlobalScale} from './graphHelper'; @@ -45,6 +44,7 @@ import { simpleLayoutEdge } from './simpleLayoutHelper'; import { circularLayout, rotateNodeLabel } from './circularLayoutHelper'; import { clone, extend } from 'zrender/src/core/util'; import ECLinePath from '../helper/LinePath'; +import { NullUndefined } from '../../util/types'; function isViewCoordSys(coordSys: CoordinateSystem): coordSys is View { return coordSys.type === 'view'; @@ -127,7 +127,7 @@ class GraphView extends ChartView { this._updateNodeAndLinkScale(); - this._updateController(seriesModel, ecModel, api); + this._updateController(null, seriesModel, api); clearTimeout(this._layoutTimeout); const forceLayout = seriesModel.forceLayout; @@ -251,29 +251,29 @@ class GraphView extends ChartView { } private _updateController( + clipRect: graphic.BoundingRect | NullUndefined, seriesModel: GraphSeriesModel, - ecModel: GlobalModel, api: ExtensionAPI ) { const controller = this._controller; const controllerHost = this._controllerHost; - const group = this.group; - - controller.setPointerChecker((e, x, y) => { - const rect = group.getBoundingRect(); - rect.applyTransform(group.transform); - return rect.contain(x, y) - && !this._thumbnail.contain(x, y) - && !onIrrelevantElement(e, api, seriesModel); - }); + const coordSys = seriesModel.coordinateSystem; - if (!isViewCoordSys(seriesModel.coordinateSystem)) { + if (!isViewCoordSys(coordSys)) { controller.disable(); return; } - controller.enable(seriesModel.get('roam')); + controller.enable(seriesModel.get('roam'), { + api, + zInfo: {component: seriesModel}, + triggerInfo: { + roamTrigger: seriesModel.get('roamTrigger'), + isInSelf: (e, x, y) => coordSys.containPoint([x, y]), + isInClip: (e, x, y) => !clipRect || clipRect.contain(x, y), + }, + }); controllerHost.zoomLimit = seriesModel.get('scaleLimit'); - controllerHost.zoom = seriesModel.coordinateSystem.getZoom(); + controllerHost.zoom = coordSys.getZoom(); controller .off('pan') @@ -292,6 +292,9 @@ class GraphView extends ChartView { dx: number, dy: number ): void { + // FIXME: should do nothing except `api.dispatchAction` here, the other logic + // should be performed in the action handler; otherwise, it causes inconsistency + // if user triggers this action explicitly. updateViewOnPan(this._controllerHost, dx, dy); api.dispatchAction({ seriesId: seriesModel.id, @@ -309,6 +312,9 @@ class GraphView extends ChartView { originX: number, originY: number ) { + // FIXME: should do nothing except `api.dispatchAction` here, the other logic + // should be performed in the action handler; otherwise, it causes inconsistency + // if user triggers this action explicitly. updateViewOnZoom(this._controllerHost, scale, originX, originY); api.dispatchAction({ seriesId: seriesModel.id, diff --git a/src/chart/graph/Thumbnail.ts b/src/chart/graph/Thumbnail.ts index e7a8a373af..3ee272e4cb 100644 --- a/src/chart/graph/Thumbnail.ts +++ b/src/chart/graph/Thumbnail.ts @@ -8,8 +8,8 @@ import BoundingRect from 'zrender/src/core/BoundingRect'; import * as matrix from 'zrender/src/core/matrix'; import * as vector from 'zrender/src/core/vector'; import SeriesModel from '../../model/Series'; -import { BoxLayoutOptionMixin, ItemStyleOption } from '../../util/types'; -import RoamController, { RoamEventDefinition, RoamType } from '../../component/helper/RoamController'; +import { BoxLayoutOptionMixin, ItemStyleOption, RoamOptionMixin } from '../../util/types'; +import RoamController, { RoamEventDefinition } from '../../component/helper/RoamController'; import Eventful from 'zrender/src/core/Eventful'; import tokens from '../../visual/tokens'; @@ -60,7 +60,7 @@ class Thumbnail extends Eventful> { render(opt: { seriesModel: GraphSeriesModel; api: ExtensionAPI; - roamType: RoamType; + roamType: RoamOptionMixin['roam']; z2Setting: ThumbnailZ2Setting; seriesBoundingRect: BoundingRect, renderThumbnailContent: (viewGroup: graphic.Group) => void @@ -85,22 +85,13 @@ class Thumbnail extends Eventful> { const itemStyle = itemStyleModel.getItemStyle(); itemStyle.fill = seriesModel.ecModel.get('backgroundColor') || tokens.color.neutral00; - // Try to use border-box in thumbnail, see https://github.com/apache/echarts/issues/18022 - const boxBorderWidth = itemStyle.lineWidth || 0; + const refContainer = layout.createBoxLayoutReference(seriesModel, api).refContainer; const boxContainBorder = layout.getLayoutRect( - { - left: thumbnailModel.get('left', true), - top: thumbnailModel.get('top', true), - right: thumbnailModel.get('right', true), - bottom: thumbnailModel.get('bottom', true), - width: thumbnailModel.get('width', true), - height: thumbnailModel.get('height', true) - }, - { - width: api.getWidth(), - height: api.getHeight() - } + layout.getBoxLayoutParams(thumbnailModel, true), + refContainer ); + // Try to use border-box in thumbnail, see https://github.com/apache/echarts/issues/18022 + const boxBorderWidth = itemStyle.lineWidth || 0; const borderBoundingRect = graphic.expandOrShrinkRect( boxContainBorder.clone(), boxBorderWidth / 2, true, true ); @@ -155,7 +146,7 @@ class Thumbnail extends Eventful> { windowRect.__r = windowStyleModel.get('borderRadius', true); clipGroup.add(windowRect); - this._resetRoamController(opt.roamType); + this._resetRoamController(opt.roamType, z2Setting.background); this.updateWindow(); } @@ -188,14 +179,23 @@ class Thumbnail extends Eventful> { this._mtThumbnailToSerise = matrix.invert([], this._mtSeriesToThumbnail); } - private _resetRoamController(roamType: RoamType): void { + private _resetRoamController( + roamType: RoamOptionMixin['roam'], + z2: number + ): void { let thumbnailController = this._thumbnailController; if (!thumbnailController) { thumbnailController = this._thumbnailController = new RoamController(this._api.getZr()); - thumbnailController.setPointerChecker((e, x, y) => this.contain(x, y)); } - thumbnailController.enable(roamType); + thumbnailController.enable(roamType, { + api: this._api, + zInfo: {component: this._seriesModel, z2}, + triggerInfo: { + roamTrigger: null, + isInSelf: (e, x, y) => this.contain(x, y) + } + }); thumbnailController .off('pan') .off('zoom') diff --git a/src/chart/graph/createView.ts b/src/chart/graph/createView.ts index e753823715..1cc6071772 100644 --- a/src/chart/graph/createView.ts +++ b/src/chart/graph/createView.ts @@ -19,7 +19,7 @@ // FIXME Where to create the simple view coordinate system import View from '../../coord/View'; -import {createBoxLayoutReference, getLayoutRect} from '../../util/layout'; +import {createBoxLayoutReference, getLayoutRect, applyPreserveAspect} from '../../util/layout'; import * as bbox from 'zrender/src/core/bbox'; import GraphSeriesModel, { GraphNodeItemOption } from './GraphSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; @@ -32,7 +32,8 @@ function getViewRect(seriesModel: GraphSeriesModel, api: ExtensionAPI, aspect: n const option = extend(seriesModel.getBoxLayoutParams(), { aspect: aspect }); - return getLayoutRect(option, layoutRef.refContainer); + const viewRect = getLayoutRect(option, layoutRef.refContainer); + return applyPreserveAspect(seriesModel, viewRect, aspect); } export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionAPI) { @@ -79,7 +80,7 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA const bbWidth = max[0] - min[0]; const bbHeight = max[1] - min[1]; - const viewCoordSys = new View(); + const viewCoordSys = new View(null, {api, ecModel}); viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); viewCoordSys.setBoundingRect( @@ -90,7 +91,7 @@ export default function createViewCoordSys(ecModel: GlobalModel, api: ExtensionA ); // Update roam info - viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel}); + viewCoordSys.setCenter(seriesModel.get('center')); viewCoordSys.setZoom(seriesModel.get('zoom')); viewList.push(viewCoordSys); diff --git a/src/chart/graph/install.ts b/src/chart/graph/install.ts index 2ffec7b035..9da98ec58f 100644 --- a/src/chart/graph/install.ts +++ b/src/chart/graph/install.ts @@ -29,7 +29,7 @@ import createView from './createView'; import View from '../../coord/View'; import GraphView from './GraphView'; import GraphSeriesModel from './GraphSeries'; -import { RoamPayload, updateCenterAndZoom } from '../../action/roamHelper'; +import { RoamPayload, updateCenterAndZoomInAction } from '../../component/helper/roamHelper'; import GlobalModel from '../../model/Global'; import { noop } from 'zrender/src/core/util'; import type ExtensionAPI from '../../core/ExtensionAPI'; @@ -79,7 +79,7 @@ export function install(registers: EChartsExtensionInstallRegisters) { }, function (seriesModel: GraphSeriesModel) { const coordSys = seriesModel.coordinateSystem as View; - const res = updateCenterAndZoom(coordSys, payload, undefined, api); + const res = updateCenterAndZoomInAction(coordSys, payload, seriesModel.get('scaleLimit')); seriesModel.setCenter && seriesModel.setCenter(res.center); diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts index 98d767d810..9409f56df6 100644 --- a/src/chart/sankey/SankeySeries.ts +++ b/src/chart/sankey/SankeySeries.ts @@ -317,6 +317,7 @@ class SankeySeriesModel extends SeriesModel { // true | false | 'move' | 'scale', see module:component/helper/RoamController. roam: false, + roamTrigger: 'global', center: null, zoom: 1, diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts index 2ae4189695..53c8a98c4b 100644 --- a/src/chart/sankey/SankeyView.ts +++ b/src/chart/sankey/SankeyView.ts @@ -130,7 +130,7 @@ class SankeyView extends ChartView { render(seriesModel: SankeySeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { const sankeyView = this; const graph = seriesModel.getGraph(); - const group = this._mainGroup; + const mainGroup = this._mainGroup; const layoutInfo = seriesModel.layoutInfo; // view width const width = layoutInfo.width; @@ -142,10 +142,13 @@ class SankeyView extends ChartView { this._model = seriesModel; - group.removeAll(); + mainGroup.removeAll(); + + mainGroup.x = layoutInfo.x; + mainGroup.y = layoutInfo.y; this._updateViewCoordSys(seriesModel, api); - roamHelper.updateController(seriesModel, api, group, this._controller, this._controllerHost); + roamHelper.updateController(seriesModel, api, mainGroup, this._controller, this._controllerHost, null); // generate a bezire Curve for each edge graph.eachEdge(function (edge) { @@ -253,7 +256,7 @@ class SankeyView extends ChartView { return style; }); - group.add(curve); + mainGroup.add(curve); edgeData.setItemGraphicEl(edge.dataIndex, curve); @@ -309,7 +312,7 @@ class SankeyView extends ChartView { setStatesStylesFromModel(rect, itemModel); - group.add(rect); + mainGroup.add(rect); nodeData.setItemGraphicEl(node.dataIndex, rect); @@ -353,8 +356,8 @@ class SankeyView extends ChartView { }); if (!this._data && seriesModel.isAnimationEnabled()) { - group.setClipPath(createGridClipShape(group.getBoundingRect(), seriesModel, function () { - group.removeClipPath(); + mainGroup.setClipPath(createGridClipShape(mainGroup.getBoundingRect(), seriesModel, function () { + mainGroup.removeClipPath(); })); } @@ -371,16 +374,20 @@ class SankeyView extends ChartView { const width = layoutInfo.width; const height = layoutInfo.height; - const viewCoordSys = seriesModel.coordinateSystem = new View(); + const viewCoordSys = seriesModel.coordinateSystem = new View(null, {api, ecModel: seriesModel.ecModel}); viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); viewCoordSys.setBoundingRect(0, 0, width, height); - viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel: seriesModel.ecModel}); + viewCoordSys.setCenter(seriesModel.get('center')); viewCoordSys.setZoom(seriesModel.get('zoom')); - this._mainGroup.x = layoutInfo.x; - this._mainGroup.y = layoutInfo.y; + this._controllerHost.target.attr({ + x: viewCoordSys.x, + y: viewCoordSys.y, + scaleX: viewCoordSys.scaleX, + scaleY: viewCoordSys.scaleY + }); } } diff --git a/src/chart/sankey/install.ts b/src/chart/sankey/install.ts index b3da093bfb..5314aaedde 100644 --- a/src/chart/sankey/install.ts +++ b/src/chart/sankey/install.ts @@ -25,7 +25,7 @@ import sankeyLayout from './sankeyLayout'; import sankeyVisual from './sankeyVisual'; import { Payload } from '../../util/types'; import GlobalModel from '../../model/Global'; -import { updateCenterAndZoom, RoamPayload } from '../../action/roamHelper'; +import { updateCenterAndZoomInAction, RoamPayload } from '../../component/helper/roamHelper'; import type ExtensionAPI from '../../core/ExtensionAPI'; interface SankeyDragNodePayload extends Payload { @@ -64,12 +64,10 @@ export function install(registers: EChartsExtensionInstallRegisters) { mainType: 'series', subType: 'sankey', query: payload }, function (seriesModel: SankeySeriesModel) { const coordSys = seriesModel.coordinateSystem; - const res = updateCenterAndZoom(coordSys, payload, undefined, api); + const res = updateCenterAndZoomInAction(coordSys, payload, seriesModel.get('scaleLimit')); - seriesModel.setCenter - && seriesModel.setCenter(res.center); - seriesModel.setZoom - && seriesModel.setZoom(res.zoom); + seriesModel.setCenter(res.center); + seriesModel.setZoom(res.zoom); }); }); } diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts index 427c275304..c00ce26c9b 100644 --- a/src/chart/tree/TreeSeries.ts +++ b/src/chart/tree/TreeSeries.ts @@ -271,6 +271,7 @@ class TreeSeriesModel extends SeriesModel { // true | false | 'move' | 'scale', see module:component/helper/RoamController. roam: false, + roamTrigger: 'global', // Symbol size scale ratio in roam nodeScaleRatio: 0.4, diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index cd82fd8e63..f7482687b6 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -35,7 +35,7 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import SeriesData from '../../data/SeriesData'; import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy, HOVER_STATE_BLUR } from '../../util/states'; -import { AnimationOption, ECElement } from '../../util/types'; +import { AnimationOption, ECElement, NullUndefined } from '../../util/types'; import tokens from '../../visual/tokens'; type TreeSymbol = SymbolClz & { @@ -173,7 +173,7 @@ class TreeView extends ChartView { } this._updateViewCoordSys(seriesModel, api); - this._updateController(seriesModel, ecModel, api); + this._updateController(seriesModel, null, ecModel, api); const oldData = this._data; @@ -252,15 +252,16 @@ class TreeView extends ChartView { max[1] = oldMax ? oldMax[1] : max[1] + 1; } - const viewCoordSys = seriesModel.coordinateSystem = new View(); + const viewCoordSys = seriesModel.coordinateSystem = new View(null, {api, ecModel: seriesModel.ecModel}); viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]); - viewCoordSys.setCenter(seriesModel.get('center'), {api, ecModel: seriesModel.ecModel}); + viewCoordSys.setCenter(seriesModel.get('center')); viewCoordSys.setZoom(seriesModel.get('zoom')); - // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group + // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group, + // and 'treeRoam' action. this.group.attr({ x: viewCoordSys.x, y: viewCoordSys.y, @@ -274,6 +275,7 @@ class TreeView extends ChartView { _updateController( seriesModel: TreeSeriesModel, + clipRect: graphic.BoundingRect | NullUndefined, ecModel: GlobalModel, api: ExtensionAPI ) { @@ -282,7 +284,8 @@ class TreeView extends ChartView { api, this.group, this._controller, - this._controllerHost + this._controllerHost, + clipRect ); this._controller diff --git a/src/chart/tree/treeAction.ts b/src/chart/tree/treeAction.ts index 358779919c..df737a4480 100644 --- a/src/chart/tree/treeAction.ts +++ b/src/chart/tree/treeAction.ts @@ -17,7 +17,7 @@ * under the License. */ -import {updateCenterAndZoom, RoamPayload} from '../../action/roamHelper'; +import {updateCenterAndZoomInAction, RoamPayload} from '../../component/helper/roamHelper'; import { Payload } from '../../util/types'; import TreeSeriesModel from './TreeSeries'; import GlobalModel from '../../model/Global'; @@ -57,13 +57,10 @@ export function installTreeAction(registers: EChartsExtensionInstallRegisters) { mainType: 'series', subType: 'tree', query: payload }, function (seriesModel: TreeSeriesModel) { const coordSys = seriesModel.coordinateSystem; - const res = updateCenterAndZoom(coordSys, payload, undefined, api); + const res = updateCenterAndZoomInAction(coordSys, payload, seriesModel.get('scaleLimit')); - seriesModel.setCenter - && seriesModel.setCenter(res.center); - - seriesModel.setZoom - && seriesModel.setZoom(res.zoom); + seriesModel.setCenter(res.center); + seriesModel.setZoom(res.zoom); }); }); diff --git a/src/chart/treemap/TreemapSeries.ts b/src/chart/treemap/TreemapSeries.ts index 831c93bc6c..9776d40684 100644 --- a/src/chart/treemap/TreemapSeries.ts +++ b/src/chart/treemap/TreemapSeries.ts @@ -272,6 +272,7 @@ class TreemapSeriesModel extends SeriesModel { }, roam: true, + roamTrigger: 'global', nodeClick: 'zoomToNode', animation: true, animationDurationUpdate: 900, diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts index 59bd46db67..36a5918cd2 100644 --- a/src/chart/treemap/TreemapView.ts +++ b/src/chart/treemap/TreemapView.ts @@ -175,7 +175,6 @@ class TreemapView extends ChartView { api: ExtensionAPI, payload: TreemapZoomToNodePayload | TreemapRenderPayload | TreemapMovePayload | TreemapRootToNodePayload ) { - const models = ecModel.findComponents({ mainType: 'series', subType: 'treemap', query: payload }); @@ -490,20 +489,34 @@ class TreemapView extends ChartView { controllerHost = this._controllerHost; } + const seriesModel = this.seriesModel; + // Init controller. if (!controller) { controller = this._controller = new RoamController(api.getZr()); - controller.enable(this.seriesModel.get('roam')); - controllerHost.zoomLimit = this.seriesModel.get('scaleLimit'); - controllerHost.zoom = this.seriesModel.get('zoom'); controller.on('pan', bind(this._onPan, this)); controller.on('zoom', bind(this._onZoom, this)); } - const rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight()); - controller.setPointerChecker(function (e, x, y) { - return rect.contain(x, y); + controller.enable(seriesModel.get('roam'), { + api, + zInfo: {component: seriesModel}, + triggerInfo: { + roamTrigger: seriesModel.get('roamTrigger'), + isInSelf: (e, x, y) => { + const containerGroup = this._containerGroup; + return containerGroup + // Currently only x, y exist in tranform. + ? containerGroup.getBoundingRect().contain(x - containerGroup.x, y - containerGroup.y) + : false; + }, + // isInClip: (e, x, y) => { + // } + }, }); + + controllerHost.zoomLimit = seriesModel.get('scaleLimit'); + controllerHost.zoom = seriesModel.get('zoom'); } private _clearController() { @@ -705,13 +718,12 @@ class TreemapView extends ChartView { } /** - * @public - * @param {number} x Global coord x. - * @param {number} y Global coord y. - * @return {Object} info If not found, return undefined; - * @return {number} info.node Target node. - * @return {number} info.offsetX x refer to target node. - * @return {number} info.offsetY y refer to target node. + * @param x Global coord x. + * @param y Global coord y. + * @return info If not found, return undefined; + * @return info.node Target node. + * @return info.offsetX x refer to target node. + * @return info.offsetY y refer to target node. */ findTarget(x: number, y: number): FoundTargetInfo { let targetInfo; @@ -746,9 +758,6 @@ class TreemapView extends ChartView { } } -/** - * @inner - */ function createStorage(): RenderElementStorage | LastCfgStorage { return { nodeGroup: [], @@ -758,7 +767,6 @@ function createStorage(): RenderElementStorage | LastCfgStorage { } /** - * @inner * @return Return undefined means do not travel further. */ function renderNode( diff --git a/src/component/axis/axisBreakHelper.ts b/src/component/axis/axisBreakHelper.ts index db8b94a56e..cbf0aba4a7 100644 --- a/src/component/axis/axisBreakHelper.ts +++ b/src/component/axis/axisBreakHelper.ts @@ -27,9 +27,9 @@ import type { PathProps } from 'zrender/src/graphic/Path'; import type SingleAxisView from './SingleAxisView'; import type { AxisBuilderCfg } from './AxisBuilder'; import type { BaseAxisBreakPayload } from './axisAction'; -import type { ComponentModel } from '../../echarts.all'; import type { AxisBaseOption } from '../../coord/axisCommonTypes'; import type { AxisBreakOptionIdentifierInAxis, NullUndefined } from '../../util/types'; +import type ComponentModel from '../../model/Component'; /** * @file The fasade of axis break view and mode. diff --git a/src/component/dataZoom/roams.ts b/src/component/dataZoom/roams.ts index d33e1e287a..38800b8c35 100644 --- a/src/component/dataZoom/roams.ts +++ b/src/component/dataZoom/roams.ts @@ -23,10 +23,10 @@ // pan or zoom, only dispatch one action for those data zoom // components. -import RoamController, { RoamType } from '../../component/helper/RoamController'; +import RoamController, { RoamOption } from '../../component/helper/RoamController'; import * as throttleUtil from '../../util/throttle'; import { makeInner } from '../../util/model'; -import { Dictionary, ZRElementEvent } from '../../util/types'; +import { Dictionary, RoamOptionMixin, ZRElementEvent } from '../../util/types'; import ExtensionAPI from '../../core/ExtensionAPI'; import InsideZoomModel from './InsideZoomModel'; import { each, curry, Curry1, HashMap, createHashMap } from 'zrender/src/core/util'; @@ -179,8 +179,15 @@ function containsPoint( /** * Merge roamController settings when multiple dataZooms share one roamController. */ -function mergeControllerParams(dataZoomInfoMap: HashMap<{ model: InsideZoomModel }>) { - let controlType: RoamType; +function mergeControllerParams( + dataZoomInfoMap: HashMap<{ model: InsideZoomModel }>, + coordSysRecord: CoordSysRecord, + api: ExtensionAPI +): { + controlType: RoamOptionMixin['roam'] + opt: RoamOption +} { + let controlType: RoamOptionMixin['roam']; // DO NOT use reserved word (true, false, undefined) as key literally. Even if encapsulated // as string, it is probably revert to reserved word by compress tool. See #7411. const prefix = 'type_'; @@ -218,7 +225,15 @@ function mergeControllerParams(dataZoomInfoMap: HashMap<{ model: InsideZoomModel zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true, - preventDefaultMouseMove: !!preventDefaultMouseMove + preventDefaultMouseMove: !!preventDefaultMouseMove, + api, + zInfo: { + component: coordSysRecord.model, + }, + triggerInfo: { + roamTrigger: null, + isInSelf: coordSysRecord.containsPoint + }, } }; } @@ -280,11 +295,9 @@ export function installDataZoomRoamProcessor(registers: EChartsExtensionInstallR return; } - const controllerParams = mergeControllerParams(dataZoomInfoMap); + const controllerParams = mergeControllerParams(dataZoomInfoMap, coordSysRecord, api); controller.enable(controllerParams.controlType, controllerParams.opt); - controller.setPointerChecker(coordSysRecord.containsPoint); - throttleUtil.createOrUpdate( coordSysRecord, 'dispatchAction', diff --git a/src/component/geo/install.ts b/src/component/geo/install.ts index 3bf9e3e2b9..f00a756bf0 100644 --- a/src/component/geo/install.ts +++ b/src/component/geo/install.ts @@ -23,7 +23,7 @@ import geoCreator from '../../coord/geo/geoCreator'; import { ActionInfo } from '../../util/types'; import { each } from 'zrender/src/core/util'; import GlobalModel from '../../model/Global'; -import { updateCenterAndZoom, RoamPayload } from '../../action/roamHelper'; +import { updateCenterAndZoomInAction, RoamPayload } from '../../component/helper/roamHelper'; import MapSeries from '../../chart/map/MapSeries'; import GeoView from './GeoView'; import geoSourceManager from '../../coord/geo/geoSourceManager'; @@ -126,8 +126,8 @@ export function install(registers: EChartsExtensionInstallRegisters) { return; } - const res = updateCenterAndZoom( - geo, payload, (componentModel as GeoModel).get('scaleLimit'), api + const res = updateCenterAndZoomInAction( + geo, payload, (componentModel as GeoModel).get('scaleLimit') ); componentModel.setCenter diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts index 563f6de8ef..83eaf2efd0 100644 --- a/src/component/helper/MapDraw.ts +++ b/src/component/helper/MapDraw.ts @@ -20,7 +20,6 @@ import * as zrUtil from 'zrender/src/core/util'; import RoamController from './RoamController'; import * as roamHelper from '../../component/helper/roamHelper'; -import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import * as graphic from '../../util/graphic'; import { toggleHoverEmphasis, @@ -33,7 +32,9 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption, RegionOption } from '../../coord/geo/GeoModel'; import MapSeries, { MapDataItemOption } from '../../chart/map/MapSeries'; import GlobalModel from '../../model/Global'; -import { Payload, ECElement, LineStyleOption, InnerFocus, DisplayState } from '../../util/types'; +import { + Payload, ECElement, LineStyleOption, InnerFocus, DisplayState, NullUndefined, RoamOptionMixin +} from '../../util/types'; import GeoView from '../geo/GeoView'; import MapView from '../../chart/map/MapView'; import Geo from '../../coord/geo/Geo'; @@ -142,6 +143,8 @@ class MapDraw { */ private _mouseDownFlag: boolean; + private _transformGroup: graphic.Group; + private _regionsGroup: RegionsGroup; private _regionsGroupByName: zrUtil.HashMap; @@ -158,14 +161,15 @@ class MapDraw { constructor(api: ExtensionAPI) { - const group = new graphic.Group(); + const group = this.group = new graphic.Group(); + const transformGroup = this._transformGroup = new graphic.Group(); + group.add(transformGroup); this.uid = getUID('ec_map_draw'); this._controller = new RoamController(api.getZr()); - this._controllerHost = { target: group }; - this.group = group; + this._controllerHost = { target: transformGroup }; - group.add(this._regionsGroup = new graphic.Group() as RegionsGroup); - group.add(this._svgGroup = new graphic.Group()); + transformGroup.add(this._regionsGroup = new graphic.Group() as RegionsGroup); + transformGroup.add(this._svgGroup = new graphic.Group()); } draw( @@ -190,7 +194,7 @@ class MapDraw { const geo = mapOrGeoModel.coordinateSystem; const regionsGroup = this._regionsGroup; - const group = this.group; + const transformGroup = this._transformGroup; const transformInfo = geo.getTransformInfo(); const transformInfoRaw = transformInfo.raw; @@ -199,15 +203,25 @@ class MapDraw { // No animation when first draw or in action const isFirstDraw = !regionsGroup.childAt(0) || payload; + const clip = (mapOrGeoModel as Model).getShallow('clip', true); + let clipRect: graphic.BoundingRect | NullUndefined; + if (clip) { + clipRect = geo.getViewRect().clone(); + this.group.setClipPath(new graphic.Rect({shape: clipRect.clone()})); + } + else { + this.group.removeClipPath(); + } + if (isFirstDraw) { - group.x = transformInfoRoam.x; - group.y = transformInfoRoam.y; - group.scaleX = transformInfoRoam.scaleX; - group.scaleY = transformInfoRoam.scaleY; - group.dirty(); + transformGroup.x = transformInfoRoam.x; + transformGroup.y = transformInfoRoam.y; + transformGroup.scaleX = transformInfoRoam.scaleX; + transformGroup.scaleY = transformInfoRoam.scaleY; + transformGroup.dirty(); } else { - graphic.updateProps(group, transformInfoRoam, mapOrGeoModel); + graphic.updateProps(transformGroup, transformInfoRoam, mapOrGeoModel); } const isVisualEncodedByVisualMap = data @@ -231,7 +245,7 @@ class MapDraw { this._buildSVG(viewBuildCtx); } - this._updateController(mapOrGeoModel, ecModel, api); + this._updateController(mapOrGeoModel, clipRect, ecModel, api); this._updateMapSelectHandler(mapOrGeoModel, regionsGroup, api, fromView); } @@ -557,19 +571,32 @@ class MapDraw { } private _updateController( - this: MapDraw, mapOrGeoModel: GeoModel | MapSeries, ecModel: GlobalModel, api: ExtensionAPI + this: MapDraw, + mapOrGeoModel: GeoModel | MapSeries, + clipRect: graphic.BoundingRect | NullUndefined, + ecModel: GlobalModel, + api: ExtensionAPI ): void { const geo = mapOrGeoModel.coordinateSystem; const controller = this._controller; const controllerHost = this._controllerHost; - // @ts-ignore FIXME:TS - controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit'); + controllerHost.zoomLimit = (mapOrGeoModel as Model).get('scaleLimit'); controllerHost.zoom = geo.getZoom(); // roamType is will be set default true if it is null - // @ts-ignore FIXME:TS - controller.enable(mapOrGeoModel.get('roam') || false); + controller.enable( + (mapOrGeoModel as Model).get('roam') || false, + { + api, + zInfo: {component: mapOrGeoModel}, + triggerInfo: { + roamTrigger: (mapOrGeoModel as Model).get('roamTrigger'), + isInSelf: (e, x, y) => geo.containPoint([x, y]), + isInClip: (e, x, y) => !clipRect || clipRect.contain(x, y), + }, + } + ); const mainType = mapOrGeoModel.mainType; function makeActionBase(): Payload { @@ -611,11 +638,6 @@ class MapDraw { })); }, this); - - controller.setPointerChecker(function (e, x, y) { - return geo.containPoint([x, y]) - && !onIrrelevantElement(e, api, mapOrGeoModel); - }); } /** diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts index 70cdd0c5d3..3b6d4bb3ef 100644 --- a/src/component/helper/RoamController.ts +++ b/src/component/helper/RoamController.ts @@ -21,14 +21,29 @@ import Eventful from 'zrender/src/core/Eventful'; import * as eventTool from 'zrender/src/core/event'; import * as interactionMutex from './interactionMutex'; import { ZRenderType } from 'zrender/src/zrender'; -import { ZRElementEvent, RoamOptionMixin } from '../../util/types'; -import { Bind3, isString, bind, defaults, clone } from 'zrender/src/core/util'; - -// Can be null/undefined or true/false -// or 'pan/move' or 'zoom'/'scale' -export type RoamType = RoamOptionMixin['roam']; +import { ZRElementEvent, RoamOptionMixin, NullUndefined } from '../../util/types'; +import { Bind3, isString, bind, defaults, extend, retrieve2 } from 'zrender/src/core/util'; +import { makeInner, retrieveZInfo } from '../../util/model'; +import type Component from '../../model/Component'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import { onIrrelevantElement } from './cursorHelper'; + + +export interface RoamOption { + zInfo: { + // mandatory to provide z and zlevel and pointer checker criteria. + component: Component + z2?: number + } + triggerInfo: { + roamTrigger: RoamOptionMixin['roamTrigger'] | NullUndefined + // At present in all scenarios it can be supported. + isInSelf: RoamPointerChecker + // Required if clipping is supported + isInClip?: RoamPointerChecker + } + api: ExtensionAPI -interface RoamOption { zoomOnMouseWheel?: boolean | 'ctrl' | 'shift' | 'alt' moveOnMouseMove?: boolean | 'ctrl' | 'shift' | 'alt' moveOnMouseWheel?: boolean | 'ctrl' | 'shift' | 'alt' @@ -37,6 +52,14 @@ interface RoamOption { */ preventDefaultMouseMove?: boolean } +type RoamSetting = Omit, 'zInfo'> & { + zInfoParsed: { + component: Component + z: number + zlevel: number + z2: number + } +}; type RoamEventType = keyof RoamEventParams; @@ -72,13 +95,13 @@ export type RoamEventDefinition = { [key in keyof RoamEventParams]: (params: RoamEventParams[key]) => void | undefined }; -class RoamController extends Eventful { +type RoamPointerChecker = (e: ZRElementEvent, x: number, y: number) => boolean; - pointerChecker: (e: ZRElementEvent, x: number, y: number) => boolean; +class RoamController extends Eventful { private _zr: ZRenderType; - private _opt: Required; + private _opt: Required; private _dragging: boolean; @@ -88,7 +111,11 @@ class RoamController extends Eventful { private _y: number; - readonly enable: (this: this, controlType?: RoamType, opt?: RoamOption) => void; + private _controlType: RoamOptionMixin['roam']; + + private _enabled: boolean; + + readonly enable: (this: this, controlType: RoamOptionMixin['roam'], opt: RoamOption) => void; readonly disable: () => void; @@ -110,40 +137,59 @@ class RoamController extends Eventful { * is not needed, 'zoom' should not be enabled, otherwise * default mousewheel behaviour (scroll page) will be disabled. */ - this.enable = function (controlType, opt) { - - // Disable previous first - this.disable(); - - this._opt = defaults(clone(opt) || {}, { + this.enable = function (controlType, rawOpt) { + const zInfo = rawOpt.zInfo; + const {z, zlevel} = retrieveZInfo(zInfo.component); + const zInfoParsed = { + component: zInfo.component, + z, + zlevel, + // By default roam controller is the lowest z2 comparing to other elememts in a component. + z2: retrieve2(zInfo.z2, -Infinity), + }; + const triggerInfo = extend({}, rawOpt.triggerInfo); + + this._opt = defaults(extend({}, rawOpt), { zoomOnMouseWheel: true, moveOnMouseMove: true, // By default, wheel do not trigger move. moveOnMouseWheel: false, - preventDefaultMouseMove: true + preventDefaultMouseMove: true, + zInfoParsed, + triggerInfo, }); if (controlType == null) { controlType = true; } - if (controlType === true || (controlType === 'move' || controlType === 'pan')) { - zr.on('mousedown', mousedownHandler); - zr.on('mousemove', mousemoveHandler); - zr.on('mouseup', mouseupHandler); - } - if (controlType === true || (controlType === 'scale' || controlType === 'zoom')) { - zr.on('mousewheel', mousewheelHandler); - zr.on('pinch', pinchHandler); + // A handy optimization for repeatedly calling `enable` during roaming. + // Assert `disable` is only affected by `controlType`. + if (!this._enabled || this._controlType !== controlType) { + this._enabled = true; + + // Disable previous first + this.disable(); + + if (controlType === true || (controlType === 'move' || controlType === 'pan')) { + addRoamZrListener(zr, 'mousedown', mousedownHandler, zInfoParsed); + addRoamZrListener(zr, 'mousemove', mousemoveHandler, zInfoParsed); + addRoamZrListener(zr, 'mouseup', mouseupHandler, zInfoParsed); + } + if (controlType === true || (controlType === 'scale' || controlType === 'zoom')) { + addRoamZrListener(zr, 'mousewheel', mousewheelHandler, zInfoParsed); + addRoamZrListener(zr, 'pinch', pinchHandler, zInfoParsed); + } } }; this.disable = function () { - zr.off('mousedown', mousedownHandler); - zr.off('mousemove', mousemoveHandler); - zr.off('mouseup', mouseupHandler); - zr.off('mousewheel', mousewheelHandler); - zr.off('pinch', pinchHandler); + this._enabled = false; + removeRoamZrListener(zr, 'mousedown', mousedownHandler); + removeRoamZrListener(zr, 'mousemove', mousemoveHandler); + removeRoamZrListener(zr, 'mouseup', mouseupHandler); + removeRoamZrListener(zr, 'mousewheel', mousewheelHandler); + removeRoamZrListener(zr, 'pinch', pinchHandler); }; } @@ -155,8 +201,27 @@ class RoamController extends Eventful { return this._pinching; } - setPointerChecker(pointerChecker: RoamController['pointerChecker']) { - this.pointerChecker = pointerChecker; + _checkPointer(e: ZRElementEvent, x: number, y: number): boolean { + const opt = this._opt; + + const zInfoParsed = opt.zInfoParsed; + if (onIrrelevantElement(e, opt.api, zInfoParsed.component)) { + return false; + }; + + const triggerInfo = opt.triggerInfo; + const roamTrigger = triggerInfo.roamTrigger; + let inArea = false; + if (roamTrigger === 'global') { + inArea = true; + } + if (!inArea) { + inArea = triggerInfo.isInSelf(e, x, y); + } + if (inArea && triggerInfo.isInClip && !triggerInfo.isInClip(e, x, y)) { + inArea = false; + } + return inArea; } dispose() { @@ -164,7 +229,9 @@ class RoamController extends Eventful { } private _mousedownHandler(e: ZRElementEvent) { - if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) { + if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e) + || eventConsumed(e) + ) { return; } @@ -182,7 +249,7 @@ class RoamController extends Eventful { // To determine dragging start, only by checking on mosedown, but not mousemove. // Mouse can be out of target when mouse moving. - if (this.pointerChecker && this.pointerChecker(e, x, y)) { + if (this._checkPointer(e, x, y)) { this._x = x; this._y = y; this._dragging = true; @@ -193,6 +260,7 @@ class RoamController extends Eventful { const zr = this._zr; if (e.gestureEvent === 'pinch' || interactionMutex.isTaken(zr, 'globalPan') + || eventConsumed(e) ) { return; } @@ -203,10 +271,11 @@ class RoamController extends Eventful { if (!this._dragging || !isAvailableBehavior('moveOnMouseMove', e, this._opt) ) { - if (this.pointerChecker && this.pointerChecker(e, x, y) + if (this._checkPointer(e, x, y) // This `grab` cursor style should take the lowest precedence. If the hovring element already // have a cursor, zrender will set it to be non-'default' before entering this handler. - && e.event && e.event.zrCursorStyle === 'default' + // && e.event && e.event.zrCursorStyle === 'default' + && (!e.topTarget || e.topTarget.silent) ) { // To indicate users that this area is draggable, otherwise users probably cannot kwown // that when hovering out of the shape but still inside the bounding rect. @@ -228,7 +297,10 @@ class RoamController extends Eventful { this._x = x; this._y = y; - this._opt.preventDefaultMouseMove && eventTool.stop(e.event); + if (this._opt.preventDefaultMouseMove) { + eventTool.stop(e.event); + } + (e as RoamControllerZREventExtend).__ecRoamConsumed = true; trigger(this, 'pan', 'moveOnMouseMove', e, { dx: dx, dy: dy, oldX: oldX, oldY: oldY, newX: x, newY: y, isAvailableBehavior: null @@ -236,12 +308,19 @@ class RoamController extends Eventful { } private _mouseupHandler(e: ZRElementEvent) { + if (eventConsumed(e)) { + return; + } if (!eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) { this._dragging = false; } } private _mousewheelHandler(e: ZRElementEvent) { + if (eventConsumed(e)) { + return; + } + const shouldZoom = isAvailableBehavior('zoomOnMouseWheel', e, this._opt); const shouldMove = isAvailableBehavior('moveOnMouseWheel', e, this._opt); const wheelDelta = e.wheelDelta; @@ -270,7 +349,7 @@ class RoamController extends Eventful { // wheelDelta of mouse wheel is bigger than touch pad. const factor = absWheelDeltaDelta > 3 ? 1.4 : absWheelDeltaDelta > 1 ? 1.2 : 1.1; const scale = wheelDelta > 0 ? factor : 1 / factor; - checkPointerAndTrigger(this, 'zoom', 'zoomOnMouseWheel', e, { + this._checkTriggerMoveZoom(this, 'zoom', 'zoomOnMouseWheel', e, { scale: scale, originX: originX, originY: originY, isAvailableBehavior: null }); } @@ -280,43 +359,155 @@ class RoamController extends Eventful { const absDelta = Math.abs(wheelDelta); // wheelDelta of mouse wheel is bigger than touch pad. const scrollDelta = (wheelDelta > 0 ? 1 : -1) * (absDelta > 3 ? 0.4 : absDelta > 1 ? 0.15 : 0.05); - checkPointerAndTrigger(this, 'scrollMove', 'moveOnMouseWheel', e, { + this._checkTriggerMoveZoom(this, 'scrollMove', 'moveOnMouseWheel', e, { scrollDelta: scrollDelta, originX: originX, originY: originY, isAvailableBehavior: null }); } } private _pinchHandler(e: ZRElementEvent) { - if (interactionMutex.isTaken(this._zr, 'globalPan')) { + if (interactionMutex.isTaken(this._zr, 'globalPan') + || eventConsumed(e) + ) { return; } const scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1; - checkPointerAndTrigger(this, 'zoom', null, e, { + this._checkTriggerMoveZoom(this, 'zoom', null, e, { scale: scale, originX: e.pinchX, originY: e.pinchY, isAvailableBehavior: null }); } + + private _checkTriggerMoveZoom( + controller: RoamController, + eventName: T, + behaviorToCheck: RoamBehavior, + e: ZRElementEvent, + contollerEvent: RoamEventParams[T] + ) { + if (controller._checkPointer(e, contollerEvent.originX, contollerEvent.originY)) { + // When mouse is out of roamController rect, + // default befavoius should not be be disabled, otherwise + // page sliding is disabled, contrary to expectation. + eventTool.stop(e.event); + (e as RoamControllerZREventExtend).__ecRoamConsumed = true; + + trigger(controller, eventName, behaviorToCheck, e, contollerEvent); + } + } } +type RoamControllerZREventExtend = ZRElementEvent & { + __ecRoamConsumed: boolean +}; -function checkPointerAndTrigger( - controller: RoamController, - eventName: T, - behaviorToCheck: RoamBehavior, - e: ZRElementEvent, - contollerEvent: RoamEventParams[T] -) { - if (controller.pointerChecker - && controller.pointerChecker(e, contollerEvent.originX, contollerEvent.originY) - ) { - // When mouse is out of roamController rect, - // default befavoius should not be be disabled, otherwise - // page sliding is disabled, contrary to expectation. - eventTool.stop(e.event); +function eventConsumed(e: ZRElementEvent): boolean { + return (e as RoamControllerZREventExtend).__ecRoamConsumed; +} + + +type RoamControllerZREventListener = (e: ZRElementEvent) => void; +type RoamControllerZREventType = 'mousedown' | 'mousemove' | 'mouseup' | 'mousewheel' | 'pinch'; +type RoamControllerListenerItem = {listener: RoamControllerZREventListener} & Pick; + +const innerZrStore = makeInner<{ + roam: Partial> + uniform: Partial> +}, ZRenderType>(); + +function ensureZrStore(zr: ZRenderType) { + const store = innerZrStore(zr); + store.roam = store.roam || {}; + store.uniform = store.uniform || {}; + return store; +} + +/** + * Listeners are sorted by z2/z/zlevel in descending order. + * This decides the precedence between different roam controllers if they are overlapped. + * + * [MEMO]: It's not easy to perfectly reconcile the conflicts caused by overlap. + * - Consider cases: + * - Multiple roam controllers overlapped. + * - Usually only the topmost can trigger roam. + * - Roam controllers overlap with other zr elements: + * - zr elements are relevant or irrelevent to the host of the roam controller. e.g., axis split line + * or series elements is relevant to a cartesian and should trigger roam. + * - zr elements is above or below the roam controller host, which affects the precedence of interaction. + * - zr elements may not silent only for triggering tooltip by hovering, which is available to roam; + * or may not silent for click, where roam is not preferable. + * - Approach - `addRoamZrListener+pointerChecker+onIrrelevantElement` (currently used): + * - Resolve the precedence between different roam controllers + * - But cannot prevent the handling on other zr elements that under the roam controller in z-order. + * - Approach - "use an invisible zr elements to receive the zr events to trigger roam": + * - More complicated in impl. + * - May cause bad cases where zr event cannot be receive due to other non-silient zr elements covering it. + */ +function addRoamZrListener( + zr: ZRenderType, + eventType: RoamControllerZREventType, + listener: RoamControllerZREventListener, + zInfoParsed: RoamSetting['zInfoParsed'] +): void { + const store = ensureZrStore(zr); + const roam = store.roam; + const listenerList = roam[eventType] = roam[eventType] || []; + let idx = 0; + for (; idx < listenerList.length; idx++) { + const currZInfo = listenerList[idx].zInfoParsed; + if ( + ((currZInfo.zlevel - zInfoParsed.zlevel) + || (currZInfo.z - zInfoParsed.z) + || (currZInfo.z2 - zInfoParsed.z2) + // If all equals, the latter added one has a higher precedence. + ) <= 0 + ) { + break; + } + } + listenerList.splice(idx, 0, {listener, zInfoParsed}); + ensureUniformListener(zr, eventType); +} - trigger(controller, eventName, behaviorToCheck, e, contollerEvent); +function removeRoamZrListener( + zr: ZRenderType, eventType: RoamControllerZREventType, listener: RoamControllerZREventListener +): void { + const store = ensureZrStore(zr); + const listenerList = store.roam[eventType] || []; + for (let idx = 0; idx < listenerList.length; idx++) { + if (listenerList[idx].listener === listener) { + listenerList.splice(idx, 1); + if (!listenerList.length) { + removeUniformListener(zr, eventType); + } + return; + } } } +function ensureUniformListener(zr: ZRenderType, eventType: RoamControllerZREventType): void { + const store = ensureZrStore(zr); + if (!store.uniform[eventType]) { + zr.on(eventType, store.uniform[eventType] = function (event) { + const listenerList = store.roam[eventType]; + if (listenerList) { + for (let i = 0; i < listenerList.length; i++) { + listenerList[i].listener(event); + } + } + }); + } +} + +function removeUniformListener(zr: ZRenderType, eventType: RoamControllerZREventType): void { + const store = ensureZrStore(zr); + const uniform = store.uniform; + if (uniform[eventType]) { + zr.off(eventType, uniform[eventType]); + uniform[eventType] = null; + } +} + + function trigger( controller: RoamController, eventName: T, diff --git a/src/component/helper/cursorHelper.ts b/src/component/helper/cursorHelper.ts index dd23501dbc..7a53da7f3f 100644 --- a/src/component/helper/cursorHelper.ts +++ b/src/component/helper/cursorHelper.ts @@ -20,23 +20,56 @@ import { ElementEvent } from 'zrender/src/Element'; import ExtensionAPI from '../../core/ExtensionAPI'; -import SeriesModel from '../../model/Series'; -import { CoordinateSystem } from '../../coord/CoordinateSystem'; +import { CoordinateSystemHostModel } from '../../coord/CoordinateSystem'; +import type Component from '../../model/Component'; +import { retrieveZInfo } from '../../util/model'; const IRRELEVANT_EXCLUDES = {'axisPointer': 1, 'tooltip': 1, 'brush': 1}; /** - * Avoid that: mouse click on a elements that is over geo or graph, - * but roam is triggered. + * Used on roam/brush triggering determination. + * This is to avoid that: mouse clicking on an elements that is over geo or graph, + * but roam is triggered unexpectedly. */ export function onIrrelevantElement( - e: ElementEvent, api: ExtensionAPI, targetCoordSysModel: CoordinateSystem['model'] + e: ElementEvent, + api: ExtensionAPI, + targetComponent: Component ): boolean { - const model = api.getComponentByElement(e.topTarget); - // If model is axisModel, it works only if it is injected with coordinateSystem. - const coordSys = model && (model as SeriesModel).coordinateSystem; - return model - && model !== targetCoordSysModel - && !IRRELEVANT_EXCLUDES.hasOwnProperty(model.mainType) - && (coordSys && coordSys.model !== targetCoordSysModel); + const eventElComponent = api.getComponentByElement(e.topTarget); + + if (!eventElComponent + || eventElComponent === targetComponent + || IRRELEVANT_EXCLUDES.hasOwnProperty(eventElComponent.mainType) + ) { + return false; + } + + // At present the `true` return is conservative. That is, the caller, such as a RoamController, + // is more likely to get a `false` return and start a roam behavior, becuase even if the + // `model.coordinateSystem` does not exist, the `e.topTarget` may be also a relevant element, + // such as axis split line/area or series elements, where roam should be available. Otherwise, + // if a dataZoom-served cartesian is full of series elements, the dataZoom-roaming can hardly + // be triggered. + const eventElCoordSys = (eventElComponent as CoordinateSystemHostModel).coordinateSystem; + // If eventElComponent is axisModel, it works only if it is injected with coordinateSystem. + if (!eventElCoordSys || eventElCoordSys.model === targetComponent) { + return false; + } + + // e.g., if a cartesian is covered by a graph, the graph has a higher presedence in roam. + // A potential bad case is that RoamController does not prevent the cartesian from handling zr + // event, such as click and hovering, but it's fine so far. + // Aslo be conservative, if equals, return false. + const eventElCmptZInfo = retrieveZInfo(eventElComponent); + const targetCmptZInfo = retrieveZInfo(targetComponent); + if (( + (eventElCmptZInfo.zlevel - targetCmptZInfo.zlevel) + || (eventElCmptZInfo.z - targetCmptZInfo.z) + ) <= 0 + ) { + return false; + } + + return true; } diff --git a/src/component/helper/roamHelper.ts b/src/component/helper/roamHelper.ts index 8f87a9e001..f19a4e5d30 100644 --- a/src/component/helper/roamHelper.ts +++ b/src/component/helper/roamHelper.ts @@ -18,23 +18,34 @@ */ import Element from 'zrender/src/Element'; -import { SeriesModel } from '../../echarts.all'; +import type SeriesModel from '../../model/Series'; import ExtensionAPI from '../../core/ExtensionAPI'; import Group from 'zrender/src/graphic/Group'; import RoamController from './RoamController'; import type { SeriesOption } from '../../export/option'; import type View from '../../coord/View'; -import type { RoamOptionMixin } from '../../util/types'; -import { onIrrelevantElement } from './cursorHelper'; +import type { NullUndefined, RoamOptionMixin, Payload } from '../../util/types'; +import { BoundingRect } from '../../util/graphic'; +import type Geo from '../../coord/geo/Geo'; +import Transformable from 'zrender/src/core/Transformable'; + +export interface ZoomLimit { + max?: number; + min?: number; +} export interface RoamControllerHost { + // Its transform x/y/scaleX/scaleY will be modified when roaming. target: Element; zoom?: number; - zoomLimit?: {min?: number, max?: number}; + zoomLimit?: ZoomLimit; } /** - * For geo and graph. + * [CAVEAT] `updateViewOnPan` and `updateViewOnZoom` modifies the group transform directly, + * but the 'center' and 'zoom' in echarts option and 'View' coordinate system are not updated yet, + * which must be performed later in 'xxxRoam' action by calling `updateCenterAndZoom`. + * @see {updateCenterAndZoomInAction} */ export function updateViewOnPan(controllerHost: RoamControllerHost, dx: number, dy: number) { const target = controllerHost.target; @@ -43,53 +54,52 @@ export function updateViewOnPan(controllerHost: RoamControllerHost, dx: number, target.dirty(); } -/** - * For geo and graph. - */ export function updateViewOnZoom(controllerHost: RoamControllerHost, zoomDelta: number, zoomX: number, zoomY: number) { const target = controllerHost.target; const zoomLimit = controllerHost.zoomLimit; let newZoom = controllerHost.zoom = controllerHost.zoom || 1; newZoom *= zoomDelta; - if (zoomLimit) { - const zoomMin = zoomLimit.min || 0; - const zoomMax = zoomLimit.max || Infinity; - newZoom = Math.max( - Math.min(zoomMax, newZoom), - zoomMin - ); - } + + newZoom = clampByZoomLimit(newZoom, zoomLimit); + const zoomScale = newZoom / controllerHost.zoom; controllerHost.zoom = newZoom; - // Keep the mouse center when scaling - target.x -= (zoomX - target.x) * (zoomScale - 1); - target.y -= (zoomY - target.y) * (zoomScale - 1); - target.scaleX *= zoomScale; - target.scaleY *= zoomScale; - + zoomTransformableByOrigin(target, zoomX, zoomY, zoomScale); target.dirty(); } +/** + * A abstraction for some similar impl in roaming. + */ export function updateController( seriesModel: SeriesModel, api: ExtensionAPI, - group: Group, + pointerCheckerEl: Group, controller: RoamController, controllerHost: RoamControllerHost, + clipRect: BoundingRect | NullUndefined, ) { - controller.setPointerChecker(function (e, x, y) { - const rect = group.getBoundingRect(); - rect.applyTransform(group.transform); - return rect.contain(x, y) - && !onIrrelevantElement(e, api, seriesModel); + const tmpRect = new BoundingRect(0, 0, 0, 0); + controller.enable(seriesModel.get('roam'), { + api, + zInfo: {component: seriesModel}, + triggerInfo: { + roamTrigger: seriesModel.get('roamTrigger'), + isInSelf: function (e, x, y) { + tmpRect.copy(pointerCheckerEl.getBoundingRect()); + tmpRect.applyTransform(pointerCheckerEl.getComputedTransform()); + return tmpRect.contain(x, y); + }, + isInClip: function (e, x, y) { + return !clipRect || clipRect.contain(x, y); + } + } }); - - controller.enable(seriesModel.get('roam')); controllerHost.zoomLimit = seriesModel.get('scaleLimit'); const coordinate = seriesModel.coordinateSystem; controllerHost.zoom = coordinate ? (coordinate as View).getZoom() : 1; - const type = seriesModel.type + 'Roam'; + const type = seriesModel.subType + 'Roam'; controller .off('pan') @@ -104,6 +114,11 @@ export function updateController( }); }) .on('zoom', (e) => { + /** + * FIXME: should do nothing except `api.dispatchAction` here, the other logic + * should be performed in the action handler and `updateTransform`; otherwise, + * they are inconsistent if user triggers this action explicitly. + */ updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); api.dispatchAction({ seriesId: seriesModel.id, @@ -116,3 +131,85 @@ export function updateController( api.updateLabelLayout(); }); } + +export interface RoamPayload extends Payload { + dx: number + dy: number + // This is a delta zoom, not an absolute zoom. + zoom: number + originX: number + originY: number +} + +function getCenterCoord(view: View, point: number[]) { + // Use projected coord as center because it's linear. + return (view as Geo).pointToProjected + ? (view as Geo).pointToProjected(point) + : view.pointToData(point); +} + +/** + * Should be called only in action handler. + * @see {updateViewOnPan|updateViewOnZoom} + */ +export function updateCenterAndZoomInAction( + view: View, + payload: RoamPayload, + zoomLimit?: ZoomLimit +) { + const previousZoom = view.getZoom(); + const center = view.getCenter(); + let deltaZoom = payload.zoom; + + const point = (view as Geo).projectedToPoint + ? (view as Geo).projectedToPoint(center) + : view.dataToPoint(center); + + if (payload.dx != null && payload.dy != null) { + point[0] -= payload.dx; + point[1] -= payload.dy; + + view.setCenter(getCenterCoord(view, point)); + } + if (deltaZoom != null) { + deltaZoom = clampByZoomLimit(previousZoom * deltaZoom, zoomLimit) / previousZoom; + + zoomTransformableByOrigin(view, payload.originX, payload.originY, deltaZoom); + view.updateTransform(); + + // [NOTICE] Tricky: `getCetnerCoord` uses `this.invTransform` modified by the `updateTransform` above. + view.setCenter(getCenterCoord(view, point)); + view.setZoom(deltaZoom * previousZoom); + } + + return { + center: view.getCenter(), + zoom: view.getZoom() + }; +} + + +function zoomTransformableByOrigin( + target: Transformable, + originX: number, + originY: number, + deltaZoom: number, // positive number, 1 means no zooming. +): void { + // Keep the mouse center when scaling. + target.x -= (originX - target.x) * (deltaZoom - 1); + target.y -= (originY - target.y) * (deltaZoom - 1); + target.scaleX *= deltaZoom; + target.scaleY *= deltaZoom; +} + +export function clampByZoomLimit(zoom: number, zoomLimit: ZoomLimit | NullUndefined): number { + if (zoomLimit) { + const zoomMin = zoomLimit.min || 0; + const zoomMax = zoomLimit.max || Infinity; + zoom = Math.max( + Math.min(zoomMax, zoom), + zoomMin + ); + } + return zoom; +} diff --git a/src/component/matrix/MatrixView.ts b/src/component/matrix/MatrixView.ts index e652a01d63..32b10d1bb9 100644 --- a/src/component/matrix/MatrixView.ts +++ b/src/component/matrix/MatrixView.ts @@ -355,6 +355,9 @@ const _tmpCellItemStyleModel = new Model(); const _tmpCellLabelModel = new Model(); const _tmpInnerTextTrans: number[] = []; +// FIXME: move all of the subpixel process to Matrix.ts resize, otherwise the result of +// `dataToLayout` is not consistent with this rendering, and the caller (like heatmap) can +// not precisely align with the matrix border. function createMatrixRect( shape: RectShape, style: ItemStyleProps, z2: number ): Rect { diff --git a/src/coord/View.ts b/src/coord/View.ts index fdbb6beb08..14935ac3d5 100644 --- a/src/coord/View.ts +++ b/src/coord/View.ts @@ -30,9 +30,10 @@ import { CoordinateSystemMaster, CoordinateSystem } from './CoordinateSystem'; import GlobalModel from '../model/Global'; import { ParsedModelFinder, ParsedModelFinderKnown } from '../util/model'; import { parsePercent } from '../util/number'; -import { RoamOptionMixin } from '../util/types'; +import { NullUndefined, RoamOptionMixin } from '../util/types'; import { clone } from 'zrender/src/core/util'; import ExtensionAPI from '../core/ExtensionAPI'; +import { clampByZoomLimit, ZoomLimit } from '../component/helper/roamHelper'; const v2ApplyTransform = vector.applyTransform; @@ -47,10 +48,7 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy readonly name: string; - zoomLimit: { - max?: number; - min?: number; - }; + zoomLimit: ZoomLimit; /** * Represents the transform brought by roam/zoom. @@ -89,9 +87,19 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy */ private _viewRect: BoundingRect; - constructor(name?: string) { + private _opt: {ecModel: GlobalModel, api: ExtensionAPI} | NullUndefined; + + constructor( + name?: string, + opt?: { + // Only for backward compat. + ecModel: GlobalModel, + api: ExtensionAPI + } + ) { super(); this.name = name; + this._opt = opt; } setBoundingRect(x: number, y: number, width: number, height: number): BoundingRect { @@ -100,13 +108,14 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy return this._rect; } - /** - * @return {module:zrender/core/BoundingRect} - */ getBoundingRect(): BoundingRect { return this._rect; } + /** + * If no need to transform `View['_rect']` to `View['_viewRect']`, the calling of + * `setViewRect` can be omitted. + */ setViewRect(x: number, y: number, width: number, height: number): void { this._transformTo(x, y, width, height); this._viewRect = new BoundingRect(x, y, width, height); @@ -139,18 +148,14 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy * @see {View['_center']} for details. */ setCenter( - centerCoord: RoamOptionMixin['center'], - opt?: { - // Only for backward compat. - ecModel?: GlobalModel - api?: ExtensionAPI - } + centerCoord: RoamOptionMixin['center'] ): void { // #16904 introcuded percentage string here, such as '33%'. But it was based on canvas // width/height, which is not reasonable - the unit may incorrect, and it is unpredictable if // the `View['_rect']` is not calculated based on the current canvas rect. Therefore the percentage // value is changed to based on `View['_rect'].width/height` since v6. Under this definition, users // can use '0%' to map the top-left of `View['_rect']` to the center of `View['_viewRect']`. + const opt = this._opt; if (opt && opt.api && opt.ecModel && opt.ecModel.getShallow('legacyViewCoordSysCenterBase') && centerCoord) { centerCoord = [ parsePercent(centerCoord[0], opt.api.getWidth()), @@ -163,19 +168,7 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy } setZoom(zoom: number): void { - zoom = zoom || 1; - - const zoomLimit = this.zoomLimit; - if (zoomLimit) { - if (zoomLimit.max != null) { - zoom = Math.min(zoomLimit.max, zoom); - } - if (zoomLimit.min != null) { - zoom = Math.max(zoomLimit.min, zoom); - } - } - this._zoom = zoom; - + this._zoom = clampByZoomLimit(zoom || 1, this.zoomLimit); this._updateCenterAndZoom(); } diff --git a/src/coord/geo/Geo.ts b/src/coord/geo/Geo.ts index e4ff951f86..62db6efca6 100644 --- a/src/coord/geo/Geo.ts +++ b/src/coord/geo/Geo.ts @@ -25,9 +25,10 @@ import { GeoJSONRegion, Region } from './Region'; import { GeoProjection, GeoResource, NameMap } from './geoTypes'; import GlobalModel from '../../model/Global'; import { ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING } from '../../util/model'; -import GeoModel from './GeoModel'; +import type GeoModel from './GeoModel'; import { resizeGeoType } from './geoCreator'; import { warn } from '../../util/log'; +import type ExtensionAPI from '../../core/ExtensionAPI'; const GEO_DEFAULT_PARAMS: { [type in GeoResource['type']]: { @@ -79,9 +80,11 @@ class Geo extends View { nameMap?: NameMap; nameProperty?: string; aspectScale?: number; + api: ExtensionAPI; + ecModel: GlobalModel; } ) { - super(name); + super(name, {api: opt.api, ecModel: opt.ecModel}); this.map = map; diff --git a/src/coord/geo/GeoModel.ts b/src/coord/geo/GeoModel.ts index 215c47e208..72f0252bd6 100644 --- a/src/coord/geo/GeoModel.ts +++ b/src/coord/geo/GeoModel.ts @@ -36,7 +36,8 @@ import { StatesOptionMixin, Dictionary, CommonTooltipOption, - StatesMixinBase + StatesMixinBase, + PreserveAspectMixin } from '../../util/types'; import { GeoProjection, NameMap } from './geoTypes'; import GlobalModel from '../../model/Global'; @@ -79,8 +80,7 @@ export interface GeoTooltipFormatterParams { $vars: ['name'] } - -export interface GeoCommonOptionMixin extends RoamOptionMixin { +export interface GeoCommonOptionMixin extends RoamOptionMixin, PreserveAspectMixin { // Map name map: string; @@ -96,6 +96,10 @@ export interface GeoCommonOptionMixin extends RoamOptionMixin { // Like: `40` or `'50%'`. layoutSize?: number | string; + // Whether to clip by the viewRect (as a viewport, decided by + // `BoxLayoutOptionMixin` (or `layoutCenter`/`layoutSize`) and `PreserveAspectMixin`) + clip?: boolean + // Define left-top, right-bottom lng/lat coords to control view // For example, [ [180, 90], [-180, -90] ] // higher priority than center and zoom diff --git a/src/coord/geo/geoCreator.ts b/src/coord/geo/geoCreator.ts index dbdbd35da5..d41f72b545 100644 --- a/src/coord/geo/geoCreator.ts +++ b/src/coord/geo/geoCreator.ts @@ -144,11 +144,12 @@ function resizeGeo(this: Geo, geoModel: ComponentModel { if (model.preventAutoZ) { return; } - const z = model.get('z') || 0; - const zlevel = model.get('zlevel') || 0; + const zInfo = modelUtil.retrieveZInfo(model); // Set z and zlevel view.eachRendered((el) => { - doUpdateZ(el, z, zlevel, -Infinity, false); + doUpdateZ(el, zInfo.z, zInfo.zlevel, -Infinity, false); // Don't traverse the children because it has been traversed in _updateZ. return true; }); diff --git a/src/model/Component.ts b/src/model/Component.ts index 9c20b276a5..29fdf0be17 100644 --- a/src/model/Component.ts +++ b/src/model/Component.ts @@ -308,15 +308,8 @@ class ComponentModel extends Mode getBoxLayoutParams() { // Consider itself having box layout configs. - const boxLayoutModel = this as Model; - return { - left: boxLayoutModel.get('left'), - top: boxLayoutModel.get('top'), - right: boxLayoutModel.get('right'), - bottom: boxLayoutModel.get('bottom'), - width: boxLayoutModel.get('width'), - height: boxLayoutModel.get('height') - }; + // For backward compatibility, by default do not `ignoreParent`. + return layout.getBoxLayoutParams(this as Model, false); } /** diff --git a/src/util/layout.ts b/src/util/layout.ts index 8578bdaad4..b8a8dc9bf7 100644 --- a/src/util/layout.ts +++ b/src/util/layout.ts @@ -24,17 +24,20 @@ import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import {parsePercent} from './number'; import * as formatUtil from './format'; import { - BoxLayoutOptionMixin, CircleLayoutOptionMixin, NullUndefined, ComponentLayoutMode, SeriesOption + BoxLayoutOptionMixin, CircleLayoutOptionMixin, NullUndefined, ComponentLayoutMode, SeriesOption, + PreserveAspectMixin, + ComponentOption } from './types'; import Group from 'zrender/src/graphic/Group'; import { SectorShape } from 'zrender/src/graphic/shape/Sector'; import Element from 'zrender/src/Element'; import { Dictionary } from 'zrender/src/core/types'; -import { ComponentModel } from '../echarts.all'; import ExtensionAPI from '../core/ExtensionAPI'; import { error } from './log'; import { BoxCoordinateSystemCoordFrom, getCoordForBoxCoordSys } from '../core/CoordinateSystem'; import SeriesModel from '../model/Series'; +import type Model from '../model/Model'; +import type ComponentModel from '../model/Component'; const each = zrUtil.each; @@ -164,6 +167,17 @@ export const vbox = zrUtil.curry(boxLayout, 'vertical'); export const hbox = zrUtil.curry(boxLayout, 'horizontal'); +export function getBoxLayoutParams(boxLayoutModel: Model, ignoreParent: boolean) { + return { + left: boxLayoutModel.getShallow('left', ignoreParent), + top: boxLayoutModel.getShallow('top', ignoreParent), + right: boxLayoutModel.getShallow('right', ignoreParent), + bottom: boxLayoutModel.getShallow('bottom', ignoreParent), + width: boxLayoutModel.getShallow('width', ignoreParent), + height: boxLayoutModel.getShallow('height', ignoreParent) + }; +} + type CircleLayoutSeriesOption = SeriesOption & CircleLayoutOptionMixin<{ // `center: string | number` has been accepted in series.pie. centerExtra: string | number @@ -245,7 +259,16 @@ type GetLayoutRectInputContainerRect = { */ export function getLayoutRect( positionInfo: BoxLayoutOptionMixin & { - aspect?: number // aspect is width / height + // PENDING: + // when width can not be decided but height can be decided and aspect is near Infinity, + // or when height can not be decided but width can be decided and aspect is near 0, + // the result width or height is near Inifity. It's logically correct, therefore + // currently we do not handle it, until bad cases arise. + // + // aspect is width / height. But this method does not preserve aspect ratio if + // both width and height can be decided by the given left/top/bottom/right/width/height. + // To always preserve aspect ratio, uses `applyPreserveAspect` to prcess the result. + aspect?: number }, containerRect: GetLayoutRectInputContainerRect, // This is the space from the `containerRect` to the returned bounding rect. @@ -294,6 +317,9 @@ export function getLayoutRect( // Margin is not considered, because there is no case that both // using margin and aspect so far. if (isNaN(width) && isNaN(height)) { + // PENDING: if only `left` or `right` is defined, perhaps it's more preferable to + // calculate size based on `containerWidth - left` or `containerWidth - left` here, + // but for backward compatibility we do not change it. if (aspect > containerWidth / containerHeight) { width = containerWidth * 0.8; } @@ -360,6 +386,52 @@ export function getLayoutRect( return rect; } +/** + * PENDING: + * when preserveAspect: 'cover' and aspect is near Infinity + * or when preserveAspect: 'contain' and aspect is near 0, + * the result width or height is near Inifity. It's logically correct, + * Therefore currently we do not handle it, until bad cases arise. + */ +export function applyPreserveAspect( + component: ComponentModel, + layoutRect: LayoutRect, + // That is, `width / height`. + // Assume `aspect` is positive. + aspect: number, +): LayoutRect { + const preserveAspect = component.getShallow('preserveAspect', true); + if (!preserveAspect) { + return layoutRect; + } + + const actualAspect = layoutRect.width / layoutRect.height; + + if (Math.abs(Math.atan(aspect) - Math.atan(actualAspect)) < 1e-9) { + return layoutRect; + } + + const preserveAspectAlign = component.getShallow('preserveAspectAlign', true); + const preserveAspectVerticalAlign = component.getShallow('preserveAspectVerticalAlign', true); + const layoutOptInner: BoxLayoutOptionMixin = {width: layoutRect.width, height: layoutRect.height}; + const isCover = preserveAspect === 'cover'; + + if ((actualAspect > aspect && !isCover) || (actualAspect < aspect && isCover)) { + layoutOptInner.width = layoutRect.height * aspect; + preserveAspectAlign === 'left' ? (layoutOptInner.left = 0) + : preserveAspectAlign === 'right' ? (layoutOptInner.right = 0) + : (layoutOptInner.left = 'center'); + } + else { + layoutOptInner.height = layoutRect.width / aspect; + preserveAspectVerticalAlign === 'top' ? (layoutOptInner.top = 0) + : preserveAspectVerticalAlign === 'bottom' ? (layoutOptInner.bottom = 0) + : (layoutOptInner.top = 'middle'); + } + return getLayoutRect(layoutOptInner, layoutRect); +} + + type CreateBoxLayoutReferenceOpt = { // Use this only if: // - Intending to layout based on coord sys that can not get a rect from `dataToLayout`. diff --git a/src/util/model.ts b/src/util/model.ts index dbeee76fd0..18814a33ac 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -54,6 +54,7 @@ import CartesianAxisModel from '../coord/cartesian/AxisModel'; import GridModel from '../coord/cartesian/GridModel'; import { isNumeric, getRandomIdBase, getPrecision, round } from './number'; import { error, warn } from './log'; +import type Model from '../model/Model'; function interpolateNumber(p0: number, p1: number, percent: number): number { return (p1 - p0) * percent + p0; @@ -1113,6 +1114,18 @@ export function interpolateRawValues( } } +export function retrieveZInfo( + model: Model>>, +): { + z: ComponentOption['z'] + zlevel: ComponentOption['zlevel'] +} { + return { + z: model.get('z') || 0, + zlevel: model.get('zlevel') || 0, + }; +} + /** * Use an iterator to avoid exposing the internal list or duplicating it * for the outside traveller, and no extra heap allocation. diff --git a/src/util/types.ts b/src/util/types.ts index 62c37ee365..93ed67d585 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1062,6 +1062,24 @@ export interface RoamOptionMixin { * If enable roam. can be specified 'scale' or 'move' */ roam?: boolean | 'pan' | 'move' | 'zoom' | 'scale' + /** + * Hover over an area where roaming is triggered. + * - if `null`/`undefined`, the trigger area is + * the intersection of "self bounding rect" and "clipping rect (if any)". + * - if 'global', the trigger area is + * the intersection of "the entire canvas" and "clipping rect (if any)". + * NOTE: + * The clipping rect, which can be enabled by `clip: true`, is typically the layout rect. + * The layout rect is typically determined by option `left`/`right`/`top`/`bottom`/`width`/`height`, some + * components/series, such as `geo` and `series.map` can also be determined by `layoutCenter`/`layoutSize`, + * and may modified by `preserveAspect`. + * + * PENDING: do we need to support to only trigger roaming on the shapes themselves, + * rather than the bounding rect? + * PENDING: do we need to support to check by the laytout rect? But in this case, + * `roamTrigger: 'global', clip: true` is more reasonable. + */ + roamTrigger?: 'global' | 'selfRect' | NullUndefined /** * Current center position. */ @@ -1077,6 +1095,21 @@ export interface RoamOptionMixin { } } +export interface PreserveAspectMixin { + // Suppose a "viewport" is decided by `left`/`right`/`top`/`bottom`/`width`/`height`, + // - null/undefined/false (default): aspect ratio will not be preserved, but stretched to fill + // the "viewport". + // - 'contain'/true: The aspect ratio is preserved; the viewRect is contained by the "viewport", + // and scaled up as much as possible to meet the "viewport". + // - 'cover': The aspect ratio is preserved; the viewRect covers the "viewport", and scaled down + // as much as possible to meet the "viewport". + preserveAspect?: boolean | 'contain' | 'cover'; + // By default 'center' + preserveAspectAlign?: 'left' | 'right' | 'center'; + // By default 'middle' + preserveAspectVerticalAlign?: 'top' | 'bottom' | 'middle'; +} + // TODO: TYPE value type? export type SymbolSizeCallback = (rawValue: any, params: T) => number | number[]; export type SymbolCallback = (rawValue: any, params: T) => string; diff --git a/test/build/mktest-tpl.html b/test/build/mktest-tpl.html index fd18c5999f..15a0cec0a7 100644 --- a/test/build/mktest-tpl.html +++ b/test/build/mktest-tpl.html @@ -57,7 +57,7 @@ require([ 'echarts', - // 'data/life-expectancy-table.json' + // 'data/flight.json' // 'theme/dark.js', // auto register if load. ], function (echarts /*, data */) { diff --git a/test/geo-map-roam.html b/test/geo-map-roam.html new file mode 100644 index 0000000000..6d943b2dd7 --- /dev/null +++ b/test/geo-map-roam.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + diff --git a/test/geo-svg-demo.html b/test/geo-svg-demo.html index 4f76e38b8f..ab99d94a84 100644 --- a/test/geo-svg-demo.html +++ b/test/geo-svg-demo.html @@ -100,6 +100,7 @@ tooltip: { }, geo: { + clip: true, left: 10, right: '50%', map: 'seatmap', diff --git a/test/graph-layout-roam.html b/test/graph-layout-roam.html index d1e5524090..8dcaa8ca97 100644 --- a/test/graph-layout-roam.html +++ b/test/graph-layout-roam.html @@ -40,42 +40,102 @@ const _ctx = { center: null, - }; - const layoutParams = { - left: 'center', - top: 'center', + layoutParams: { + left: 'center', + top: 'center', + }, }; - var option = { - backgroundColor: '#f4f4f9', - tooltip: {}, - graphic: { - elements: [{ - id: 'geo_boundary_indicator', - type: 'rect', - style: { - fill: '#e0e0e0', - stroke: '#aaa', - lineWidth: 1, + const _graphDataset = { + fourNodes: { + data: [{ + name: 'node_1', + x: 300, + y: 300, + }, { + name: 'node_2', + x: 800, + y: 300 + }, { + name: 'node_3', + x: 550, + y: 100 + }, { + name: 'node_4', + x: 550, + y: 500 + }], + links: [{ + source: 0, + target: 1, + symbolSize: [5, 20], + lineStyle: { + width: 5, + opacity: 1, + curveness: 0.2 }, - textContent: { - type: 'text', - style: { - text: 'graph view rect', - fill: '#777', - fontSize: 10, + emphasis: { + lineStyle: { + color: 'blue', + width: 20, + opacity: 0.1 }, - }, - textConfig: { - position: 'insideTopRight' + label: { + fontSize: 40, + color: 'red' + } + } + }, { + source: 'node_2', + target: 'node_1', + lineStyle: { + curveness: 0.2 + } + }, { + source: 'node_1', + target: 'node_3', + emphasis: { + label: { + show: true + } } + }, { + source: 'node_2', + target: 'node_3' + }, { + source: 'node_2', + target: 'node_4' + }, { + source: 'node_1', + target: 'node_4' }] }, - series : [ + heightZero: { + data: [{ + name: 'node_1', + x: 300, + y: 300, + }, { + name: 'node_2', + x: 800, + y: 300 + }], + links: [{ + source: 0, + target: 1, + symbolSize: [5, 20], + }] + } + }; + + var option = { + backgroundColor: '#f4f4f9', + tooltip: {}, + series: [ { type: 'graph', id: 'graph0', - symbolSize: 40, + symbolSize: 20, roam: true, itemStyle: { color: 'rgba(100,100,0,0.5)', @@ -87,16 +147,14 @@ return `series.data[i].x: ${param.data.x}\nseries.data[i].y: ${param.data.y}` } }, - ...layoutParams, + ..._ctx.layoutParams, center: _ctx.center, edgeSymbol: ['circle', 'arrow'], edgeSymbolSize: [4, 10], edgeLabel: { - normal: { - textStyle: { - color: 'green', - fontSize: 30 - } + textStyle: { + color: 'green', + fontSize: 30 }, emphasis: { textStyle: { @@ -105,78 +163,87 @@ } }, // focusNodeAdjacency: focusNodeAdjacency, - data: [{ - name: 'node_1', - x: 300, - y: 300, - }, { - name: 'node_2', - x: 800, - y: 300 - }, { - name: 'node_3', - x: 550, - y: 100 - }, { - name: 'node_4', - x: 550, - y: 500 - }], lineStyle: { - normal: { - width: 3, - color: '#184029', - curveness: 0 - } + width: 3, + color: '#184029', + curveness: 0 }, - links: [{ - source: 0, - target: 1, - symbolSize: [5, 20], - lineStyle: { - width: 5, - opacity: 1, - curveness: 0.2 - }, - emphasis: { - lineStyle: { - color: 'blue', - width: 20, - opacity: 0.1 - }, - label: { - fontSize: 40, - color: 'red' - } - } - }, { - source: 'node_2', - target: 'node_1', - lineStyle: { - curveness: 0.2 - } - }, { - source: 'node_1', - target: 'node_3', - emphasis: { - label: { - show: true - } - } - }, { - source: 'node_2', - target: 'node_3' - }, { - source: 'node_2', - target: 'node_4' - }, { - source: 'node_1', - target: 'node_4' - }] + ..._graphDataset.fourNodes, } ] }; + + function makeRectIndicators(option) { + var graphicOption = option.graphic = option.graphic || {}; + graphicElsOption = graphicOption.elements = graphicOption.elements || []; + graphicElsOption.push({ + id: 'view_rect_indicator', + type: 'rect', + silent: true, + z: -9, + style: { + // fill: '#e0e0e0', + fill: 'none', + stroke: '#999', + lineWidth: 1, + lineDash: 'dashed', + }, + textContent: { + type: 'text', + style: { + text: 'view rect', + fill: '#555', + fontSize: 11, + }, + }, + textConfig: { + position: 'insideTopRight' + } + }, { + id: 'content_rect_indicator', + type: 'rect', + silent: true, + z: -10, + style: { + // fill: 'rgba(150,100,50,0.3)', + fill: 'none', + stroke: '#999', + lineWidth: 1, + lineDash: 'dashed', + }, + textContent: { + type: 'text', + style: { + text: 'content bounding rect', + fill: '#555', + fontSize: 11, + }, + }, + textConfig: { + position: 'insideTopRight' + } + }); + + var gridOptions = option.grid = option.grid || []; + if (!Array.isArray(gridOptions)) { + gridOptions = option.grid = [option.grid]; + } + gridOptions.push({ + id: 'viewport_indicator', + show: true, + // Using `grid` as a indicator is a tricky way - should no bounding rect, + // otherwise it affect the trigger of geo roam due to `onIrrelevantElement`. + backgroundColor: '#ccd', + silent: false, + ..._ctx.layoutParams, + borderColor: '#aaa', + borderType: 'dashed', + z: -100, + }); + } + makeRectIndicators(option); + var chart = testHelper.create(echarts, 'main0', { title: [ 'graph layout center', @@ -186,16 +253,31 @@ option: option, draggable: true, onResize() { - renderViewRect(); + renderIndicatorRect(); }, + height: 300, inputsStyle: 'compact', inputs: [ + { + type: 'select', + text: 'graph data:', + values: Object.keys(_graphDataset), + onchange() { + const dataKey = this.value; + chart.setOption({ + series: { + id: 'graph0', + ..._graphDataset[dataKey], + } + }); + } + }, { type: 'select', text: 'geo box:', values: [ 'do_nothing', - {left: 50, top: 50, right: '30%', bottom: 30}, + {left: 120, top: 100, right: '30%', bottom: 80}, {left: null, top: null, right: null, bottom: null}, ], onchange: function () { @@ -203,25 +285,35 @@ if (layoutParams == null) { return; } + _ctx.layoutParams = layoutParams; chart.setOption({ series: { id: 'graph0', - ...layoutParams, + ..._ctx.layoutParams, // zoom: 1, // center: [0, 0], }, - grid: { - id: 'geo_boundary_indicator', - ...layoutParams - }, }); - renderViewRect(); + renderIndicatorRect(); } }, { type: 'br', }, + { + text: 'graph layout:', + type: 'select', + values: [undefined, 'force', 'circular', 'none'], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + layout: this.value, + }, + }); + }, + }, { text: 'legacyViewCoordSysCenterBase:', type: 'select', @@ -234,7 +326,35 @@ }, { type: 'select', - text: 'specify center', + text: 'roamTrigger:', + values: [undefined, 'global'], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + roamTrigger: this.value, + } + }); + renderIndicatorRect(); + } + }, + { + text: 'zoom:', + type: 'select', + values: [undefined, 0.5, 1, 5], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + zoom: this.value, + } + }); + renderIndicatorRect(); + } + }, + { + type: 'select', + text: 'specify center:', values: [false, true], onchange() { chart.__testHelper.switchGroup( @@ -252,7 +372,7 @@ center: _ctx.center } }); - renderViewRect(); + renderIndicatorRect(); } }, { @@ -260,7 +380,7 @@ }, { type: 'groupset', - inputsHeight: 70, + inputsHeight: 40, groups: [{ id: 'group_no_center', text: 'no center', @@ -297,33 +417,89 @@ } }); - renderViewRect(); + renderIndicatorRect(); } })) ] }] }, // End of groupset + { + type: 'select', + text: 'preserveAspect:', + values: [undefined, 'contain', 'cover'], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + preserveAspect: this.value + } + }); + renderIndicatorRect(); + } + }, + { + type: 'select', + text: 'preserveAspectAlign:', + values: [undefined, 'left', 'right', 'center'], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + preserveAspectAlign: this.value + } + }); + renderIndicatorRect(); + } + }, + { + type: 'select', + text: 'preserveAspectVerticalAlign:', + values: [undefined, 'top', 'bottom', 'middle'], + onchange() { + chart.setOption({ + series: { + id: 'graph0', + preserveAspectVerticalAlign: this.value + } + }); + renderIndicatorRect(); + } + } ] // End of inputs }); - renderViewRect(); + renderIndicatorRect(); + + if (chart) { + chart.on('graphroam', function () { + renderIndicatorRect(); + }); + } - function renderViewRect() { + function renderIndicatorRect() { if (!chart) { return; } // [CAVEAT] Internal data structure, probably change, do not use it outside. - const viewRect = chart.getModel().getComponent('series', 0).coordinateSystem.getViewRect(); + const viewCoordSys = chart.getModel().getComponent('series', 0).coordinateSystem; + const viewRect = viewCoordSys.getViewRect(); + const contentBoundingRect = viewCoordSys.getBoundingRect().clone(); + const trans = viewCoordSys.getComputedTransform(); + if (trans) { + contentBoundingRect.applyTransform(trans); + } chart.setOption({ + grid: { + id: 'viewport_indicator', + ..._ctx.layoutParams, + }, graphic: { elements: [{ - id: 'geo_boundary_indicator', - shape: { - x: viewRect.x, - y: viewRect.y, - width: viewRect.width, - height: viewRect.height, - } + id: 'view_rect_indicator', + shape: viewRect, + }, { + id: 'content_rect_indicator', + shape: contentBoundingRect, }] }, }); diff --git a/test/matrix.html b/test/matrix.html index 20e744c2ec..ba553194f5 100644 --- a/test/matrix.html +++ b/test/matrix.html @@ -69,6 +69,11 @@ }, y: { data: ['U', 'V'] + }, + body: { + itemStyle: { + // borderColor: '#333', + }, } }, visualMap: { @@ -77,13 +82,17 @@ max: 80, top: 'middle', dimension: 2, + color: ['rgba(100,204,105,0.4)', 'rgba(3,4,5,0.4)'], calculable: true }, series: { type: 'heatmap', coordinateSystem: 'matrix', + itemStyle: { + borderRadius: 10, + }, data: [ - ['A1', 'U', 10], ['A1', 'V', 20], ['A2', 'U', 30], + ['A1', 'U', 10], ['A1', 'V', 200], ['A2', 'U', 300], ['A2', 'V', 40], ['A31', 'U', 50], ['A3', 'V', 60] ], label: { @@ -105,8 +114,6 @@ diff --git a/test/tmp-base.html b/test/tmp-base.html index f81ce9ef5e..7c03de1db4 100644 --- a/test/tmp-base.html +++ b/test/tmp-base.html @@ -51,7 +51,7 @@ require([ 'echarts', - // 'data/life-expectancy-table.json' + // 'data/flight.json' // 'theme/dark.js', // auto register if load. ], function (echarts /*, data */) { diff --git a/test/tree-roam.html b/test/tree-roam.html index d207a8e98a..831c742ccc 100644 --- a/test/tree-roam.html +++ b/test/tree-roam.html @@ -25,29 +25,37 @@ + + + + + -
+ + +
+ + + \ No newline at end of file diff --git a/test/treemap-simple2.html b/test/treemap-simple2.html index 305bffd226..c78b71b7cc 100644 --- a/test/treemap-simple2.html +++ b/test/treemap-simple2.html @@ -39,6 +39,8 @@
+
+ @@ -132,6 +134,107 @@ + + + + + + + From 74af455d40fb3a1e8cdeae1692535b8ca3dd5d23 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 19 Jun 2025 22:22:25 +0800 Subject: [PATCH 46/49] feat(thumbnail): (1) Refactor thumbnail to a component for further extension, and add test cases. (2) Fix RoamController cursor style. --- package.json | 1 + src/chart/graph/GraphSeries.ts | 5 - src/chart/graph/GraphView.ts | 235 +++++++------ src/chart/graph/Thumbnail.ts | 275 --------------- src/chart/graph/install.ts | 23 +- src/component/geo/install.ts | 18 +- src/component/helper/RoamController.ts | 68 +++- src/component/helper/cursorHelper.ts | 2 +- src/component/helper/thumbnailBridge.ts | 91 +++++ src/component/thumbnail.ts | 25 ++ .../thumbnail/ThumbnailBridgeImpl.ts | 93 +++++ src/component/thumbnail/ThumbnailModel.ts | 161 +++++++++ src/component/thumbnail/ThumbnailView.ts | 322 ++++++++++++++++++ src/component/thumbnail/install.ts | 27 ++ src/component/timeline.ts | 2 +- src/core/ExtensionAPI.ts | 1 + src/core/echarts.ts | 86 ++--- src/echarts.all.ts | 3 + src/export/components.ts | 4 + src/export/option.ts | 3 + src/util/graphic.ts | 127 ++++++- src/util/model.ts | 12 - test/graph-layout-roam.html | 1 + test/graph-thumbnail.html | 170 +++++++-- test/matrix3.html | 10 + 25 files changed, 1245 insertions(+), 520 deletions(-) delete mode 100644 src/chart/graph/Thumbnail.ts create mode 100644 src/component/helper/thumbnailBridge.ts create mode 100644 src/component/thumbnail.ts create mode 100644 src/component/thumbnail/ThumbnailBridgeImpl.ts create mode 100644 src/component/thumbnail/ThumbnailModel.ts create mode 100644 src/component/thumbnail/ThumbnailView.ts create mode 100644 src/component/thumbnail/install.ts diff --git a/package.json b/package.json index e9f7c5f7f2..e1ff68498b 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,7 @@ "./lib/component/brush": "./lib/component/brush.js", "./lib/component/calendar": "./lib/component/calendar.js", "./lib/component/matrix": "./lib/component/matrix.js", + "./lib/component/thumbnail": "./lib/component/thumbnail.js", "./lib/component/dataZoom": "./lib/component/dataZoom.js", "./lib/component/dataZoomInside": "./lib/component/dataZoomInside.js", "./lib/component/dataZoomSelect": "./lib/component/dataZoomSelect.js", diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts index 8762238e49..88aee86d30 100644 --- a/src/chart/graph/GraphSeries.ts +++ b/src/chart/graph/GraphSeries.ts @@ -55,7 +55,6 @@ import { LineDataVisual } from '../../visual/commonVisualTypes'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip'; import {initCurvenessList, createEdgeMapForCurveness} from '../helper/multipleGraphEdgeHelper'; -import Thumbnail, { ThumbnailOption } from './Thumbnail'; import tokens from '../../visual/tokens'; @@ -232,8 +231,6 @@ export interface GraphSeriesOption * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set */ autoCurveness?: boolean | number | number[] - - thumbnail?: ThumbnailOption } class GraphSeriesModel extends SeriesModel { @@ -516,8 +513,6 @@ class GraphSeriesModel extends SeriesModel { borderColor: tokens.color.primary } }, - - thumbnail: Thumbnail.defaultOption }; } diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index 3dab5ebb8c..a8e4dccd16 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -23,7 +23,8 @@ import RoamController from '../../component/helper/RoamController'; import { updateViewOnZoom, updateViewOnPan, - RoamControllerHost + RoamControllerHost, + RoamPayload } from '../../component/helper/roamHelper'; import * as graphic from '../../util/graphic'; import adjustEdge from './adjustEdge'; @@ -38,13 +39,13 @@ import Symbol from '../helper/Symbol'; import SeriesData from '../../data/SeriesData'; import Line from '../helper/Line'; import { getECData } from '../../util/innerStore'; -import Thumbnail from './Thumbnail'; import { simpleLayoutEdge } from './simpleLayoutHelper'; import { circularLayout, rotateNodeLabel } from './circularLayoutHelper'; import { clone, extend } from 'zrender/src/core/util'; import ECLinePath from '../helper/LinePath'; import { NullUndefined } from '../../util/types'; +import { getThumbnailBridge, ThumbnailBridge } from '../../component/helper/thumbnailBridge'; function isViewCoordSys(coordSys: CoordinateSystem): coordSys is View { return coordSys.type === 'view'; @@ -65,12 +66,12 @@ class GraphView extends ChartView { private _model: GraphSeriesModel; + private _api: ExtensionAPI; + private _layoutTimeout: number; private _layouting: boolean; - private _thumbnail: Thumbnail = new Thumbnail(); - private _mainGroup: graphic.Group; init(ecModel: GlobalModel, api: ExtensionAPI) { @@ -100,6 +101,12 @@ class GraphView extends ChartView { let isForceLayout = false; this._model = seriesModel; + this._api = api; + + const thumbnailInfo = this._getThumbnailInfo(); + if (thumbnailInfo) { + thumbnailInfo.bridge.reset(api); + } const symbolDraw = this._symbolDraw; const lineDraw = this._lineDraw; @@ -218,7 +225,10 @@ class GraphView extends ChartView { }); this._firstRender = false; - isForceLayout || this._renderThumbnail(seriesModel, api, this._symbolDraw, this._lineDraw); + // Force layout will render thumbnail when layout is finished. + if (!isForceLayout) { + this._renderThumbnail(seriesModel, api, this._symbolDraw, this._lineDraw); + } } dispose() { @@ -226,7 +236,6 @@ class GraphView extends ChartView { this._controller && this._controller.dispose(); this._controllerHost = null; - this._thumbnail.dispose(); } private _startForceLayoutIteration( @@ -235,12 +244,14 @@ class GraphView extends ChartView { layoutAnimation?: boolean ) { const self = this; + let firstRendered = false; (function step() { forceLayout.step(function (stopped) { - if (stopped) { + self.updateLayout(self._model); + if (stopped || !firstRendered) { + firstRendered = true; self._renderThumbnail(self._model, api, self._symbolDraw, self._lineDraw); } - self.updateLayout(self._model); (self._layouting = !stopped) && ( layoutAnimation ? (self._layoutTimeout = setTimeout(step, 16) as any) @@ -279,56 +290,55 @@ class GraphView extends ChartView { .off('pan') .off('zoom') .on('pan', (e) => { - this._updateViewOnPan(seriesModel, api, e.dx, e.dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + dx: e.dx, + dy: e.dy + }); }) .on('zoom', (e) => { - this._updateViewOnZoom(seriesModel, api, e.scale, e.originX, e.originY); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + zoom: e.scale, + originX: e.originX, + originY: e.originY + }); }); } - private _updateViewOnPan( + /** + * A performance shortcut - called by action handler to update the view directly + * without any data/visual processing (which are assumed to be unchanged), while + * ensuring consistent behavior between internal and external action triggers. + */ + updateViewOnPan( seriesModel: GraphSeriesModel, api: ExtensionAPI, - dx: number, - dy: number + params: Pick ): void { - // FIXME: should do nothing except `api.dispatchAction` here, the other logic - // should be performed in the action handler; otherwise, it causes inconsistency - // if user triggers this action explicitly. - updateViewOnPan(this._controllerHost, dx, dy); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'graphRoam', - dx: dx, - dy: dy - }); - this._thumbnail.updateWindow(); + updateViewOnPan(this._controllerHost, params.dx, params.dy); + this._updateThumbnailWindow(); } - private _updateViewOnZoom( + /** + * A performance shortcut - called by action handler to update the view directly + * without any data/visual processing (which are assumed to be unchanged), while + * ensuring consistent behavior between internal and external action triggers. + */ + updateViewOnZoom( seriesModel: GraphSeriesModel, api: ExtensionAPI, - scale: number, - originX: number, - originY: number + params: Pick ) { - // FIXME: should do nothing except `api.dispatchAction` here, the other logic - // should be performed in the action handler; otherwise, it causes inconsistency - // if user triggers this action explicitly. - updateViewOnZoom(this._controllerHost, scale, originX, originY); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'graphRoam', - zoom: scale, - originX: originX, - originY: originY - }); + updateViewOnZoom(this._controllerHost, params.zoom, params.originX, params.originY); this._updateNodeAndLinkScale(); adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); this._lineDraw.updateLayout(); // Only update label layout on zoom api.updateLabelLayout(); - this._thumbnail.updateWindow(); + this._updateThumbnailWindow(); } private _updateNodeAndLinkScale() { @@ -357,84 +367,99 @@ class GraphView extends ChartView { this._symbolDraw && this._symbolDraw.remove(); this._lineDraw && this._lineDraw.remove(); this._controller && this._controller.disable(); - this._thumbnail.remove(); } - // TODO: register thumbnail (consider code size). + /** + * Get thumbnail data structure only if supported. + */ + private _getThumbnailInfo(): { + bridge: ThumbnailBridge + coordSys: View + } | NullUndefined { + const model = this._model; + const coordSys = model.coordinateSystem; + if (coordSys.type !== 'view') { + return; + } + const bridge = getThumbnailBridge(model); + if (!bridge) { + return; + } + return { + bridge, + coordSys: coordSys as View, + }; + } + + private _updateThumbnailWindow() { + const info = this._getThumbnailInfo(); + if (info) { + info.bridge.updateWindow(info.coordSys.transform, this._api); + } + } + private _renderThumbnail( seriesModel: GraphSeriesModel, api: ExtensionAPI, symbolDraw: SymbolDraw, lineDraw: LineDraw ) { - const thumbnail = this._thumbnail; - this.group.add(thumbnail.group); - - const renderThumbnailContent = (viewGroup: graphic.Group) => { - const symbolNodes = symbolDraw.group.children(); - const lineNodes = lineDraw.group.children(); - - const lineGroup = new graphic.Group(); - const symbolGroup = new graphic.Group(); - viewGroup.add(symbolGroup); - viewGroup.add(lineGroup); - - for (let i = 0; i < symbolNodes.length; i++) { - const node = symbolNodes[i]; - const sub = (node as graphic.Group).children()[0]; - const x = (node as Symbol).x; - const y = (node as Symbol).y; - const subShape = clone((sub as graphic.Path).shape); - const shape = extend(subShape, { - width: sub.scaleX, - height: sub.scaleY, - x: x - sub.scaleX / 2, - y: y - sub.scaleY / 2 - }); - const style = clone((sub as graphic.Path).style); - const subThumbnail = new (sub as any).constructor({ - shape, - style, - z2: 151 - }); - symbolGroup.add(subThumbnail); - } + const info = this._getThumbnailInfo(); + if (!info) { + return; + } - for (let i = 0; i < lineNodes.length; i++) { - const node = lineNodes[i]; - const line = (node as graphic.Group).children()[0]; - const style = clone((line as ECLinePath).style); - const shape = clone((line as ECLinePath).shape); - const lineThumbnail = new ECLinePath({ - style, - shape, - z2: 151 - }); - lineGroup.add(lineThumbnail); - } - }; + const bridgeGroup = new graphic.Group(); + const symbolNodes = symbolDraw.group.children(); + const lineNodes = lineDraw.group.children(); + + const lineGroup = new graphic.Group(); + const symbolGroup = new graphic.Group(); + bridgeGroup.add(symbolGroup); + bridgeGroup.add(lineGroup); + + // TODO: reuse elemenents for performance in large graph? + for (let i = 0; i < symbolNodes.length; i++) { + const node = symbolNodes[i]; + const sub = (node as graphic.Group).children()[0]; + const x = (node as Symbol).x; + const y = (node as Symbol).y; + const subShape = clone((sub as graphic.Path).shape); + const shape = extend(subShape, { + width: sub.scaleX, + height: sub.scaleY, + x: x - sub.scaleX / 2, + y: y - sub.scaleY / 2 + }); + const style = clone((sub as graphic.Path).style); + const subThumbnail = new (sub as any).constructor({ + shape, + style, + z2: 151 + }); + symbolGroup.add(subThumbnail); + } - thumbnail.render({ - seriesModel, + for (let i = 0; i < lineNodes.length; i++) { + const node = lineNodes[i]; + const line = (node as graphic.Group).children()[0]; + const style = clone((line as ECLinePath).style); + const shape = clone((line as ECLinePath).shape); + const lineThumbnail = new ECLinePath({ + style, + shape, + z2: 151 + }); + lineGroup.add(lineThumbnail); + } + + info.bridge.renderContent({ api, roamType: seriesModel.get('roam'), - z2Setting: { - background: 150, - window: 160 - }, - seriesBoundingRect: this._mainGroup.getBoundingRect(), - renderThumbnailContent + viewportRect: null, + group: bridgeGroup, + targetTrans: info.coordSys.transform, }); - - thumbnail - .off('pan') - .off('zoom') - .on('pan', (event) => { - this._updateViewOnPan(seriesModel, api, event.dx, event.dy); - }) - .on('zoom', (event) => { - this._updateViewOnZoom(seriesModel, api, event.scale, event.originX, event.originY); - }); } } diff --git a/src/chart/graph/Thumbnail.ts b/src/chart/graph/Thumbnail.ts deleted file mode 100644 index 3ee272e4cb..0000000000 --- a/src/chart/graph/Thumbnail.ts +++ /dev/null @@ -1,275 +0,0 @@ -import * as graphic from '../../util/graphic'; -import ExtensionAPI from '../../core/ExtensionAPI'; -import * as layout from '../../util/layout'; -import GraphSeriesModel from './GraphSeries'; -import * as zrUtil from 'zrender/src/core/util'; -import View from '../../coord/View'; -import BoundingRect from 'zrender/src/core/BoundingRect'; -import * as matrix from 'zrender/src/core/matrix'; -import * as vector from 'zrender/src/core/vector'; -import SeriesModel from '../../model/Series'; -import { BoxLayoutOptionMixin, ItemStyleOption, RoamOptionMixin } from '../../util/types'; -import RoamController, { RoamEventDefinition } from '../../component/helper/RoamController'; -import Eventful from 'zrender/src/core/Eventful'; -import tokens from '../../visual/tokens'; - - -// TODO: -// Thumbnail should not be bound to a single series when used on -// coordinate system like cartesian and geo/map? -// Should we make thumbnail as a component like markers/axisPointer/brush did? - - -interface BorderRadiusOption { - borderRadius?: number | number[] -} - -// TODO: apply to other series -export interface ThumbnailOption extends BoxLayoutOptionMixin { - show?: boolean, - itemStyle?: ItemStyleOption & BorderRadiusOption - windowStyle?: ItemStyleOption & BorderRadiusOption -} - -interface WindowRect extends graphic.Rect { - __r?: BorderRadiusOption['borderRadius']; -} - -export interface ThumbnailZ2Setting { - background: number; - window: number; -} - -class Thumbnail extends Eventful> { - - group = new graphic.Group(); - - private _api: ExtensionAPI; - private _seriesModel: GraphSeriesModel; - - private _windowRect: WindowRect; - private _contentBoundingRect: BoundingRect; - private _thumbnailCoordSys: View; - - private _mtSeriesToThumbnail: matrix.MatrixArray; - private _mtThumbnailToSerise: matrix.MatrixArray; - - private _thumbnailController: RoamController; - private _isEnabled: boolean; - - render(opt: { - seriesModel: GraphSeriesModel; - api: ExtensionAPI; - roamType: RoamOptionMixin['roam']; - z2Setting: ThumbnailZ2Setting; - seriesBoundingRect: BoundingRect, - renderThumbnailContent: (viewGroup: graphic.Group) => void - }) { - const seriesModel = this._seriesModel = opt.seriesModel; - const api = this._api = opt.api; - - const thumbnailModel = seriesModel.getModel('thumbnail'); - const group = this.group; - - this._isEnabled = thumbnailModel.get('show', true) && isSeriesSupported(seriesModel); - if (!this._isEnabled) { - this._clear(); - return; - } - - group.removeAll(); - - const z2Setting = opt.z2Setting; - const cursor = opt.roamType ? 'pointer' : 'default'; - const itemStyleModel = thumbnailModel.getModel('itemStyle'); - const itemStyle = itemStyleModel.getItemStyle(); - itemStyle.fill = seriesModel.ecModel.get('backgroundColor') || tokens.color.neutral00; - - const refContainer = layout.createBoxLayoutReference(seriesModel, api).refContainer; - const boxContainBorder = layout.getLayoutRect( - layout.getBoxLayoutParams(thumbnailModel, true), - refContainer - ); - // Try to use border-box in thumbnail, see https://github.com/apache/echarts/issues/18022 - const boxBorderWidth = itemStyle.lineWidth || 0; - const borderBoundingRect = graphic.expandOrShrinkRect( - boxContainBorder.clone(), boxBorderWidth / 2, true, true - ); - const contentBoundingRect = this._contentBoundingRect = graphic.expandOrShrinkRect( - boxContainBorder.clone(), boxBorderWidth, true, true - ); - - const clipGroup = new graphic.Group(); - group.add(clipGroup); - clipGroup.setClipPath(new graphic.Rect({ - shape: contentBoundingRect.plain() - })); - - const seriesViewGroup = new graphic.Group(); - clipGroup.add(seriesViewGroup); - opt.renderThumbnailContent(seriesViewGroup); - - // Draw border and background and shadow of thumbnail box. - group.add(new graphic.Rect({ - style: itemStyle, - shape: zrUtil.extend(borderBoundingRect.plain(), { - r: itemStyleModel.get('borderRadius', true) - }), - cursor, - z2: z2Setting.background - })); - - const coordSys = this._thumbnailCoordSys = new View(); - const seriesBoundingRect = opt.seriesBoundingRect; - coordSys.setBoundingRect( - seriesBoundingRect.x, seriesBoundingRect.y, seriesBoundingRect.width, seriesBoundingRect.height - ); - - // Find an approperiate rect in contentBoundingRect for the entire graph. - const graphViewRect = layout.getLayoutRect( - { - left: 'center', - top: 'center', - aspect: seriesBoundingRect.width / seriesBoundingRect.height - }, - contentBoundingRect - ); - coordSys.setViewRect(graphViewRect.x, graphViewRect.y, graphViewRect.width, graphViewRect.height); - seriesViewGroup.attr(coordSys.getTransformInfo().raw); - - const windowStyleModel = thumbnailModel.getModel('windowStyle'); - const windowRect: WindowRect = this._windowRect = new graphic.Rect({ - style: windowStyleModel.getItemStyle(), - cursor, - z2: z2Setting.window - }); - windowRect.__r = windowStyleModel.get('borderRadius', true); - clipGroup.add(windowRect); - - this._resetRoamController(opt.roamType, z2Setting.background); - - this.updateWindow(); - } - - /** - * Update window by series view roam status. - */ - updateWindow(): void { - if (!this._isEnabled) { - return; - } - - this._updateTransform(); - - const rect = new BoundingRect(0, 0, this._api.getWidth(), this._api.getHeight()); - rect.applyTransform(this._mtSeriesToThumbnail); - const windowRect = this._windowRect; - windowRect.setShape(zrUtil.defaults({r: windowRect.__r}, rect)); - } - - /** - * Create transform that convert pixel vector from - * series coordinate system to thumbnail coordinate system. - * - * TODO: consider other type of series. - */ - private _updateTransform(): void { - const seriesCoordSys = this._seriesModel.coordinateSystem as View; - this._mtSeriesToThumbnail = matrix.mul([], this._thumbnailCoordSys.transform, seriesCoordSys.invTransform); - this._mtThumbnailToSerise = matrix.invert([], this._mtSeriesToThumbnail); - } - - private _resetRoamController( - roamType: RoamOptionMixin['roam'], - z2: number - ): void { - let thumbnailController = this._thumbnailController; - if (!thumbnailController) { - thumbnailController = this._thumbnailController = new RoamController(this._api.getZr()); - } - - thumbnailController.enable(roamType, { - api: this._api, - zInfo: {component: this._seriesModel, z2}, - triggerInfo: { - roamTrigger: null, - isInSelf: (e, x, y) => this.contain(x, y) - } - }); - thumbnailController - .off('pan') - .off('zoom') - .on('pan', (event) => { - const transform = this._mtThumbnailToSerise; - const oldOffset = vector.applyTransform([], [event.oldX, event.oldY], transform); - // reverse old and new because we pan window rather graph in thumbnail. - const newOffset = vector.applyTransform([], [event.oldX - event.dx, event.oldY - event.dy], transform); - this.trigger('pan', { - dx: newOffset[0] - oldOffset[0], - dy: newOffset[1] - oldOffset[1], - oldX: oldOffset[0], - oldY: oldOffset[1], - newX: newOffset[0], - newY: newOffset[1], - isAvailableBehavior: event.isAvailableBehavior - }); - }) - .on('zoom', (event) => { - const offset = vector.applyTransform([], [event.originX, event.originY], this._mtThumbnailToSerise); - this.trigger('zoom', { - scale: 1 / event.scale, - originX: offset[0], - originY: offset[1], - isAvailableBehavior: event.isAvailableBehavior - }); - }); - } - - contain(x: number, y: number): boolean { - return this._contentBoundingRect && this._contentBoundingRect.contain(x, y); - } - - private _clear(): void { - this.group.removeAll(); - this._thumbnailController && this._thumbnailController.disable(); - } - - remove() { - this._clear(); - } - - dispose() { - this._clear(); - } - - static defaultOption: ThumbnailOption = { - show: false, - - right: 0, - bottom: 0, - - height: '25%', - width: '25%', - - itemStyle: { - // Use echarts option.backgorundColor by default. - borderColor: tokens.color.border, - borderWidth: 1 - }, - - windowStyle: { - borderWidth: 1, - color: tokens.color.neutral30, - borderColor: tokens.color.neutral40, - opacity: 0.3 - } - }; -} - -// TODO: other coordinate system. -function isSeriesSupported(seriesModel: SeriesModel): boolean { - const seriesCoordSys = seriesModel.coordinateSystem; - return seriesCoordSys && seriesCoordSys.type === 'view'; -} - -export default Thumbnail; \ No newline at end of file diff --git a/src/chart/graph/install.ts b/src/chart/graph/install.ts index 9da98ec58f..a92dbb1159 100644 --- a/src/chart/graph/install.ts +++ b/src/chart/graph/install.ts @@ -34,11 +34,6 @@ import GlobalModel from '../../model/Global'; import { noop } from 'zrender/src/core/util'; import type ExtensionAPI from '../../core/ExtensionAPI'; -const actionInfo = { - type: 'graphRoam', - event: 'graphRoam', - update: 'none' -}; export function install(registers: EChartsExtensionInstallRegisters) { @@ -73,12 +68,26 @@ export function install(registers: EChartsExtensionInstallRegisters) { }, noop); // Register roam action. - registers.registerAction(actionInfo, function (payload: RoamPayload, ecModel: GlobalModel, api: ExtensionAPI) { + registers.registerAction({ + type: 'graphRoam', + event: 'graphRoam', + update: 'none' + }, function (payload: RoamPayload, ecModel: GlobalModel, api: ExtensionAPI) { ecModel.eachComponent({ mainType: 'series', query: payload }, function (seriesModel: GraphSeriesModel) { - const coordSys = seriesModel.coordinateSystem as View; + const graphView = api.getViewOfSeriesModel(seriesModel) as GraphView; + if (graphView) { + if (payload.dx != null && payload.dy != null) { + graphView.updateViewOnPan(seriesModel, api, payload); + } + if (payload.zoom != null && payload.originX != null && payload.originY != null) { + graphView.updateViewOnZoom(seriesModel, api, payload); + } + } + + const coordSys = seriesModel.coordinateSystem as View; const res = updateCenterAndZoomInAction(coordSys, payload, seriesModel.get('scaleLimit')); seriesModel.setCenter diff --git a/src/component/geo/install.ts b/src/component/geo/install.ts index f00a756bf0..e77bb60905 100644 --- a/src/component/geo/install.ts +++ b/src/component/geo/install.ts @@ -116,8 +116,22 @@ export function install(registers: EChartsExtensionInstallRegisters) { event: 'geoRoam', update: 'updateTransform' }, function (payload: RoamPayload, ecModel: GlobalModel, api: ExtensionAPI) { - const componentType = payload.componentType || 'series'; - + let componentType = payload.componentType; + if (!componentType) { // backward compat, but `payload.componentType` is deprecated. + if (payload.geoId != null) { + componentType = 'geo'; + } + else if (payload.seriesId != null) { + componentType = 'series'; + } + } + if (!componentType) { + componentType = 'series'; + } + + // FIXME: payload.geoId/payload.seriesId should be required, but historically + // it is not mandatory, causing that all of the geo or series can be queried below, + // which is not reasonable. ecModel.eachComponent( { mainType: componentType, query: payload }, function (componentModel: GeoModel | MapSeries) { diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts index 3b6d4bb3ef..4c057412e5 100644 --- a/src/component/helper/RoamController.ts +++ b/src/component/helper/RoamController.ts @@ -23,10 +23,12 @@ import * as interactionMutex from './interactionMutex'; import { ZRenderType } from 'zrender/src/zrender'; import { ZRElementEvent, RoamOptionMixin, NullUndefined } from '../../util/types'; import { Bind3, isString, bind, defaults, extend, retrieve2 } from 'zrender/src/core/util'; -import { makeInner, retrieveZInfo } from '../../util/model'; +import { makeInner } from '../../util/model'; +import { retrieveZInfo } from '../../util/graphic'; import type Component from '../../model/Component'; import ExtensionAPI from '../../core/ExtensionAPI'; import { onIrrelevantElement } from './cursorHelper'; +import Displayable from 'zrender/src/graphic/Displayable'; export interface RoamOption { @@ -97,6 +99,19 @@ export type RoamEventDefinition = { type RoamPointerChecker = (e: ZRElementEvent, x: number, y: number) => boolean; +/** + * An manager of zoom and pan(darg) hehavior. + * But it is not responsible for updating the view, since view updates vary and can + * not be handled in a uniform way. + * + * Note: regarding view updates: + * - Transformabe views typically use `coord/View` (e.g., geo and series.graph roaming). + * Some commonly used view update logic has been organized into `roamHelper.ts`. + * - Non-transformable views handle updates themselves, possibly involving re-layout, + * (e.g., treemap). + * - Some scenarios do not require transformation (e.g., dataZoom roaming for cartesian, + * brush component). + */ class RoamController extends Eventful { private _zr: ZRenderType; @@ -133,9 +148,11 @@ class RoamController extends Eventful { const pinchHandler = bind(this._pinchHandler, this); /** - * Notice: only enable needed types. For example, if 'zoom' - * is not needed, 'zoom' should not be enabled, otherwise - * default mousewheel behaviour (scroll page) will be disabled. + * Notice: + * - only enable needed types. For example, if 'zoom' + * is not needed, 'zoom' should not be enabled, otherwise + * default mousewheel behaviour (scroll page) will be disabled. + * - This method is idempotent. */ this.enable = function (controlType, rawOpt) { const zInfo = rawOpt.zInfo; @@ -224,6 +241,29 @@ class RoamController extends Eventful { return inArea; } + private _decideCursorStyle( + e: ZRElementEvent, + x: number, + y: number, + forReverse: boolean, + ): string | NullUndefined { + // If this cursor style decision is not strictly consistent with zrender, + // it's fine - zr will set the cursor on the next mousemove. + + // This `grab` cursor style should take the lowest precedence. If the hovring element already + // have a cursor, zrender will set it to be non-'default' before entering this handler. + // (note, e.target is never silent, e.topTarget can be silent be irrelevant.) + const target = e.target; + if (!target && this._checkPointer(e, x, y)) { + // To indicate users that this area is draggable, otherwise users probably cannot kwown + // that when hovering out of the shape but still inside the bounding rect. + return 'grab'; + } + if (forReverse) { + return target && (target as Displayable).cursor || 'default'; + } + } + dispose() { this.disable(); } @@ -271,17 +311,9 @@ class RoamController extends Eventful { if (!this._dragging || !isAvailableBehavior('moveOnMouseMove', e, this._opt) ) { - if (this._checkPointer(e, x, y) - // This `grab` cursor style should take the lowest precedence. If the hovring element already - // have a cursor, zrender will set it to be non-'default' before entering this handler. - // && e.event && e.event.zrCursorStyle === 'default' - && (!e.topTarget || e.topTarget.silent) - ) { - // To indicate users that this area is draggable, otherwise users probably cannot kwown - // that when hovering out of the shape but still inside the bounding rect. - zr.setCursorStyle('grab'); - // Do not need to set the cursor back, because in the current impl, zr is responsible - // for setting the cursor on each mousemove. + const cursorStyle = this._decideCursorStyle(e, x, y, false); + if (cursorStyle) { + zr.setCursorStyle(cursorStyle); } return; } @@ -311,8 +343,14 @@ class RoamController extends Eventful { if (eventConsumed(e)) { return; } + const zr = this._zr; if (!eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) { this._dragging = false; + + const cursorStyle = this._decideCursorStyle(e, e.offsetX, e.offsetY, true); + if (cursorStyle) { + zr.setCursorStyle(cursorStyle); + } } } diff --git a/src/component/helper/cursorHelper.ts b/src/component/helper/cursorHelper.ts index 7a53da7f3f..e6810e4df4 100644 --- a/src/component/helper/cursorHelper.ts +++ b/src/component/helper/cursorHelper.ts @@ -22,7 +22,7 @@ import { ElementEvent } from 'zrender/src/Element'; import ExtensionAPI from '../../core/ExtensionAPI'; import { CoordinateSystemHostModel } from '../../coord/CoordinateSystem'; import type Component from '../../model/Component'; -import { retrieveZInfo } from '../../util/model'; +import { retrieveZInfo } from '../../util/graphic'; const IRRELEVANT_EXCLUDES = {'axisPointer': 1, 'tooltip': 1, 'brush': 1}; diff --git a/src/component/helper/thumbnailBridge.ts b/src/component/helper/thumbnailBridge.ts new file mode 100644 index 0000000000..610e1464d5 --- /dev/null +++ b/src/component/helper/thumbnailBridge.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 type Group from 'zrender/src/graphic/Group'; +import type ComponentModel from '../../model/Component'; +import { makeInner } from '../../util/model'; +import { NullUndefined, RoamOptionMixin } from '../../util/types'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import BoundingRect from 'zrender/src/core/BoundingRect'; +import type View from '../../coord/View'; +/** + * @caveat Do not import other `src/component/thumbnail/*` files. + * This file should be decoupled from them for sake of the size consideration. + */ + +/** + * FIXME: This is a temporary implmentation. May need refactor to decouple + * the direct call from series.graph to thumbnail. + */ + +const inner = makeInner<{ + bridge: ThumbnailBridge +}, ComponentModel>(); + +export function getThumbnailBridge( + model: ComponentModel +): ThumbnailBridge | NullUndefined { + if (model) { + return inner(model).bridge; + } +} + +export function injectThumbnailBridge( + model: ComponentModel, + thumbnailBridge: ThumbnailBridge | NullUndefined +): void { + if (model) { + inner(model).bridge = thumbnailBridge; + } +} + +/** + * This is the transform from the rendered target elements (e.g., the graph elements, the geo map elements) + * in their local unit (e.g., geo in longitude-latitude) to screen coord. + * Typically it is `View['transform']` if `coord/View` is used. + */ +export type ThumbnailTargetTransformRawToViewport = View['transform']; + +export interface ThumbnailBridge { + /** + * Must be called in `ChartView['render']`/`ComponentView['render']` + */ + reset(api: ExtensionAPI): void; + + /** + * Trigger content rendering. + * Some series, such graph force layout, will update elements asynchronously, + * therefore rendering and register are separated. + */ + renderContent(opt: { + roamType: RoamOptionMixin['roam']; + // `viewportRect`: + // - If clip is suppored, this should be the clip rect. + // - Otherwise (can pass NullUndefined), this should be the canvas rect. + viewportRect: BoundingRect; + group: Group; + targetTrans: ThumbnailTargetTransformRawToViewport; + api: ExtensionAPI; + }): void; + + updateWindow( + targetTrans: ThumbnailTargetTransformRawToViewport, + api: ExtensionAPI + ): void; +}; diff --git a/src/component/thumbnail.ts b/src/component/thumbnail.ts new file mode 100644 index 0000000000..1370d941f5 --- /dev/null +++ b/src/component/thumbnail.ts @@ -0,0 +1,25 @@ +/* +* 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 { use } from '../extension'; +import { install } from './thumbnail/install'; + +use(install); diff --git a/src/component/thumbnail/ThumbnailBridgeImpl.ts b/src/component/thumbnail/ThumbnailBridgeImpl.ts new file mode 100644 index 0000000000..9b33cec8c5 --- /dev/null +++ b/src/component/thumbnail/ThumbnailBridgeImpl.ts @@ -0,0 +1,93 @@ +/* +* 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 { calcZ2Range, Group } from '../../util/graphic'; +import { RoamOptionMixin } from '../../util/types'; +import { ThumbnailBridge, ThumbnailTargetTransformRawToViewport } from '../helper/thumbnailBridge'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import type { ThumbnailModel } from './ThumbnailModel'; +import type { ThumbnailView } from './ThumbnailView'; +import BoundingRect from 'zrender/src/core/BoundingRect'; + +export interface ThumbnailBridgeRendered { + roamType: RoamOptionMixin['roam']; + group: Group; + targetTrans: ThumbnailTargetTransformRawToViewport; + z2Range: {min: number, max: number}; + viewportRect: BoundingRect; + // Use version becuase: + // - `renderContent` may be called asynchronously by graph force layout. + // - The order of `updateContent` and `ComponentView['render']` is not guaranteed. + renderVersion: number; +} + +/** + * [CAVEAT]: the call order of `ThumbnailView['render']` and other + * `ChartView['render']/ComponentView['render']` is not guaranteed. + */ +export class ThumbnailBridgeImpl implements ThumbnailBridge { + + private _thumbnailModel: ThumbnailModel; + private _renderVersion: number; + + constructor(thumbnailModel: ThumbnailModel) { + this._thumbnailModel = thumbnailModel; + } + + reset(api: ExtensionAPI) { + this._renderVersion = api.getMainProcessVersion(); + } + + renderContent(opt: { + roamType: RoamOptionMixin['roam']; + viewportRect: BoundingRect; + group: Group; + targetTrans: ThumbnailTargetTransformRawToViewport; + api: ExtensionAPI; + }): void { + const thumbnailView = opt.api.getViewOfComponentModel(this._thumbnailModel) as ThumbnailView; + if (!thumbnailView) { + return; + } + opt.group.silent = true; + thumbnailView.renderContent({ + group: opt.group, + targetTrans: opt.targetTrans, + z2Range: calcZ2Range(opt.group), + roamType: opt.roamType, + viewportRect: opt.viewportRect, + renderVersion: this._renderVersion, + }); + } + + updateWindow( + targetTrans: ThumbnailTargetTransformRawToViewport, + api: ExtensionAPI + ): void { + const thumbnailView = api.getViewOfComponentModel(this._thumbnailModel) as ThumbnailView; + if (!thumbnailView) { + return; + } + thumbnailView.updateWindow({ + targetTrans, + renderVersion: this._renderVersion, + }); + } + +} diff --git a/src/component/thumbnail/ThumbnailModel.ts b/src/component/thumbnail/ThumbnailModel.ts new file mode 100644 index 0000000000..7f480dc5ac --- /dev/null +++ b/src/component/thumbnail/ThumbnailModel.ts @@ -0,0 +1,161 @@ +/* +* 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 ComponentModel from '../../model/Component'; +import { error } from '../../util/log'; +import { + BorderOptionMixin, BoxLayoutOptionMixin, ComponentOption, ItemStyleOption, NullUndefined, +} from '../../util/types'; +import tokens from '../../visual/tokens'; +import { + injectThumbnailBridge +} from '../helper/thumbnailBridge'; +import { ThumbnailBridgeImpl } from './ThumbnailBridgeImpl'; + +/** + * [NOTE]: thumbnail is implemented as a component, rather than internal data strucutrue, + * due to the possibility of serveing geo and related series with a single thumbnail, + * and enable to apply some common layout feature, such as matrix coord sys. + */ + +// TODO: currently only graph supports thumbnail. +// May need some refactor if serving new components in future. + +export interface ThumbnailOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + mainType?: 'thumbnail' + + show?: boolean + + itemStyle?: ItemStyleOption + windowStyle?: ItemStyleOption + + seriesIndex?: number | number[] + seriesId?: string | string[] +} + +export interface ThumbnailZ2Setting { + background: number; + window: number; +} + +export class ThumbnailModel extends ComponentModel { + static type = 'thumbnail' as const; + type = ThumbnailModel.type; + + static layoutMode = 'box' as const; + + preventAutoZ = true; + + // All the supported components should be added here. + static dependencies = ['series', 'geo']; + + static defaultOption: ThumbnailOption = { + show: true, + right: 1, + bottom: 1, + height: '25%', + width: '25%', + + itemStyle: { + // Use echarts option.backgorundColor by default. + borderColor: tokens.color.border, + borderWidth: 2 + }, + + windowStyle: { + borderWidth: 1, + color: tokens.color.neutral30, + borderColor: tokens.color.neutral40, + opacity: 0.3 + }, + + z: 10, + + }; + + // Never remove after created. + private _birdge: ThumbnailBridgeImpl; + + private _target: { + baseMapProvider: ComponentModel | NullUndefined + // May extend. + } | NullUndefined; + + optionUpdated(newCptOption: ThumbnailOption, isInit: boolean): void { + this._updateBridge(); + } + + private _updateBridge() { + const bridge = this._birdge = this._birdge || new ThumbnailBridgeImpl(this); + + // Clear all, in case of option changed. + this._target = null; + this.ecModel.eachSeries(series => { + injectThumbnailBridge(series, null); + }); + + if (this.shouldShow()) { + const target = this.getTarget(); + // If a component is targeted by more than one thumbnails, simply only the last one works. + injectThumbnailBridge(target.baseMapProvider, bridge); + } + } + + shouldShow() { + return this.getShallow('show', true); + } + + getBridge(): ThumbnailBridgeImpl { + return this._birdge; + } + + getTarget(): { + baseMapProvider: ComponentModel | NullUndefined + // May extend. + } { + if (this._target) { + return this._target; + } + + // Find by `seriesId`/`seriesIndex`. + let series = this.getReferringComponents('series', { + useDefault: false, enableAll: false, enableNone: false + }).models[0]; + if (series) { + if (series.subType !== 'graph') { + series = null; + if (__DEV__) { + error(`series.${series.subType} is not supported in thumbnail.`, true); + } + } + } + else { + // If no xxxId and xxxIndex specified, find the first series.graph. If other components, + // such as geo, is supported in future, the default stretagy may be extended. + series = this.ecModel.queryComponents({mainType: 'series', subType: 'graph'})[0]; + } + + this._target = { + baseMapProvider: series + }; + + return this._target; + } + +} diff --git a/src/component/thumbnail/ThumbnailView.ts b/src/component/thumbnail/ThumbnailView.ts new file mode 100644 index 0000000000..617f6dfda1 --- /dev/null +++ b/src/component/thumbnail/ThumbnailView.ts @@ -0,0 +1,322 @@ +/* +* 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 ExtensionAPI from '../../core/ExtensionAPI'; +import GlobalModel from '../../model/Global'; +import ComponentView from '../../view/Component'; +import { ThumbnailModel } from './ThumbnailModel'; +import { NullUndefined, RoamOptionMixin } from '../../util/types'; +import BoundingRect from 'zrender/src/core/BoundingRect'; +import * as matrix from 'zrender/src/core/matrix'; +import RoamController, { RoamEventParams } from '../helper/RoamController'; +import tokens from '../../visual/tokens'; +import { createBoxLayoutReference, getBoxLayoutParams, getLayoutRect } from '../../util/layout'; +import { expandOrShrinkRect, Rect, Group, traverseUpdateZ, retrieveZInfo } from '../../util/graphic'; +import { RectShape } from 'zrender/src/graphic/shape/Rect'; +import { applyTransform } from 'zrender/src/core/vector'; +import View from '../../coord/View'; +import { bind, defaults, extend } from 'zrender/src/core/util'; +import type ComponentModel from '../../model/Component'; +import { RoamPayload } from '../helper/roamHelper'; +import { ThumbnailBridgeRendered } from './ThumbnailBridgeImpl'; + + +export class ThumbnailView extends ComponentView { + + static type = 'thumbnail' as const; + type = ThumbnailView.type; + + private _api: ExtensionAPI; + private _model: ThumbnailModel; + private _bgRect: Rect; + private _windowRect: Rect; + private _contentRect: BoundingRect; + private _targetGroup: Group; + private _transThisToTarget: matrix.MatrixArray; + private _roamController: RoamController; + private _coordSys: View; + private _bridgeRendered: ThumbnailBridgeRendered | NullUndefined; + + // The version of rendering result. + // `render`/`updateContent`/`updateWindow` can be called separatedly and the order + // is not guaranteed. Use version to ensure the consistency. + private _renderVersion: number; + + render(thumbnailModel: ThumbnailModel, ecModel: GlobalModel, api: ExtensionAPI): void { + this._api = api; + this._model = thumbnailModel; + if (!this._coordSys) { + this._coordSys = new View(); + } + + if (!this._isEnabled()) { + this._clear(); + return; + } + + this._renderVersion = api.getMainProcessVersion(); + const group = this.group; + + group.removeAll(); + + const itemStyleModel = thumbnailModel.getModel('itemStyle'); + const itemStyle = itemStyleModel.getItemStyle(); + if (itemStyle.fill == null) { + itemStyle.fill = ecModel.get('backgroundColor') || tokens.color.neutral00; + } + + const refContainer = createBoxLayoutReference(thumbnailModel, api).refContainer; + const boxRect = getLayoutRect( + getBoxLayoutParams(thumbnailModel, true), + refContainer + ); + const boxBorderWidth = itemStyle.lineWidth || 0; + const contentRect = this._contentRect = expandOrShrinkRect( + boxRect.clone(), boxBorderWidth / 2, true, true + ); + + const contentGroup = new Group(); + group.add(contentGroup); + contentGroup.setClipPath(new Rect({shape: contentRect.plain()})); + + const targetGroup = this._targetGroup = new Group(); + contentGroup.add(targetGroup); + + // Draw border and background and shadow of thumbnail box. + const borderShape: RectShape = boxRect.plain(); + borderShape.r = itemStyleModel.getShallow('borderRadius', true) as (number | number[]); + group.add(this._bgRect = new Rect({ + style: itemStyle, + shape: borderShape, + silent: false, // Prevent from hovering on the lower elements. + cursor: 'grab', + })); + + const windowStyleModel = thumbnailModel.getModel('windowStyle'); + const windowR = windowStyleModel.getShallow('borderRadius', true) as (number | number[]); + contentGroup.add(this._windowRect = new Rect({ + shape: {x: 0, y: 0, width: 0, height: 0, r: windowR}, + style: windowStyleModel.getItemStyle(), + silent: false, // Prevent from hovering on the lower elements. + cursor: 'grab', + })); + + this._dealRenderContent(); + this._dealUpdateWindow(); + + updateZ(thumbnailModel, this); + } + + /** + * Can be called asynchronously directly. + * This method should be idempotent. + */ + renderContent(bridgeRendered: ThumbnailBridgeRendered): void { + this._bridgeRendered = bridgeRendered; + if (this._isEnabled()) { + this._dealRenderContent(); + this._dealUpdateWindow(); + updateZ(this._model, this); + } + } + + private _dealRenderContent(): void { + const bridgeRendered = this._bridgeRendered; + if (!bridgeRendered || bridgeRendered.renderVersion !== this._renderVersion) { + return; + } + + const targetGroup = this._targetGroup; + const coordSys = this._coordSys; + const contentRect = this._contentRect; + + targetGroup.removeAll(); + + if (!bridgeRendered) { + return; + } + + const bridgeGroup = bridgeRendered.group; + const bridgeRect = bridgeGroup.getBoundingRect(); + + targetGroup.add(bridgeGroup); + + this._bgRect.z2 = bridgeRendered.z2Range.min - 10; + + coordSys.setBoundingRect(bridgeRect.x, bridgeRect.y, bridgeRect.width, bridgeRect.height); + // Use `getLayoutRect` is just to find an approperiate rect in thumbnail. + const viewRect = getLayoutRect( + { + left: 'center', + top: 'center', + aspect: bridgeRect.width / bridgeRect.height + }, + contentRect + ); + coordSys.setViewRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height); + bridgeGroup.attr(coordSys.getTransformInfo().raw); + + this._windowRect.z2 = bridgeRendered.z2Range.max + 10; + + this._resetRoamController(bridgeRendered.roamType); + } + + /** + * Can be called from action handler directly. + * This method should be idempotent. + */ + updateWindow(param: Pick): void { + const bridgeRendered = this._bridgeRendered; + if (bridgeRendered && bridgeRendered.renderVersion === param.renderVersion) { + bridgeRendered.targetTrans = param.targetTrans; + } + if (this._isEnabled()) { + this._dealUpdateWindow(); + } + } + + private _dealUpdateWindow(): void { + const bridgeRendered = this._bridgeRendered; + if (!bridgeRendered || bridgeRendered.renderVersion !== this._renderVersion) { + return; + } + + const invTargetTrans = matrix.invert([], bridgeRendered.targetTrans); + const transTargetToThis = matrix.mul([], this._coordSys.transform, invTargetTrans); + this._transThisToTarget = matrix.invert([], transTargetToThis); + + let viewportRect = bridgeRendered.viewportRect; + if (!viewportRect) { + viewportRect = new BoundingRect(0, 0, this._api.getWidth(), this._api.getHeight()); + } + else { + viewportRect = viewportRect.clone(); + } + viewportRect.applyTransform(transTargetToThis); + const windowRect = this._windowRect; + const r = windowRect.shape.r; + windowRect.setShape(defaults({r}, viewportRect)); + } + + private _resetRoamController( + roamType: RoamOptionMixin['roam'], + ): void { + const api = this._api; + + let roamController = this._roamController; + if (!roamController) { + roamController = this._roamController = new RoamController(api.getZr()); + } + + if (!roamType || !this._isEnabled()) { + roamController.disable(); + return; + } + + roamController.enable(roamType, { + api: api, + zInfo: {component: this._model}, + triggerInfo: { + roamTrigger: null, + isInSelf: (e, x, y) => this._contentRect.contain(x, y) + } + }); + roamController + .off('pan') + .off('zoom') + .on('pan', bind(this._onPan, this)) + .on('zoom', bind(this._onZoom, this)); + } + + private _onPan(event: RoamEventParams['pan']): void { + const trans = this._transThisToTarget; + if (!this._isEnabled() || !trans) { + return; + } + const oldOffset = applyTransform([], [event.oldX, event.oldY], trans); + const newOffset = applyTransform([], [event.oldX - event.dx, event.oldY - event.dy], trans); + + this._api.dispatchAction(makeRoamPayload(this._model.getTarget().baseMapProvider, { + dx: newOffset[0] - oldOffset[0], + dy: newOffset[1] - oldOffset[1], + })); + } + + private _onZoom(event: RoamEventParams['zoom']): void { + const trans = this._transThisToTarget; + if (!this._isEnabled() || !trans) { + return; + } + const offset = applyTransform([], [event.originX, event.originY], trans); + + this._api.dispatchAction(makeRoamPayload(this._model.getTarget().baseMapProvider, { + zoom: 1 / event.scale, + originX: offset[0], + originY: offset[1], + })); + } + + /** + * This method is also responsible for check enable in asynchronous situation, + * e.g., in event listeners that is supposed to be outdated but not be removed. + */ + private _isEnabled(): boolean { + const thumbnailModel = this._model; + if (!thumbnailModel || !thumbnailModel.shouldShow()) { + return false; + } + const baseMapProvider = thumbnailModel.getTarget().baseMapProvider; + if (!baseMapProvider) { + return false; + } + return true; + } + + private _clear(): void { + this.group.removeAll(); + this._bridgeRendered = null; + if (this._roamController) { + this._roamController.disable(); + } + } + + remove() { + this._clear(); + } + + dispose() { + this._clear(); + } + +} + +function makeRoamPayload(baseMapProvider: ComponentModel, params: Partial): RoamPayload { + const type = baseMapProvider.mainType === 'series' + ? `${baseMapProvider.subType}Roam` // e.g. 'graphRoam' + : `${baseMapProvider.mainType}Roam`; // e.g., 'geoRoam' + const payload = {type} as RoamPayload; + payload[`${baseMapProvider.mainType}Id`] = baseMapProvider.id; + extend(payload, params); + return payload; +} + +function updateZ(thumbnailModel: ThumbnailModel, thumbnailView: ThumbnailView): void { + const zInfo = retrieveZInfo(thumbnailModel); + traverseUpdateZ(thumbnailView.group, zInfo.z, zInfo.zlevel); +} diff --git a/src/component/thumbnail/install.ts b/src/component/thumbnail/install.ts new file mode 100644 index 0000000000..4cacdda222 --- /dev/null +++ b/src/component/thumbnail/install.ts @@ -0,0 +1,27 @@ +/* +* 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 { EChartsExtensionInstallRegisters } from '../../extension'; +import { ThumbnailModel } from './ThumbnailModel'; +import { ThumbnailView } from './ThumbnailView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(ThumbnailModel); + registers.registerComponentView(ThumbnailView); +} diff --git a/src/component/timeline.ts b/src/component/timeline.ts index d42d780593..bc264e1706 100644 --- a/src/component/timeline.ts +++ b/src/component/timeline.ts @@ -24,4 +24,4 @@ import { use } from '../extension'; import { install } from './timeline/install'; -use(install); \ No newline at end of file +use(install); diff --git a/src/core/ExtensionAPI.ts b/src/core/ExtensionAPI.ts index 98f15e5cab..047bb704c9 100644 --- a/src/core/ExtensionAPI.ts +++ b/src/core/ExtensionAPI.ts @@ -72,6 +72,7 @@ abstract class ExtensionAPI { abstract getViewOfComponentModel(componentModel: ComponentModel): ComponentView; abstract getViewOfSeriesModel(seriesModel: SeriesModel): ChartView; abstract getModel(): GlobalModel; + abstract getMainProcessVersion(): number; } export default ExtensionAPI; diff --git a/src/core/echarts.ts b/src/core/echarts.ts index 5aaab2b927..e1875429a9 100644 --- a/src/core/echarts.ts +++ b/src/core/echarts.ts @@ -136,7 +136,6 @@ import lifecycle, { import { platformApi, setPlatformAPI } from 'zrender/src/core/platform'; import { getImpl } from './impl'; import type geoSourceManager from '../coord/geo/geoSourceManager'; -import { ExtendedElement } from './ExtendedElement'; import { registerCustomSeries as registerCustom } from '../chart/custom/customSeriesRegister'; @@ -205,6 +204,11 @@ export const PRIORITY = { // This flag is used to carry out this rule. // All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). const IN_MAIN_PROCESS_KEY = '__flagInMainProcess' as const; +// Useful for detecting outdated rendering results in scenarios that these issues are involved: +// - Use shortcut (such as, updateTransform, or no update) to start a main process. +// - Asynchronously update rendered view (e.g., graph force layout). +// - Multiple ChartView/ComponentView render to one group cooperatively. +const MAIN_PROCESS_VERSION_KEY = '__mainProcessVersion' as const; const PENDING_UPDATE = '__pendingUpdate' as const; const STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus' as const; const ACTION_REG = /^[a-zA-Z0-9_]+$/; @@ -336,6 +340,7 @@ let enableConnect: (ecIns: ECharts) => void; let markStatusToUpdate: (ecIns: ECharts) => void; let applyChangedStates: (ecIns: ECharts) => void; +let updateMainProcessVersion: (ecIns: ECharts) => void; type RenderedEventParam = { elapsedTime: number }; type ECEventDefinition = { @@ -418,6 +423,7 @@ class ECharts extends Eventful { updateParams: UpdateLifecycleParams }; private [IN_MAIN_PROCESS_KEY]: boolean; + private [MAIN_PROCESS_VERSION_KEY]: number; private [CONNECT_STATUS_KEY]: ConnectStatus; private [STATUS_NEEDS_UPDATE_KEY]: boolean; @@ -437,6 +443,8 @@ class ECharts extends Eventful { let defaultCoarsePointer: 'auto' | boolean = 'auto'; let defaultUseDirtyRect = false; + this[MAIN_PROCESS_VERSION_KEY] = 1; + if (__DEV__) { const root = ( /* eslint-disable-next-line */ @@ -530,6 +538,7 @@ class ECharts extends Eventful { const silent = (this[PENDING_UPDATE] as any).silent; this[IN_MAIN_PROCESS_KEY] = true; + updateMainProcessVersion(this); try { prepare(this); @@ -656,6 +665,7 @@ class ECharts extends Eventful { } this[IN_MAIN_PROCESS_KEY] = true; + updateMainProcessVersion(this); if (!this._model || notMerge) { const optionManager = new OptionManager(this._api); @@ -746,6 +756,7 @@ class ECharts extends Eventful { } this[IN_MAIN_PROCESS_KEY] = true; + updateMainProcessVersion(this); try { this._updateTheme(theme); @@ -1377,6 +1388,7 @@ class ECharts extends Eventful { } this[IN_MAIN_PROCESS_KEY] = true; + updateMainProcessVersion(this); try { needPrepare && prepare(this); @@ -2029,6 +2041,7 @@ class ECharts extends Eventful { const cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]); this[IN_MAIN_PROCESS_KEY] = true; + updateMainProcessVersion(this); let payloads: Payload[] = [payload]; let batched = false; @@ -2411,6 +2424,10 @@ class ECharts extends Eventful { ecIns.getZr().wakeUp(); }; + updateMainProcessVersion = function (ecIns: ECharts): void { + ecIns[MAIN_PROCESS_VERSION_KEY] = (ecIns[MAIN_PROCESS_VERSION_KEY] + 1) % 1000; + }; + applyChangedStates = function (ecIns: ECharts): void { if (!ecIns[STATUS_NEEDS_UPDATE_KEY]) { return; @@ -2498,75 +2515,15 @@ class ECharts extends Eventful { if (model.preventAutoZ) { return; } - const zInfo = modelUtil.retrieveZInfo(model); + const zInfo = graphic.retrieveZInfo(model); // Set z and zlevel view.eachRendered((el) => { - doUpdateZ(el, zInfo.z, zInfo.zlevel, -Infinity, false); + graphic.traverseUpdateZ(el, zInfo.z, zInfo.zlevel); // Don't traverse the children because it has been traversed in _updateZ. return true; }); }; - function doUpdateZ( - el: Element, - z: number, - zlevel: number, - maxZ2: number, - ignoreModelZ: boolean - ): number { - // Group may also have textContent - const label = el.getTextContent(); - const labelLine = el.getTextGuideLine(); - const isGroup = el.isGroup; - - if (isGroup) { - // set z & zlevel of children elements of Group - const children = (el as graphic.Group).childrenRef(); - for (let i = 0; i < children.length; i++) { - ignoreModelZ = ignoreModelZ || (el as ExtendedElement).ignoreModelZ; - maxZ2 = Math.max( - doUpdateZ( - children[i], - z, - zlevel, - maxZ2, - ignoreModelZ || (el as ExtendedElement).ignoreModelZ - ), - maxZ2 - ); - } - } - else { - if (ignoreModelZ || (el as ExtendedElement).ignoreModelZ) { - // This child element will not be set z and zlevel of the group - return maxZ2; - } - - // not Group - (el as Displayable).z = z; - (el as Displayable).zlevel = zlevel; - - maxZ2 = Math.max((el as Displayable).z2, maxZ2); - } - - // always set z and zlevel if label/labelLine exists - if (label) { - label.z = z; - label.zlevel = zlevel; - // lift z2 of text content - // TODO if el.emphasis.z2 is spcefied, what about textContent. - isFinite(maxZ2) && (label.z2 = maxZ2 + 2); - } - if (labelLine) { - const textGuideLineConfig = el.textGuideLineConfig; - labelLine.z = z; - labelLine.zlevel = zlevel; - isFinite(maxZ2) - && (labelLine.z2 = maxZ2 + (textGuideLineConfig && textGuideLineConfig.showAbove ? 1 : -1)); - } - return maxZ2; - } - // Clear states without animation. // TODO States on component. function clearStates(model: ComponentModel, view: ComponentView | ChartView): void { @@ -2699,6 +2656,9 @@ class ECharts extends Eventful { getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { return ecIns.getViewOfSeriesModel(seriesModel); } + getMainProcessVersion(): number { + return ecIns[MAIN_PROCESS_VERSION_KEY]; + } })(ecIns); }; diff --git a/src/echarts.all.ts b/src/echarts.all.ts index cf59ed3cc4..13aca093e1 100644 --- a/src/echarts.all.ts +++ b/src/echarts.all.ts @@ -84,6 +84,7 @@ import { VisualMapComponent, VisualMapContinuousComponent, VisualMapPiecewiseComponent, + ThumbnailComponent, AriaComponent, DatasetComponent, TransformComponent @@ -332,6 +333,8 @@ use(VisualMapContinuousComponent); // }); use(VisualMapPiecewiseComponent); +use(ThumbnailComponent); + // `aria` component providing aria, for example: // chart.setOption({ // aria: {...} diff --git a/src/export/components.ts b/src/export/components.ts index 934b99027e..6a4091ae50 100644 --- a/src/export/components.ts +++ b/src/export/components.ts @@ -58,6 +58,8 @@ export {install as VisualMapContinuousComponent} from '../component/visualMap/in export {install as VisualMapPiecewiseComponent} from '../component/visualMap/installVisualMapPiecewise'; +export {install as ThumbnailComponent} from '../component/thumbnail/install'; + export {install as AriaComponent} from '../component/aria/install'; export {install as TransformComponent} from '../component/transform/install'; @@ -100,6 +102,8 @@ export { VisualMapComponentOption, + ThumbnailComponentOption, + AriaComponentOption, DatasetComponentOption diff --git a/src/export/option.ts b/src/export/option.ts index 0a7d60b509..1859271193 100644 --- a/src/export/option.ts +++ b/src/export/option.ts @@ -42,6 +42,7 @@ import type { import type {AxisPointerOption as AxisPointerComponentOption} from '../component/axisPointer/AxisPointerModel'; import type {BrushOption as BrushComponentOption} from '../component/brush/BrushModel'; import type {TitleOption as TitleComponentOption} from '../component/title/install'; +import type {ThumbnailOption as ThumbnailComponentOption} from '../component/thumbnail/ThumbnailModel'; import type {TimelineOption as TimelineComponentOption} from '../component/timeline/TimelineModel'; import type {SliderTimelineOption as TimelineSliderComponentOption} from '../component/timeline/SliderTimelineModel'; @@ -167,6 +168,7 @@ export { MarkPointComponentOption, MarkAreaComponentOption, ToolboxComponentOption, + ThumbnailComponentOption, GraphicComponentOption, AriaComponentOption, DatasetComponentOption @@ -273,6 +275,7 @@ export interface EChartsOption extends ECBasicOption { legend?: LegendComponentOption | (LegendComponentOption)[]; dataZoom?: DataZoomComponentOption | (DataZoomComponentOption)[]; visualMap?: VisualMapComponentOption | (VisualMapComponentOption)[]; + thumbnail?: ThumbnailComponentOption | (ThumbnailComponentOption)[]; graphic?: GraphicComponentOption | GraphicComponentOption[]; // TODO Generally we support specify a single object on series. diff --git a/src/util/graphic.ts b/src/util/graphic.ts index c8f1408ace..a2838adac7 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -53,7 +53,8 @@ import { ZRStyleProps, CommonTooltipOption, ComponentItemTooltipLabelFormatterParams, - NullUndefined + NullUndefined, + ComponentOption } from './types'; import { extend, @@ -78,6 +79,7 @@ import { removeElementWithFadeOut, isElementRemoved } from '../animation/basicTransition'; +import { ExtendedElement } from '../core/ExtendedElement'; /** * @deprecated export for compatitable reason @@ -722,6 +724,129 @@ export function traverseElements(els: Element | Element[] | undefined | null, cb } } +export function retrieveZInfo( + model: Model>>, +): { + z: ComponentOption['z'] + zlevel: ComponentOption['zlevel'] +} { + return { + z: model.get('z') || 0, + zlevel: model.get('zlevel') || 0, + }; +} + +/** + * Assume all of the elements has the same `z` and `zlevel`. + */ +export function calcZ2Range(el: Element): { + min: number + max: number +} { + let max = -Infinity; + let min = Infinity; + traverseElement(el, el => { + visitEl(el); + visitEl(el.getTextContent()); + visitEl(el.getTextGuideLine()); + }); + function visitEl(el: Element): void { + if (!el || el.isGroup) { + return; + } + const currentStates = el.currentStates; + if (currentStates.length) { + for (let idx = 0; idx < currentStates.length; idx++) { + calcZ2(el.states[currentStates[idx]] as Displayable); + } + } + calcZ2(el as Displayable); + } + function calcZ2(entity: Pick): void { + if (entity) { + const z2 = entity.z2; + // Consider z2 may be NullUndefined + if (z2 > max) { + max = z2; + } + if (z2 < min) { + min = z2; + } + } + } + if (min > max) { + min = max = 0; + } + return {min, max}; +} + +export function traverseUpdateZ( + el: Element, + z: number, + zlevel: number, +): void { + doUpdateZ(el, z, zlevel); +} + +function doUpdateZ( + el: Element, + z: number, + zlevel: number +): number { + let maxZ2 = -Infinity; + + // `ignoreModelZ` is used to intentionally lift elements to cover other elements, + // where maxZ2 (for label.z2) should also not be counted for its parents. + if ((el as ExtendedElement).ignoreModelZ) { + return maxZ2; + } + + // Group may also have textContent + const label = el.getTextContent(); + const labelLine = el.getTextGuideLine(); + const isGroup = el.isGroup; + + if (isGroup) { + // set z & zlevel of children elements of Group + const children = (el as Group).childrenRef(); + for (let i = 0; i < children.length; i++) { + maxZ2 = Math.max( + doUpdateZ( + children[i], + z, + zlevel, + ), + maxZ2 + ); + } + } + else { + // not Group + (el as Displayable).z = z; + (el as Displayable).zlevel = zlevel; + + maxZ2 = Math.max((el as Displayable).z2 || 0, maxZ2); + } + + // always set z and zlevel if label/labelLine exists + if (label) { + label.z = z; + label.zlevel = zlevel; + // lift z2 of text content + // TODO if el.emphasis.z2 is spcefied, what about textContent. + isFinite(maxZ2) && (label.z2 = maxZ2 + 2); + } + if (labelLine) { + const textGuideLineConfig = el.textGuideLineConfig; + labelLine.z = z; + labelLine.zlevel = zlevel; + isFinite(maxZ2) + && (labelLine.z2 = maxZ2 + (textGuideLineConfig && textGuideLineConfig.showAbove ? 1 : -1)); + } + return maxZ2; +} + + // Register built-in shapes. These shapes might be overwritten // by users, although we do not recommend that. registerShape('circle', Circle); diff --git a/src/util/model.ts b/src/util/model.ts index 18814a33ac..77758454fb 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -1114,18 +1114,6 @@ export function interpolateRawValues( } } -export function retrieveZInfo( - model: Model>>, -): { - z: ComponentOption['z'] - zlevel: ComponentOption['zlevel'] -} { - return { - z: model.get('z') || 0, - zlevel: model.get('zlevel') || 0, - }; -} - /** * Use an iterator to avoid exposing the internal list or duplicating it * for the outside traveller, and no extra heap allocation. diff --git a/test/graph-layout-roam.html b/test/graph-layout-roam.html index 8dcaa8ca97..6658d887bf 100644 --- a/test/graph-layout-roam.html +++ b/test/graph-layout-roam.html @@ -131,6 +131,7 @@ var option = { backgroundColor: '#f4f4f9', tooltip: {}, + thumbnail: {}, series: [ { type: 'graph', diff --git a/test/graph-thumbnail.html b/test/graph-thumbnail.html index 86a17840bf..3f109b9824 100644 --- a/test/graph-thumbnail.html +++ b/test/graph-thumbnail.html @@ -19,9 +19,9 @@ }
-
+
- - + + + + + + \ No newline at end of file diff --git a/test/matrix3.html b/test/matrix3.html index 259d85d69a..00edce4b82 100644 --- a/test/matrix3.html +++ b/test/matrix3.html @@ -446,6 +446,16 @@ fontSize: 10 } }], + thumbnail: { + seriesId: 'graph_a', + coordinateSystem: 'matrix', + matrixId: 'matrix1', + coord: ['S', 'A1'], + left: 2, + top: 2, + right: 2, + bottom: 2, + }, series: [{ type: 'scatter', coordinateSystem: 'matrix', From b5e06ca13d944a46cda78b58c34b12b323e15500 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 19 Jun 2025 22:38:04 +0800 Subject: [PATCH 47/49] fix(graph): fix NPE when triggering graphRoam action after removing. --- src/chart/graph/GraphView.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index a8e4dccd16..0311b59f66 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -74,6 +74,8 @@ class GraphView extends ChartView { private _mainGroup: graphic.Group; + private _active: boolean; + init(ecModel: GlobalModel, api: ExtensionAPI) { const symbolDraw = new SymbolDraw(); const lineDraw = new LineDraw(); @@ -102,6 +104,7 @@ class GraphView extends ChartView { this._model = seriesModel; this._api = api; + this._active = true; const thumbnailInfo = this._getThumbnailInfo(); if (thumbnailInfo) { @@ -318,6 +321,9 @@ class GraphView extends ChartView { api: ExtensionAPI, params: Pick ): void { + if (!this._active) { + return; + } updateViewOnPan(this._controllerHost, params.dx, params.dy); this._updateThumbnailWindow(); } @@ -332,6 +338,9 @@ class GraphView extends ChartView { api: ExtensionAPI, params: Pick ) { + if (!this._active) { + return; + } updateViewOnZoom(this._controllerHost, params.zoom, params.originX, params.originY); this._updateNodeAndLinkScale(); adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); @@ -353,6 +362,10 @@ class GraphView extends ChartView { } updateLayout(seriesModel: GraphSeriesModel) { + if (!this._active) { + return; + } + adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); this._symbolDraw.updateLayout(); @@ -360,6 +373,7 @@ class GraphView extends ChartView { } remove() { + this._active = false; clearTimeout(this._layoutTimeout); this._layouting = false; this._layoutTimeout = null; From 732331794f62ca05341a795950fa056ca409e182 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 19 Jun 2025 22:49:56 +0800 Subject: [PATCH 48/49] test(matrix): complete visual test for matrix. --- test/matrix3.html | 4 ++-- test/runTest/actions/__meta__.json | 2 ++ test/runTest/actions/graph-thumbnail.json | 1 + test/runTest/actions/matrix3.json | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 test/runTest/actions/graph-thumbnail.json create mode 100644 test/runTest/actions/matrix3.json diff --git a/test/matrix3.html b/test/matrix3.html index 00edce4b82..ba219bed2e 100644 --- a/test/matrix3.html +++ b/test/matrix3.html @@ -1092,7 +1092,7 @@ id: 'matrix0', left: 10, width: '80%', - bottom: '55%', + bottom: 20, tooltip: {show: true}, x: {data: _xData}, y: {data: _yData}, @@ -1105,7 +1105,7 @@ 'Cell text overflow and tooltip.', ], option: option, - height: 550, + height: 350, // boundingRect: true, draggable: true, inputsStyle: 'compact', diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 5192ef5670..1350aa339b 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -110,6 +110,7 @@ "graph-case": 3, "graph-grid": 1, "graph-simple": 2, + "graph-thumbnail": 4, "graphic-animation": 1, "graphic-animation-wave": 1, "graphic-cases": 2, @@ -153,6 +154,7 @@ "matrix_application": 4, "matrix_application2": 2, "matrix2": 5, + "matrix3": 3, "media-dataZoom": 1, "media-finance": 2, "media-pie": 1, diff --git a/test/runTest/actions/graph-thumbnail.json b/test/runTest/actions/graph-thumbnail.json new file mode 100644 index 0000000000..181861398d --- /dev/null +++ b/test/runTest/actions/graph-thumbnail.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":30,"x":690,"y":368},{"type":"mousemove","time":231,"x":410,"y":385},{"type":"mousemove","time":437,"x":378,"y":383},{"type":"mousewheel","time":814,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":835,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":854,"x":378,"y":383,"deltaY":-9},{"type":"mousewheel","time":873,"x":378,"y":383,"deltaY":-11},{"type":"mousewheel","time":893,"x":378,"y":383,"deltaY":-10},{"type":"mousewheel","time":913,"x":378,"y":383,"deltaY":-16},{"type":"mousewheel","time":933,"x":378,"y":383,"deltaY":-6},{"type":"mousewheel","time":954,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":975,"x":378,"y":383,"deltaY":-3},{"type":"mousewheel","time":997,"x":378,"y":383,"deltaY":-3},{"type":"mousewheel","time":1024,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":1045,"x":378,"y":383,"deltaY":-2},{"type":"mousewheel","time":1064,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":1086,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":1109,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":1132,"x":378,"y":383,"deltaY":-2},{"type":"mousewheel","time":1229,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1248,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1267,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":1289,"x":378,"y":383,"deltaY":3},{"type":"mousewheel","time":1310,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":1329,"x":378,"y":383,"deltaY":10},{"type":"mousewheel","time":1350,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":1373,"x":378,"y":383,"deltaY":4},{"type":"mousewheel","time":1392,"x":378,"y":383,"deltaY":3},{"type":"mousewheel","time":1411,"x":378,"y":383,"deltaY":4},{"type":"mousewheel","time":1429,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":1448,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":1468,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1488,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1509,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1531,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":1846,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":1862,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":1879,"x":378,"y":383,"deltaY":-8},{"type":"mousewheel","time":1896,"x":378,"y":383,"deltaY":-17},{"type":"mousewheel","time":1929,"x":378,"y":383,"deltaY":-39},{"type":"mousewheel","time":1946,"x":378,"y":383,"deltaY":-79},{"type":"mousewheel","time":1964,"x":378,"y":383,"deltaY":-31},{"type":"mousewheel","time":1982,"x":378,"y":383,"deltaY":-47},{"type":"mousewheel","time":2000,"x":378,"y":383,"deltaY":-54},{"type":"mousewheel","time":2018,"x":378,"y":383,"deltaY":-58},{"type":"mousewheel","time":2035,"x":378,"y":383,"deltaY":-58},{"type":"mousewheel","time":2054,"x":378,"y":383,"deltaY":-56},{"type":"mousewheel","time":2070,"x":378,"y":383,"deltaY":-114},{"type":"mousewheel","time":2088,"x":378,"y":383,"deltaY":-56},{"type":"mousewheel","time":2105,"x":378,"y":383,"deltaY":-53},{"type":"mousewheel","time":2122,"x":378,"y":383,"deltaY":-49},{"type":"mousewheel","time":2138,"x":378,"y":383,"deltaY":-45},{"type":"mousewheel","time":2155,"x":378,"y":383,"deltaY":-41},{"type":"mousewheel","time":2172,"x":378,"y":383,"deltaY":-38},{"type":"mousewheel","time":2189,"x":378,"y":383,"deltaY":-35},{"type":"mousewheel","time":2209,"x":378,"y":383,"deltaY":-32},{"type":"mousewheel","time":2229,"x":378,"y":383,"deltaY":-30},{"type":"mousewheel","time":2246,"x":378,"y":383,"deltaY":-27},{"type":"mousewheel","time":2263,"x":378,"y":383,"deltaY":-24},{"type":"mousewheel","time":2280,"x":378,"y":383,"deltaY":-22},{"type":"mousewheel","time":2297,"x":378,"y":383,"deltaY":-20},{"type":"mousewheel","time":2315,"x":378,"y":383,"deltaY":-19},{"type":"mousewheel","time":2335,"x":378,"y":383,"deltaY":-17},{"type":"mousewheel","time":2356,"x":378,"y":383,"deltaY":-16},{"type":"mousewheel","time":2374,"x":378,"y":383,"deltaY":-27},{"type":"mousewheel","time":2394,"x":378,"y":383,"deltaY":-12},{"type":"mousewheel","time":2496,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":2513,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":2530,"x":378,"y":383,"deltaY":13},{"type":"mousewheel","time":2549,"x":378,"y":383,"deltaY":20},{"type":"mousewheel","time":2567,"x":378,"y":383,"deltaY":26},{"type":"mousewheel","time":2585,"x":378,"y":383,"deltaY":29},{"type":"mousewheel","time":2605,"x":378,"y":383,"deltaY":21},{"type":"mousewheel","time":2623,"x":378,"y":383,"deltaY":21},{"type":"mousewheel","time":2642,"x":378,"y":383,"deltaY":16},{"type":"mousewheel","time":2661,"x":378,"y":383,"deltaY":28},{"type":"mousewheel","time":2679,"x":378,"y":383,"deltaY":97},{"type":"mousewheel","time":2699,"x":378,"y":383,"deltaY":33},{"type":"mousewheel","time":2717,"x":378,"y":383,"deltaY":32},{"type":"mousewheel","time":2735,"x":378,"y":383,"deltaY":36},{"type":"mousewheel","time":2752,"x":378,"y":383,"deltaY":35},{"type":"mousewheel","time":2769,"x":378,"y":383,"deltaY":32},{"type":"mousewheel","time":2787,"x":378,"y":383,"deltaY":29},{"type":"mousewheel","time":2805,"x":378,"y":383,"deltaY":26},{"type":"mousewheel","time":2822,"x":378,"y":383,"deltaY":24},{"type":"mousewheel","time":2841,"x":378,"y":383,"deltaY":22},{"type":"mousewheel","time":2860,"x":378,"y":383,"deltaY":20},{"type":"mousewheel","time":2879,"x":378,"y":383,"deltaY":36},{"type":"mousewheel","time":2906,"x":378,"y":383,"deltaY":16},{"type":"mousewheel","time":2925,"x":378,"y":383,"deltaY":14},{"type":"mousewheel","time":2945,"x":378,"y":383,"deltaY":25},{"type":"mousewheel","time":2966,"x":378,"y":383,"deltaY":11},{"type":"mousewheel","time":2988,"x":378,"y":383,"deltaY":10},{"type":"mousewheel","time":3007,"x":378,"y":383,"deltaY":9},{"type":"mousewheel","time":3027,"x":378,"y":383,"deltaY":8},{"type":"mousewheel","time":3045,"x":378,"y":383,"deltaY":15},{"type":"mousewheel","time":3062,"x":378,"y":383,"deltaY":7},{"type":"mousewheel","time":3129,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":3147,"x":378,"y":383,"deltaY":4},{"type":"mousewheel","time":3166,"x":378,"y":383,"deltaY":7},{"type":"mousewheel","time":3188,"x":378,"y":383,"deltaY":12},{"type":"mousewheel","time":3210,"x":378,"y":383,"deltaY":20},{"type":"mousewheel","time":3231,"x":378,"y":383,"deltaY":47},{"type":"mousewheel","time":3249,"x":378,"y":383,"deltaY":37},{"type":"mousewheel","time":3267,"x":378,"y":383,"deltaY":86},{"type":"mousewheel","time":3285,"x":378,"y":383,"deltaY":43},{"type":"mousewheel","time":3304,"x":378,"y":383,"deltaY":42},{"type":"mousewheel","time":3321,"x":378,"y":383,"deltaY":41},{"type":"mousewheel","time":3339,"x":378,"y":383,"deltaY":45},{"type":"mousewheel","time":3356,"x":378,"y":383,"deltaY":43},{"type":"mousewheel","time":3373,"x":378,"y":383,"deltaY":39},{"type":"mousewheel","time":3391,"x":378,"y":383,"deltaY":37},{"type":"mousewheel","time":3409,"x":378,"y":383,"deltaY":33},{"type":"mousewheel","time":3428,"x":378,"y":383,"deltaY":31},{"type":"mousewheel","time":3448,"x":378,"y":383,"deltaY":28},{"type":"mousewheel","time":3471,"x":378,"y":383,"deltaY":50},{"type":"mousewheel","time":3493,"x":378,"y":383,"deltaY":22},{"type":"mousewheel","time":3546,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":3565,"x":378,"y":383,"deltaY":9},{"type":"mousewheel","time":3585,"x":378,"y":383,"deltaY":8},{"type":"mousewheel","time":3604,"x":378,"y":383,"deltaY":10},{"type":"mousewheel","time":3622,"x":378,"y":383,"deltaY":14},{"type":"mousewheel","time":3641,"x":378,"y":383,"deltaY":13},{"type":"mousewheel","time":3659,"x":378,"y":383,"deltaY":12},{"type":"mousewheel","time":3678,"x":378,"y":383,"deltaY":21},{"type":"mousewheel","time":3697,"x":378,"y":383,"deltaY":6},{"type":"mousewheel","time":3717,"x":378,"y":383,"deltaY":4},{"type":"mousewheel","time":3736,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":3754,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":3773,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":3846,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":3862,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":3879,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":3896,"x":378,"y":383,"deltaY":-9},{"type":"mousewheel","time":3914,"x":378,"y":383,"deltaY":-17},{"type":"mousewheel","time":3932,"x":378,"y":383,"deltaY":-20},{"type":"mousewheel","time":3952,"x":378,"y":383,"deltaY":-28},{"type":"mousewheel","time":3972,"x":378,"y":383,"deltaY":-28},{"type":"mousewheel","time":3991,"x":378,"y":383,"deltaY":-31},{"type":"mousewheel","time":4010,"x":378,"y":383,"deltaY":-69},{"type":"mousewheel","time":4031,"x":378,"y":383,"deltaY":-34},{"type":"mousewheel","time":4052,"x":378,"y":383,"deltaY":-35},{"type":"mousewheel","time":4072,"x":378,"y":383,"deltaY":-33},{"type":"mousewheel","time":4091,"x":378,"y":383,"deltaY":-36},{"type":"mousewheel","time":4116,"x":378,"y":383,"deltaY":-67},{"type":"mousewheel","time":4135,"x":378,"y":383,"deltaY":-29},{"type":"mousewheel","time":4155,"x":378,"y":383,"deltaY":-26},{"type":"mousewheel","time":4175,"x":378,"y":383,"deltaY":-24},{"type":"mousewheel","time":4196,"x":378,"y":383,"deltaY":-42},{"type":"mousewheel","time":4218,"x":378,"y":383,"deltaY":-19},{"type":"mousewheel","time":4238,"x":378,"y":383,"deltaY":-17},{"type":"mousewheel","time":4257,"x":378,"y":383,"deltaY":-16},{"type":"mousewheel","time":4278,"x":378,"y":383,"deltaY":-14},{"type":"mousewheel","time":4297,"x":378,"y":383,"deltaY":-25},{"type":"mousewheel","time":4317,"x":378,"y":383,"deltaY":-11},{"type":"mousewheel","time":4336,"x":378,"y":383,"deltaY":-10},{"type":"mousewheel","time":4355,"x":378,"y":383,"deltaY":-9},{"type":"mousewheel","time":4375,"x":378,"y":383,"deltaY":-8},{"type":"mousewheel","time":4395,"x":378,"y":383,"deltaY":-15},{"type":"mousewheel","time":4419,"x":378,"y":383,"deltaY":-7},{"type":"mousewheel","time":4438,"x":378,"y":383,"deltaY":-6},{"type":"mousewheel","time":4458,"x":378,"y":383,"deltaY":-5},{"type":"mousewheel","time":4477,"x":378,"y":383,"deltaY":-10},{"type":"mousewheel","time":4496,"x":378,"y":383,"deltaY":-5},{"type":"mousewheel","time":4515,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":4535,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":4562,"x":378,"y":383,"deltaY":-4},{"type":"mousewheel","time":4584,"x":378,"y":383,"deltaY":-6},{"type":"mousewheel","time":4607,"x":378,"y":383,"deltaY":-3},{"type":"mousewheel","time":4680,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":4702,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":4725,"x":378,"y":383,"deltaY":3},{"type":"mousewheel","time":4748,"x":378,"y":383,"deltaY":9},{"type":"mousewheel","time":4778,"x":378,"y":383,"deltaY":7},{"type":"mousewheel","time":4799,"x":378,"y":383,"deltaY":13},{"type":"mousewheel","time":4821,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":4843,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":4868,"x":378,"y":383,"deltaY":7},{"type":"mousewheel","time":4890,"x":378,"y":383,"deltaY":3},{"type":"mousewheel","time":4915,"x":378,"y":383,"deltaY":3},{"type":"mousewheel","time":4939,"x":378,"y":383,"deltaY":7},{"type":"mousewheel","time":4971,"x":378,"y":383,"deltaY":5},{"type":"mousewheel","time":4993,"x":378,"y":383,"deltaY":10},{"type":"mousewheel","time":5015,"x":378,"y":383,"deltaY":6},{"type":"mousewheel","time":5044,"x":378,"y":383,"deltaY":1},{"type":"mousewheel","time":5069,"x":378,"y":383,"deltaY":2},{"type":"mousewheel","time":5105,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":5129,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":5164,"x":378,"y":383,"deltaY":-1},{"type":"mousewheel","time":5193,"x":378,"y":383,"deltaY":-2},{"type":"mousewheel","time":5223,"x":378,"y":383,"deltaY":-1},{"type":"mousemove","time":5436,"x":385,"y":392},{"type":"mousemove","time":5645,"x":479,"y":512},{"type":"mousemove","time":5858,"x":489,"y":511},{"type":"mousemove","time":6180,"x":491,"y":513},{"type":"mousemove","time":6396,"x":519,"y":536},{"type":"mousewheel","time":6541,"x":519,"y":536,"deltaY":-1},{"type":"mousewheel","time":6574,"x":519,"y":536,"deltaY":-4},{"type":"mousewheel","time":6615,"x":519,"y":536,"deltaY":-7},{"type":"mousewheel","time":6663,"x":519,"y":536,"deltaY":-19},{"type":"mousewheel","time":6708,"x":519,"y":536,"deltaY":-13},{"type":"mousewheel","time":6754,"x":519,"y":536,"deltaY":-11},{"type":"mousewheel","time":6802,"x":519,"y":536,"deltaY":-19},{"type":"mousewheel","time":6843,"x":519,"y":536,"deltaY":-23},{"type":"mousewheel","time":6880,"x":519,"y":536,"deltaY":-10},{"type":"mousewheel","time":6917,"x":519,"y":536,"deltaY":-41},{"type":"mousewheel","time":6957,"x":519,"y":536,"deltaY":-30},{"type":"mousewheel","time":6984,"x":519,"y":536,"deltaY":-17},{"type":"mousewheel","time":7008,"x":519,"y":536,"deltaY":-15},{"type":"mousewheel","time":7048,"x":519,"y":536,"deltaY":-6},{"type":"mousewheel","time":7096,"x":519,"y":536,"deltaY":-6},{"type":"mousewheel","time":7188,"x":519,"y":536,"deltaY":-1},{"type":"mousewheel","time":7218,"x":519,"y":536,"deltaY":-2},{"type":"mousewheel","time":7249,"x":519,"y":536,"deltaY":-3},{"type":"mousewheel","time":7281,"x":519,"y":536,"deltaY":-6},{"type":"mousewheel","time":7318,"x":519,"y":536,"deltaY":-4},{"type":"mousewheel","time":7351,"x":519,"y":536,"deltaY":-4},{"type":"mousewheel","time":7381,"x":519,"y":536,"deltaY":-6},{"type":"mousewheel","time":7719,"x":519,"y":536,"deltaY":1},{"type":"mousewheel","time":7740,"x":519,"y":536,"deltaY":2},{"type":"mousewheel","time":7769,"x":519,"y":536,"deltaY":5},{"type":"mousewheel","time":7797,"x":519,"y":536,"deltaY":9},{"type":"mousewheel","time":7828,"x":519,"y":536,"deltaY":27},{"type":"mousewheel","time":7860,"x":519,"y":536,"deltaY":32},{"type":"mousewheel","time":7886,"x":519,"y":536,"deltaY":26},{"type":"mousewheel","time":7917,"x":519,"y":536,"deltaY":19},{"type":"mousewheel","time":7952,"x":519,"y":536,"deltaY":42},{"type":"mousewheel","time":7979,"x":519,"y":536,"deltaY":42},{"type":"mousewheel","time":8011,"x":519,"y":536,"deltaY":43},{"type":"mousewheel","time":8037,"x":519,"y":536,"deltaY":42},{"type":"mousewheel","time":8058,"x":519,"y":536,"deltaY":19},{"type":"mousewheel","time":8087,"x":519,"y":536,"deltaY":17},{"type":"mousewheel","time":8116,"x":519,"y":536,"deltaY":30},{"type":"mousewheel","time":8149,"x":519,"y":536,"deltaY":25},{"type":"mousewheel","time":8184,"x":519,"y":536,"deltaY":21},{"type":"mousewheel","time":8218,"x":519,"y":536,"deltaY":17},{"type":"mousewheel","time":8245,"x":519,"y":536,"deltaY":15},{"type":"mousewheel","time":8277,"x":519,"y":536,"deltaY":12},{"type":"mousewheel","time":8306,"x":519,"y":536,"deltaY":10},{"type":"mousewheel","time":8338,"x":519,"y":536,"deltaY":9},{"type":"mousewheel","time":8360,"x":519,"y":536,"deltaY":4},{"type":"mousewheel","time":8388,"x":519,"y":536,"deltaY":7},{"type":"mousewheel","time":8421,"x":519,"y":536,"deltaY":3},{"type":"mousedown","time":8943,"x":519,"y":536},{"type":"mouseup","time":8953,"x":519,"y":536},{"time":8954,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9082,"x":522,"y":535},{"type":"mousemove","time":9290,"x":540,"y":519},{"type":"mousedown","time":9473,"x":540,"y":519},{"type":"mousemove","time":9483,"x":540,"y":519},{"type":"mousemove","time":9683,"x":464,"y":545},{"type":"mousemove","time":9891,"x":448,"y":554},{"type":"mousemove","time":10107,"x":504,"y":537},{"type":"mouseup","time":10257,"x":504,"y":537},{"time":10258,"delay":400,"type":"screenshot-auto"},{"type":"mousewheel","time":10796,"x":504,"y":537,"deltaY":-1},{"type":"mousewheel","time":10815,"x":504,"y":537,"deltaY":-5},{"type":"mousewheel","time":10836,"x":504,"y":537,"deltaY":-7},{"type":"mousewheel","time":10857,"x":504,"y":537,"deltaY":-7},{"type":"mousewheel","time":10879,"x":504,"y":537,"deltaY":-11},{"type":"mousewheel","time":10900,"x":504,"y":537,"deltaY":-22},{"type":"mousewheel","time":10924,"x":504,"y":537,"deltaY":-10},{"type":"mousewheel","time":10946,"x":504,"y":537,"deltaY":-7},{"type":"mousewheel","time":10969,"x":504,"y":537,"deltaY":-9},{"type":"mousewheel","time":10998,"x":504,"y":537,"deltaY":-3},{"type":"mousewheel","time":11022,"x":504,"y":537,"deltaY":-6},{"type":"mousewheel","time":11042,"x":504,"y":537,"deltaY":-18},{"type":"mousewheel","time":11065,"x":504,"y":537,"deltaY":-6},{"type":"mousewheel","time":11086,"x":504,"y":537,"deltaY":-6},{"type":"mousewheel","time":11109,"x":504,"y":537,"deltaY":-12},{"type":"mousewheel","time":11131,"x":504,"y":537,"deltaY":-5},{"type":"mousewheel","time":11151,"x":504,"y":537,"deltaY":-5},{"type":"mousewheel","time":11172,"x":504,"y":537,"deltaY":-5},{"type":"mousewheel","time":11196,"x":504,"y":537,"deltaY":-8},{"type":"mousewheel","time":11217,"x":504,"y":537,"deltaY":-4},{"type":"mousewheel","time":11241,"x":504,"y":537,"deltaY":-3},{"type":"mousewheel","time":11270,"x":504,"y":537,"deltaY":-6},{"type":"mousedown","time":11592,"x":504,"y":537},{"type":"mousemove","time":11602,"x":505,"y":537},{"type":"mousemove","time":11809,"x":538,"y":512},{"type":"mousemove","time":12012,"x":575,"y":486},{"type":"mousemove","time":12212,"x":566,"y":497},{"type":"mousemove","time":12421,"x":556,"y":507},{"type":"mouseup","time":12542,"x":556,"y":507},{"time":12543,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12553,"x":538,"y":524},{"type":"mousemove","time":12756,"x":491,"y":561},{"type":"mousedown","time":12957,"x":491,"y":561},{"type":"mousemove","time":12967,"x":492,"y":561},{"type":"mousemove","time":13175,"x":546,"y":544},{"type":"mousemove","time":13379,"x":512,"y":559},{"type":"mousemove","time":13589,"x":484,"y":572},{"type":"mousemove","time":13812,"x":484,"y":572},{"type":"mousemove","time":14012,"x":493,"y":557},{"type":"mousemove","time":14212,"x":524,"y":542},{"type":"mousemove","time":14422,"x":490,"y":564},{"type":"mouseup","time":14623,"x":490,"y":564},{"time":14624,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14632,"x":496,"y":560},{"type":"mousemove","time":14839,"x":512,"y":550},{"type":"mousedown","time":15073,"x":512,"y":550},{"type":"mousemove","time":15084,"x":512,"y":550},{"type":"mousemove","time":15287,"x":495,"y":553},{"type":"mouseup","time":15424,"x":495,"y":553},{"time":15425,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15435,"x":467,"y":552},{"type":"mousemove","time":15643,"x":322,"y":551},{"type":"mousedown","time":15824,"x":315,"y":553},{"type":"mousemove","time":15873,"x":315,"y":553},{"type":"mouseup","time":15922,"x":315,"y":553},{"time":15923,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16229,"x":315,"y":554},{"type":"mousemove","time":16440,"x":319,"y":557},{"type":"mousedown","time":16639,"x":319,"y":557},{"type":"mousemove","time":16656,"x":319,"y":557},{"type":"mouseup","time":16739,"x":319,"y":557},{"time":16740,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16946,"x":321,"y":557},{"type":"mousemove","time":17158,"x":520,"y":536},{"type":"mousemove","time":17372,"x":523,"y":536},{"type":"mousewheel","time":17481,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17501,"x":523,"y":536,"deltaY":-3},{"type":"mousewheel","time":17521,"x":523,"y":536,"deltaY":-8},{"type":"mousewheel","time":17542,"x":523,"y":536,"deltaY":-10},{"type":"mousewheel","time":17563,"x":523,"y":536,"deltaY":-20},{"type":"mousewheel","time":17584,"x":523,"y":536,"deltaY":-7},{"type":"mousewheel","time":17604,"x":523,"y":536,"deltaY":-6},{"type":"mousewheel","time":17625,"x":523,"y":536,"deltaY":-3},{"type":"mousewheel","time":17649,"x":523,"y":536,"deltaY":-3},{"type":"mousewheel","time":17670,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17692,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17765,"x":523,"y":536,"deltaY":0},{"type":"mousewheel","time":17786,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17807,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17827,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17848,"x":523,"y":536,"deltaY":-2},{"type":"mousewheel","time":17869,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17895,"x":523,"y":536,"deltaY":-1},{"type":"mousewheel","time":17919,"x":523,"y":536,"deltaY":4},{"type":"mousewheel","time":17940,"x":523,"y":536,"deltaY":4},{"type":"mousewheel","time":17962,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":17984,"x":523,"y":536,"deltaY":12},{"type":"mousewheel","time":18007,"x":523,"y":536,"deltaY":6},{"type":"mousewheel","time":18028,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":18049,"x":523,"y":536,"deltaY":11},{"type":"mousewheel","time":18072,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":18097,"x":523,"y":536,"deltaY":9},{"type":"mousewheel","time":18120,"x":523,"y":536,"deltaY":4},{"type":"mousewheel","time":18141,"x":523,"y":536,"deltaY":4},{"type":"mousewheel","time":18162,"x":523,"y":536,"deltaY":8},{"type":"mousewheel","time":18184,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":18205,"x":523,"y":536,"deltaY":6},{"type":"mousewheel","time":18227,"x":523,"y":536,"deltaY":6},{"type":"mousewheel","time":18250,"x":523,"y":536,"deltaY":14},{"type":"mousewheel","time":18272,"x":523,"y":536,"deltaY":7},{"type":"mousewheel","time":18295,"x":523,"y":536,"deltaY":8},{"type":"mousewheel","time":18316,"x":523,"y":536,"deltaY":14},{"type":"mousewheel","time":18345,"x":523,"y":536,"deltaY":7},{"type":"mousewheel","time":18366,"x":523,"y":536,"deltaY":15},{"type":"mousewheel","time":18387,"x":523,"y":536,"deltaY":7},{"type":"mousewheel","time":18409,"x":523,"y":536,"deltaY":6},{"type":"mousewheel","time":18431,"x":523,"y":536,"deltaY":11},{"type":"mousewheel","time":18454,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":18476,"x":523,"y":536,"deltaY":5},{"type":"mousewheel","time":18500,"x":523,"y":536,"deltaY":8},{"type":"mousewheel","time":18524,"x":523,"y":536,"deltaY":4},{"type":"mousewheel","time":18549,"x":523,"y":536,"deltaY":7},{"type":"mousewheel","time":18571,"x":523,"y":536,"deltaY":3},{"type":"mousewheel","time":18593,"x":523,"y":536,"deltaY":3},{"type":"mousedown","time":18724,"x":523,"y":536},{"type":"mouseup","time":18822,"x":523,"y":536},{"time":18823,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":19473,"x":523,"y":536},{"type":"mousemove","time":19483,"x":523,"y":536},{"type":"mousemove","time":19691,"x":510,"y":567},{"type":"mousemove","time":19908,"x":506,"y":578},{"type":"mouseup","time":20057,"x":506,"y":578},{"time":20058,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20113,"x":522,"y":535},{"type":"mousemove","time":20323,"x":531,"y":507},{"type":"mousedown","time":20441,"x":531,"y":507},{"type":"mousemove","time":20452,"x":530,"y":509},{"type":"mousemove","time":20659,"x":506,"y":542},{"type":"mousemove","time":20874,"x":506,"y":544},{"type":"mouseup","time":20924,"x":506,"y":544},{"time":20925,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21141,"x":334,"y":478},{"type":"mousedown","time":21224,"x":334,"y":478},{"type":"mousemove","time":21235,"x":331,"y":475},{"type":"mousemove","time":21445,"x":339,"y":448},{"type":"mousemove","time":21659,"x":340,"y":448},{"type":"mouseup","time":21742,"x":340,"y":448},{"time":21743,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":21754,"x":278,"y":365},{"type":"mousemove","time":21957,"x":109,"y":179},{"type":"mousemove","time":22164,"x":126,"y":173},{"type":"mousemove","time":22375,"x":130,"y":168},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":23369,"target":"select"},{"time":23370,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":23451,"x":128,"y":158},{"type":"mousemove","time":23661,"x":128,"y":158},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":24745,"target":"select"},{"time":24746,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":24766,"x":211,"y":148},{"type":"mousemove","time":24974,"x":232,"y":163},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"1","time":26203,"target":"select"},{"time":26204,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":26233,"x":204,"y":210},{"type":"mousemove","time":26441,"x":148,"y":277},{"type":"mousedown","time":26512,"x":148,"y":277},{"type":"mousemove","time":26525,"x":156,"y":275},{"type":"mousemove","time":26725,"x":241,"y":287},{"type":"mousemove","time":26939,"x":193,"y":303},{"type":"mouseup","time":27041,"x":193,"y":303},{"time":27042,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":27721,"x":523,"y":519},{"type":"mousemove","time":27731,"x":523,"y":519},{"type":"mousemove","time":27939,"x":487,"y":512},{"type":"mousemove","time":28156,"x":504,"y":513},{"type":"mouseup","time":28207,"x":504,"y":513},{"time":28208,"delay":400,"type":"screenshot-auto"},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"2","time":30247,"target":"select"},{"time":30248,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":30280,"x":226,"y":267},{"type":"mousemove","time":30488,"x":206,"y":371},{"type":"mousedown","time":31856,"x":206,"y":371},{"type":"mousemove","time":31866,"x":206,"y":371},{"type":"mousemove","time":32074,"x":226,"y":361},{"type":"mousemove","time":32290,"x":212,"y":369},{"type":"mouseup","time":32523,"x":212,"y":369},{"time":32524,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32534,"x":196,"y":388},{"type":"mousemove","time":32739,"x":177,"y":402},{"type":"mousewheel","time":32996,"x":177,"y":402,"deltaY":1},{"type":"mousewheel","time":33018,"x":177,"y":402,"deltaY":4},{"type":"mousewheel","time":33040,"x":177,"y":402,"deltaY":2},{"type":"mousewheel","time":33062,"x":177,"y":402,"deltaY":2},{"type":"mousewheel","time":33085,"x":177,"y":402,"deltaY":2},{"type":"mousewheel","time":33107,"x":177,"y":402,"deltaY":1},{"type":"mousewheel","time":33128,"x":177,"y":402,"deltaY":1},{"type":"mousewheel","time":33330,"x":177,"y":402,"deltaY":-1},{"type":"mousewheel","time":33352,"x":177,"y":402,"deltaY":-1},{"type":"mousewheel","time":33374,"x":177,"y":402,"deltaY":-3},{"type":"mousewheel","time":33397,"x":177,"y":402,"deltaY":-5},{"type":"mousewheel","time":33420,"x":177,"y":402,"deltaY":-2},{"type":"mousewheel","time":33442,"x":177,"y":402,"deltaY":-1},{"type":"mousewheel","time":33464,"x":177,"y":402,"deltaY":-1},{"type":"mousemove","time":33680,"x":179,"y":396},{"type":"mousemove","time":33880,"x":243,"y":213},{"type":"mousemove","time":34090,"x":241,"y":178},{"type":"mousemove","time":34305,"x":247,"y":158},{"type":"mousemove","time":35046,"x":250,"y":158},{"type":"mousemove","time":35246,"x":401,"y":325},{"type":"mousemove","time":35455,"x":455,"y":413},{"type":"mousemove","time":35663,"x":514,"y":512},{"type":"mousemove","time":35872,"x":514,"y":513},{"type":"mousedown","time":36188,"x":514,"y":513},{"type":"mousemove","time":36198,"x":514,"y":513},{"type":"mousemove","time":36406,"x":518,"y":513},{"type":"mousemove","time":36622,"x":514,"y":516},{"type":"mousewheel","time":36889,"x":514,"y":516,"deltaY":1},{"type":"mouseup","time":36899,"x":514,"y":516},{"time":36900,"delay":400,"type":"screenshot-auto"},{"type":"mousewheel","time":36909,"x":514,"y":516,"deltaY":1},{"type":"mousewheel","time":36930,"x":514,"y":516,"deltaY":3},{"type":"mousewheel","time":36954,"x":514,"y":516,"deltaY":4},{"type":"mousewheel","time":36978,"x":514,"y":516,"deltaY":4},{"type":"mousewheel","time":37001,"x":514,"y":516,"deltaY":5},{"type":"mousewheel","time":37026,"x":514,"y":516,"deltaY":1},{"type":"mousewheel","time":37052,"x":514,"y":516,"deltaY":2},{"type":"mousewheel","time":37130,"x":514,"y":516,"deltaY":-1},{"type":"mousewheel","time":37152,"x":514,"y":516,"deltaY":-1},{"type":"mousewheel","time":37176,"x":514,"y":516,"deltaY":-2},{"type":"mousewheel","time":37200,"x":514,"y":516,"deltaY":-4},{"type":"mousewheel","time":37223,"x":514,"y":516,"deltaY":-2},{"type":"mousewheel","time":37248,"x":514,"y":516,"deltaY":-2},{"type":"mousewheel","time":37274,"x":514,"y":516,"deltaY":-3},{"type":"mousewheel","time":37298,"x":514,"y":516,"deltaY":-2},{"type":"mousewheel","time":37323,"x":514,"y":516,"deltaY":-1},{"type":"mousewheel","time":37346,"x":514,"y":516,"deltaY":-1},{"type":"mousewheel","time":37369,"x":514,"y":516,"deltaY":-1},{"type":"mousemove","time":37696,"x":514,"y":514},{"type":"mousemove","time":37906,"x":248,"y":214},{"type":"mousemove","time":38107,"x":254,"y":171},{"type":"mousemove","time":38315,"x":252,"y":168},{"type":"mousemove","time":38524,"x":251,"y":168},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"3","time":39319,"target":"select"},{"time":39320,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":39343,"x":248,"y":208},{"type":"mousemove","time":39543,"x":244,"y":389},{"type":"mousedown","time":40260,"x":244,"y":389},{"type":"mousemove","time":40275,"x":249,"y":386},{"type":"mousemove","time":40476,"x":307,"y":371},{"type":"mousemove","time":40692,"x":259,"y":397},{"type":"mousemove","time":40908,"x":259,"y":397},{"type":"mouseup","time":41008,"x":259,"y":397},{"time":41009,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41021,"x":275,"y":402},{"type":"mousemove","time":41224,"x":579,"y":534},{"type":"mousemove","time":41440,"x":561,"y":551},{"type":"mousedown","time":41641,"x":561,"y":551},{"type":"mousemove","time":41651,"x":561,"y":551},{"type":"mousemove","time":41859,"x":575,"y":547},{"type":"mousemove","time":42062,"x":564,"y":550},{"type":"mouseup","time":42273,"x":563,"y":550},{"time":42274,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":42284,"x":549,"y":531},{"type":"mousemove","time":42490,"x":328,"y":341},{"type":"mousewheel","time":42779,"x":328,"y":341,"deltaY":1},{"type":"mousewheel","time":42799,"x":328,"y":341,"deltaY":2},{"type":"mousewheel","time":42820,"x":328,"y":341,"deltaY":6},{"type":"mousewheel","time":42841,"x":328,"y":341,"deltaY":2},{"type":"mousewheel","time":42863,"x":328,"y":341,"deltaY":1},{"type":"mousewheel","time":42883,"x":328,"y":341,"deltaY":1},{"type":"mousewheel","time":42904,"x":328,"y":341,"deltaY":2},{"type":"mousemove","time":43597,"x":331,"y":343},{"type":"mousemove","time":43807,"x":494,"y":529},{"type":"mousemove","time":44024,"x":503,"y":538},{"type":"mousewheel","time":44279,"x":503,"y":538,"deltaY":2},{"type":"mousewheel","time":44300,"x":503,"y":538,"deltaY":3},{"type":"mousewheel","time":44321,"x":503,"y":538,"deltaY":7},{"type":"mousewheel","time":44345,"x":503,"y":538,"deltaY":3},{"type":"mousewheel","time":44366,"x":503,"y":537,"deltaY":2},{"type":"mousewheel","time":44389,"x":503,"y":537,"deltaY":3},{"type":"mousewheel","time":44413,"x":503,"y":537,"deltaY":1},{"type":"mousewheel","time":44436,"x":503,"y":537,"deltaY":2},{"type":"mousemove","time":44764,"x":501,"y":533},{"type":"mousemove","time":44978,"x":209,"y":170},{"type":"mousemove","time":45213,"x":210,"y":169},{"type":"mousemove","time":45414,"x":241,"y":162},{"type":"mousemove","time":45623,"x":242,"y":162},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"0","time":46770,"target":"select"},{"time":46771,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":46809,"x":322,"y":130},{"type":"mousemove","time":47009,"x":368,"y":165},{"type":"mousemove","time":47241,"x":368,"y":165},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":48278,"target":"select"},{"time":48279,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48315,"x":367,"y":179},{"type":"mousemove","time":48525,"x":373,"y":164},{"type":"mousemove","time":48740,"x":374,"y":162},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":49951,"target":"select"},{"time":49952,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":50013,"x":409,"y":181},{"type":"mousemove","time":50238,"x":314,"y":300},{"type":"mousemove","time":50442,"x":306,"y":317},{"type":"mousewheel","time":50813,"x":306,"y":317,"deltaY":-1},{"type":"mousewheel","time":50834,"x":306,"y":317,"deltaY":-2},{"type":"mousewheel","time":50856,"x":306,"y":317,"deltaY":-3},{"type":"mousewheel","time":50877,"x":306,"y":317,"deltaY":-2},{"type":"mousewheel","time":50900,"x":306,"y":317,"deltaY":-2},{"type":"mousewheel","time":50922,"x":306,"y":317,"deltaY":-1},{"type":"mousemove","time":51280,"x":308,"y":317},{"type":"mousemove","time":51480,"x":517,"y":204},{"type":"mousemove","time":51684,"x":537,"y":183},{"type":"mousemove","time":51892,"x":541,"y":156},{"type":"valuechange","selector":"#main0>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":52905,"target":"select"},{"time":52906,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":52930,"x":534,"y":181},{"type":"mousemove","time":53143,"x":419,"y":266},{"type":"mousemove","time":53349,"x":331,"y":344},{"type":"mousemove","time":53963,"x":333,"y":344},{"type":"mousemove","time":54164,"x":361,"y":439},{"type":"mousemove","time":54375,"x":341,"y":506},{"type":"mousemove","time":54913,"x":341,"y":506},{"type":"mousemove","time":55114,"x":327,"y":522},{"type":"mousemove","time":55326,"x":310,"y":543},{"type":"mousemove","time":55526,"x":310,"y":545},{"type":"mousemove","time":55735,"x":316,"y":547},{"type":"mousemove","time":55935,"x":341,"y":504},{"type":"mousedown","time":62623,"x":341,"y":504},{"type":"mousemove","time":62633,"x":341,"y":504},{"type":"mousemove","time":62840,"x":321,"y":500},{"type":"mousemove","time":63046,"x":311,"y":498},{"type":"mousemove","time":63256,"x":309,"y":498},{"type":"mouseup","time":63275,"x":309,"y":498},{"time":63276,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":63286,"x":327,"y":437},{"type":"mousemove","time":63491,"x":328,"y":338},{"type":"mousemove","time":63706,"x":321,"y":347},{"type":"mousedown","time":63856,"x":321,"y":347},{"type":"mousemove","time":63866,"x":321,"y":347},{"type":"mousemove","time":64073,"x":274,"y":371},{"type":"mouseup","time":64306,"x":274,"y":371},{"time":64307,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":64796,"x":272,"y":376},{"type":"mousemove","time":65007,"x":255,"y":410},{"type":"mousewheel","time":65230,"x":255,"y":410,"deltaY":1},{"type":"mousewheel","time":65251,"x":255,"y":410,"deltaY":4},{"type":"mousewheel","time":65273,"x":255,"y":410,"deltaY":6},{"type":"mousewheel","time":65297,"x":255,"y":410,"deltaY":11},{"type":"mousewheel","time":65320,"x":255,"y":410,"deltaY":4},{"type":"mousewheel","time":65342,"x":255,"y":410,"deltaY":3},{"type":"mousewheel","time":65365,"x":255,"y":410,"deltaY":3},{"type":"mousemove","time":65680,"x":255,"y":411},{"type":"mousemove","time":65880,"x":249,"y":435},{"type":"mousewheel","time":66030,"x":248,"y":438,"deltaY":1},{"type":"mousewheel","time":66054,"x":248,"y":438,"deltaY":1},{"type":"mousewheel","time":66079,"x":248,"y":438,"deltaY":2},{"type":"mousewheel","time":66106,"x":248,"y":438,"deltaY":4},{"type":"mousewheel","time":66131,"x":248,"y":438,"deltaY":3},{"type":"mousewheel","time":66156,"x":248,"y":438,"deltaY":6},{"type":"mousewheel","time":66186,"x":248,"y":438,"deltaY":4},{"type":"mousewheel","time":66210,"x":248,"y":438,"deltaY":2},{"type":"mousemove","time":66235,"x":248,"y":438},{"type":"mousewheel","time":66247,"x":248,"y":438,"deltaY":2},{"type":"mousewheel","time":66272,"x":248,"y":438,"deltaY":1},{"type":"mousemove","time":66446,"x":248,"y":437},{"type":"mousemove","time":66647,"x":300,"y":346},{"type":"mousedown","time":66907,"x":300,"y":346},{"type":"mousemove","time":66919,"x":299,"y":349},{"type":"mousemove","time":67127,"x":261,"y":491},{"type":"mouseup","time":67340,"x":261,"y":491},{"time":67341,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":67352,"x":289,"y":485},{"type":"mousemove","time":67562,"x":528,"y":518},{"type":"mousemove","time":67779,"x":540,"y":523},{"type":"mousewheel","time":67913,"x":540,"y":523,"deltaY":-1},{"type":"mousewheel","time":67937,"x":540,"y":523,"deltaY":-4},{"type":"mousewheel","time":67963,"x":540,"y":523,"deltaY":-5},{"type":"mousewheel","time":67989,"x":540,"y":523,"deltaY":-16},{"type":"mousewheel","time":68015,"x":540,"y":523,"deltaY":-16},{"type":"mousewheel","time":68041,"x":540,"y":523,"deltaY":-6},{"type":"mousewheel","time":68073,"x":540,"y":523,"deltaY":-7},{"type":"mousewheel","time":68101,"x":540,"y":523,"deltaY":-4},{"type":"mousewheel","time":68127,"x":540,"y":523,"deltaY":-1},{"type":"mousewheel","time":68151,"x":540,"y":523,"deltaY":-3},{"type":"mousewheel","time":68176,"x":540,"y":523,"deltaY":-2},{"type":"mousewheel","time":68201,"x":540,"y":523,"deltaY":-6},{"type":"mousewheel","time":68226,"x":540,"y":523,"deltaY":-4},{"type":"mousewheel","time":68252,"x":540,"y":523,"deltaY":-6},{"type":"mousewheel","time":68276,"x":540,"y":523,"deltaY":-2},{"type":"mousewheel","time":68305,"x":540,"y":523,"deltaY":-2},{"type":"mousewheel","time":68530,"x":540,"y":523,"deltaY":-1},{"type":"mousewheel","time":68680,"x":540,"y":523,"deltaY":1},{"type":"mousewheel","time":68706,"x":540,"y":523,"deltaY":1},{"type":"mousewheel","time":68731,"x":540,"y":523,"deltaY":1},{"type":"mousewheel","time":68757,"x":540,"y":523,"deltaY":4},{"type":"mousewheel","time":68782,"x":540,"y":523,"deltaY":4},{"type":"mousewheel","time":68808,"x":540,"y":523,"deltaY":1},{"type":"mousewheel","time":68836,"x":540,"y":523,"deltaY":1},{"type":"mousemove","time":69030,"x":534,"y":523},{"type":"mousemove","time":69232,"x":360,"y":578},{"type":"mousemove","time":69442,"x":348,"y":569},{"type":"mousedown","time":69626,"x":332,"y":560},{"type":"mousemove","time":69675,"x":332,"y":560},{"type":"mouseup","time":69759,"x":332,"y":560},{"time":69760,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":70210,"x":332,"y":560},{"type":"mouseup","time":70275,"x":332,"y":560},{"time":70276,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":70432,"x":341,"y":558},{"type":"mousemove","time":70632,"x":518,"y":538},{"type":"mousemove","time":70845,"x":553,"y":528},{"type":"mousewheel","time":71497,"x":553,"y":528,"deltaY":-1},{"type":"mousewheel","time":71522,"x":553,"y":528,"deltaY":-4},{"type":"mousewheel","time":71547,"x":553,"y":528,"deltaY":-3},{"type":"mousewheel","time":71572,"x":553,"y":528,"deltaY":-6},{"type":"mousewheel","time":71598,"x":553,"y":528,"deltaY":-2},{"type":"mousewheel","time":71622,"x":553,"y":528,"deltaY":-2},{"type":"mousewheel","time":71648,"x":553,"y":528,"deltaY":0},{"type":"mousewheel","time":71676,"x":553,"y":528,"deltaY":-1},{"type":"mousewheel","time":71730,"x":553,"y":528,"deltaY":-1},{"type":"mousewheel","time":71754,"x":553,"y":528,"deltaY":-1},{"type":"mousewheel","time":71779,"x":553,"y":528,"deltaY":-2},{"type":"mousewheel","time":71804,"x":553,"y":528,"deltaY":-4},{"type":"mousewheel","time":71830,"x":553,"y":528,"deltaY":-2},{"type":"mousewheel","time":71856,"x":553,"y":528,"deltaY":-2},{"type":"mousewheel","time":71887,"x":553,"y":528,"deltaY":0},{"type":"mousewheel","time":71930,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":71955,"x":553,"y":528,"deltaY":2},{"type":"mousewheel","time":71982,"x":553,"y":528,"deltaY":2},{"type":"mousewheel","time":72010,"x":553,"y":528,"deltaY":3},{"type":"mousewheel","time":72038,"x":553,"y":528,"deltaY":2},{"type":"mousewheel","time":72065,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72123,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72147,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72189,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72221,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72247,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72273,"x":553,"y":528,"deltaY":4},{"type":"mousewheel","time":72300,"x":553,"y":528,"deltaY":2},{"type":"mousewheel","time":72328,"x":553,"y":528,"deltaY":1},{"type":"mousewheel","time":72354,"x":553,"y":528,"deltaY":1},{"type":"mousemove","time":72630,"x":552,"y":528},{"type":"mousemove","time":72845,"x":308,"y":416},{"type":"mousedown","time":73014,"x":308,"y":416},{"type":"mousemove","time":73029,"x":311,"y":415},{"type":"mousemove","time":73247,"x":407,"y":393},{"type":"mousemove","time":73453,"x":364,"y":399},{"type":"mousemove","time":73661,"x":360,"y":399},{"type":"mouseup","time":73859,"x":360,"y":399},{"time":73860,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":73873,"x":372,"y":397},{"type":"mousemove","time":74079,"x":447,"y":368},{"type":"mousedown","time":74096,"x":448,"y":368},{"type":"mouseup","time":74171,"x":448,"y":368},{"time":74172,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":74296,"x":448,"y":368}],"scrollY":0,"scrollX":0,"timestamp":1750343219896},{"name":"Action 2","ops":[{"type":"mousewheel","time":777,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":806,"x":315,"y":326,"deltaY":-5},{"type":"mousewheel","time":835,"x":315,"y":326,"deltaY":-11},{"type":"mousewheel","time":869,"x":315,"y":326,"deltaY":-10},{"type":"mousewheel","time":897,"x":315,"y":326,"deltaY":-9},{"type":"mousewheel","time":926,"x":315,"y":326,"deltaY":-3},{"type":"mousewheel","time":954,"x":315,"y":326,"deltaY":-4},{"type":"mousewheel","time":984,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1011,"x":315,"y":326,"deltaY":0},{"type":"mousewheel","time":1040,"x":315,"y":326,"deltaY":0},{"type":"mousewheel","time":1160,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":1187,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":1213,"x":315,"y":326,"deltaY":-4},{"type":"mousewheel","time":1239,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1266,"x":315,"y":326,"deltaY":-4},{"type":"mousewheel","time":1291,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1317,"x":315,"y":326,"deltaY":-4},{"type":"mousewheel","time":1346,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1372,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1398,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":1527,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1556,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1590,"x":315,"y":326,"deltaY":6},{"type":"mousewheel","time":1616,"x":315,"y":326,"deltaY":6},{"type":"mousewheel","time":1641,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":1667,"x":315,"y":326,"deltaY":4},{"type":"mousewheel","time":1692,"x":315,"y":326,"deltaY":3},{"type":"mousewheel","time":1717,"x":315,"y":326,"deltaY":6},{"type":"mousewheel","time":1743,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":1767,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":1792,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1817,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":1843,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1870,"x":315,"y":326,"deltaY":4},{"type":"mousewheel","time":1895,"x":315,"y":326,"deltaY":4},{"type":"mousewheel","time":1920,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1944,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":1969,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":1993,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":2018,"x":315,"y":326,"deltaY":3},{"type":"mousewheel","time":2042,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2072,"x":315,"y":326,"deltaY":4},{"type":"mousewheel","time":2097,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2123,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":2149,"x":315,"y":326,"deltaY":3},{"type":"mousewheel","time":2174,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2199,"x":315,"y":326,"deltaY":6},{"type":"mousewheel","time":2224,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2248,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2272,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":2360,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2383,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2419,"x":315,"y":326,"deltaY":1},{"type":"mousewheel","time":2442,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2465,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2488,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2516,"x":315,"y":326,"deltaY":2},{"type":"mousewheel","time":2553,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2577,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2604,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2631,"x":315,"y":326,"deltaY":-4},{"type":"mousewheel","time":2657,"x":315,"y":326,"deltaY":-2},{"type":"mousewheel","time":2684,"x":315,"y":326,"deltaY":-3},{"type":"mousewheel","time":2712,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2746,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2774,"x":315,"y":326,"deltaY":0},{"type":"mousewheel","time":2802,"x":315,"y":326,"deltaY":0},{"type":"mousewheel","time":2833,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2863,"x":315,"y":326,"deltaY":-1},{"type":"mousewheel","time":2891,"x":315,"y":326,"deltaY":-1},{"type":"mousedown","time":3106,"x":315,"y":326},{"type":"mousemove","time":3119,"x":318,"y":325},{"type":"mousemove","time":3321,"x":446,"y":309},{"type":"mousemove","time":3526,"x":323,"y":322},{"type":"mouseup","time":3738,"x":315,"y":322},{"time":3739,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3751,"x":352,"y":317},{"type":"mousemove","time":3954,"x":629,"y":338},{"type":"mousemove","time":4160,"x":566,"y":341},{"type":"mousemove","time":4370,"x":556,"y":329},{"type":"mousewheel","time":4527,"x":556,"y":329,"deltaY":1},{"type":"mousewheel","time":4551,"x":556,"y":329,"deltaY":7},{"type":"mousewheel","time":4577,"x":556,"y":329,"deltaY":10},{"type":"mousewheel","time":4604,"x":556,"y":329,"deltaY":27},{"type":"mousewheel","time":4631,"x":556,"y":329,"deltaY":23},{"type":"mousewheel","time":4658,"x":556,"y":329,"deltaY":7},{"type":"mousewheel","time":4691,"x":556,"y":329,"deltaY":9},{"type":"mousewheel","time":4718,"x":556,"y":329,"deltaY":4},{"type":"mousewheel","time":4744,"x":556,"y":329,"deltaY":2},{"type":"mousewheel","time":4771,"x":556,"y":329,"deltaY":6},{"type":"mousewheel","time":4798,"x":556,"y":329,"deltaY":4},{"type":"mousewheel","time":4824,"x":556,"y":329,"deltaY":2},{"type":"mousewheel","time":4850,"x":556,"y":329,"deltaY":4},{"type":"mousewheel","time":4876,"x":556,"y":329,"deltaY":1},{"type":"mousewheel","time":4901,"x":556,"y":329,"deltaY":2},{"type":"mousewheel","time":4993,"x":556,"y":329,"deltaY":-1},{"type":"mousewheel","time":5019,"x":556,"y":329,"deltaY":-1},{"type":"mousewheel","time":5045,"x":556,"y":329,"deltaY":-5},{"type":"mousewheel","time":5070,"x":556,"y":329,"deltaY":-3},{"type":"mousewheel","time":5097,"x":556,"y":329,"deltaY":-8},{"type":"mousewheel","time":5123,"x":556,"y":329,"deltaY":-4},{"type":"mousewheel","time":5150,"x":556,"y":329,"deltaY":-8},{"type":"mousewheel","time":5176,"x":556,"y":329,"deltaY":-3},{"type":"mousewheel","time":5203,"x":556,"y":329,"deltaY":-3},{"type":"mousewheel","time":5228,"x":556,"y":329,"deltaY":-2},{"type":"mousewheel","time":5269,"x":556,"y":329,"deltaY":-1},{"type":"mousewheel","time":5294,"x":556,"y":329,"deltaY":-1},{"type":"mousemove","time":5593,"x":558,"y":329},{"type":"mousemove","time":5806,"x":660,"y":311},{"type":"mousedown","time":6023,"x":676,"y":308},{"type":"mousemove","time":6054,"x":676,"y":308},{"type":"mouseup","time":6122,"x":676,"y":308},{"time":6123,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7659,"x":670,"y":308},{"type":"mousemove","time":7866,"x":291,"y":417},{"type":"mousemove","time":8078,"x":286,"y":412},{"type":"mousedown","time":8227,"x":296,"y":407},{"type":"mousemove","time":8302,"x":296,"y":407},{"type":"mouseup","time":8356,"x":296,"y":407},{"time":8357,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":8609,"x":296,"y":407},{"type":"mouseup","time":8724,"x":296,"y":407},{"time":8725,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8828,"x":300,"y":407},{"type":"mousemove","time":9040,"x":337,"y":393},{"type":"mousemove","time":9259,"x":310,"y":409},{"type":"mousemove","time":9478,"x":469,"y":349},{"type":"mousemove","time":9691,"x":526,"y":330},{"type":"mousemove","time":9877,"x":526,"y":330}],"scrollY":472,"scrollX":0,"timestamp":1750343303266},{"name":"Action 3","ops":[{"type":"mousemove","time":291,"x":608,"y":281},{"type":"mousemove","time":507,"x":346,"y":292},{"type":"mousemove","time":716,"x":342,"y":294},{"type":"mousewheel","time":919,"x":342,"y":294,"deltaY":-1},{"type":"mousewheel","time":948,"x":342,"y":294,"deltaY":-4},{"type":"mousewheel","time":978,"x":342,"y":294,"deltaY":-15},{"type":"mousewheel","time":1009,"x":342,"y":294,"deltaY":-13},{"type":"mousewheel","time":1044,"x":342,"y":294,"deltaY":-5},{"type":"mousewheel","time":1080,"x":342,"y":294,"deltaY":-9},{"type":"mousewheel","time":1116,"x":342,"y":294,"deltaY":-5},{"type":"mousewheel","time":1158,"x":342,"y":294,"deltaY":-9},{"type":"mousewheel","time":1188,"x":342,"y":294,"deltaY":-15},{"type":"mousewheel","time":1215,"x":342,"y":294,"deltaY":-19},{"type":"mousewheel","time":1251,"x":342,"y":294,"deltaY":-18},{"type":"mousewheel","time":1286,"x":342,"y":294,"deltaY":-11},{"type":"mousewheel","time":1321,"x":342,"y":294,"deltaY":-48},{"type":"mousewheel","time":1357,"x":342,"y":294,"deltaY":-23},{"type":"mousewheel","time":1393,"x":342,"y":294,"deltaY":-23},{"type":"mousewheel","time":1434,"x":342,"y":294,"deltaY":-27},{"type":"mousewheel","time":1467,"x":342,"y":294,"deltaY":-15},{"type":"mousewheel","time":1510,"x":342,"y":294,"deltaY":-3},{"type":"mousewheel","time":1538,"x":342,"y":294,"deltaY":-7},{"type":"mousewheel","time":1567,"x":342,"y":294,"deltaY":-62},{"type":"mousewheel","time":1596,"x":342,"y":294,"deltaY":-131},{"type":"mousewheel","time":1623,"x":342,"y":294,"deltaY":-105},{"type":"mousewheel","time":1650,"x":342,"y":294,"deltaY":-331},{"type":"mousewheel","time":1683,"x":342,"y":294,"deltaY":-204},{"type":"mousewheel","time":1710,"x":342,"y":294,"deltaY":-188},{"type":"mousewheel","time":1739,"x":342,"y":294,"deltaY":-87},{"type":"mousewheel","time":1768,"x":342,"y":294,"deltaY":-159},{"type":"mousewheel","time":1795,"x":342,"y":294,"deltaY":-142},{"type":"mousewheel","time":1822,"x":342,"y":294,"deltaY":-64},{"type":"mousewheel","time":1850,"x":342,"y":294,"deltaY":-115},{"type":"mousewheel","time":1876,"x":342,"y":294,"deltaY":-98},{"type":"mousewheel","time":1925,"x":342,"y":294,"deltaY":-2},{"type":"mousewheel","time":1953,"x":342,"y":294,"deltaY":-1},{"type":"mousewheel","time":1981,"x":342,"y":294,"deltaY":-12},{"type":"mousewheel","time":2011,"x":342,"y":294,"deltaY":-25},{"type":"mousewheel","time":2040,"x":342,"y":294,"deltaY":-12},{"type":"mousewheel","time":2067,"x":342,"y":294,"deltaY":-17},{"type":"mousewheel","time":2094,"x":342,"y":294,"deltaY":-77},{"type":"mousewheel","time":2121,"x":342,"y":294,"deltaY":-18},{"type":"mousewheel","time":2153,"x":342,"y":294,"deltaY":-40},{"type":"mousewheel","time":2179,"x":342,"y":294,"deltaY":-34},{"type":"mousewheel","time":2206,"x":342,"y":294,"deltaY":-14},{"type":"mousewheel","time":2247,"x":342,"y":294,"deltaY":2},{"type":"mousewheel","time":2277,"x":342,"y":294,"deltaY":29},{"type":"mousewheel","time":2308,"x":342,"y":294,"deltaY":67},{"type":"mousewheel","time":2336,"x":342,"y":294,"deltaY":32},{"type":"mousewheel","time":2363,"x":342,"y":294,"deltaY":53},{"type":"mousewheel","time":2398,"x":342,"y":294,"deltaY":187},{"type":"mousewheel","time":2425,"x":342,"y":294,"deltaY":120},{"type":"mousewheel","time":2453,"x":342,"y":294,"deltaY":61},{"type":"mousewheel","time":2487,"x":342,"y":294,"deltaY":113},{"type":"mousewheel","time":2514,"x":342,"y":294,"deltaY":98},{"type":"mousewheel","time":2541,"x":342,"y":294,"deltaY":83},{"type":"mousewheel","time":2566,"x":342,"y":294,"deltaY":37},{"type":"mousewheel","time":2592,"x":342,"y":294,"deltaY":65},{"type":"mousewheel","time":2618,"x":342,"y":294,"deltaY":28},{"type":"mousewheel","time":2644,"x":342,"y":294,"deltaY":26},{"type":"mousewheel","time":2686,"x":342,"y":294,"deltaY":7},{"type":"mousewheel","time":2712,"x":342,"y":294,"deltaY":62},{"type":"mousewheel","time":2741,"x":342,"y":294,"deltaY":132},{"type":"mousewheel","time":2769,"x":342,"y":294,"deltaY":103},{"type":"mousewheel","time":2796,"x":342,"y":294,"deltaY":330},{"type":"mousewheel","time":2824,"x":342,"y":294,"deltaY":103},{"type":"mousewheel","time":2853,"x":342,"y":294,"deltaY":196},{"type":"mousewheel","time":2883,"x":342,"y":294,"deltaY":179},{"type":"mousewheel","time":2918,"x":342,"y":294,"deltaY":82},{"type":"mousewheel","time":2948,"x":342,"y":294,"deltaY":151},{"type":"mousewheel","time":2978,"x":342,"y":294,"deltaY":132},{"type":"mousewheel","time":3008,"x":342,"y":294,"deltaY":115},{"type":"mousewheel","time":3040,"x":342,"y":294,"deltaY":51},{"type":"mousemove","time":3055,"x":334,"y":292},{"type":"mousemove","time":3267,"x":65,"y":296},{"type":"mousemove","time":3481,"x":47,"y":301},{"type":"mousewheel","time":3619,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":3644,"x":47,"y":301,"deltaY":-5},{"type":"mousewheel","time":3669,"x":47,"y":301,"deltaY":-11},{"type":"mousewheel","time":3701,"x":47,"y":301,"deltaY":-71},{"type":"mousewheel","time":3734,"x":47,"y":301,"deltaY":-41},{"type":"mousewheel","time":3764,"x":47,"y":301,"deltaY":-59},{"type":"mousewheel","time":3792,"x":47,"y":301,"deltaY":-274},{"type":"mousewheel","time":3820,"x":47,"y":301,"deltaY":-132},{"type":"mousewheel","time":3847,"x":47,"y":301,"deltaY":-64},{"type":"mousewheel","time":3873,"x":47,"y":301,"deltaY":-114},{"type":"mousewheel","time":3900,"x":47,"y":301,"deltaY":-98},{"type":"mousewheel","time":3932,"x":47,"y":301,"deltaY":-45},{"type":"mousewheel","time":3959,"x":47,"y":301,"deltaY":-78},{"type":"mousewheel","time":3987,"x":47,"y":301,"deltaY":-67},{"type":"mousewheel","time":4021,"x":47,"y":301,"deltaY":-55},{"type":"mousewheel","time":4049,"x":47,"y":301,"deltaY":-24},{"type":"mousewheel","time":4077,"x":47,"y":301,"deltaY":-42},{"type":"mousewheel","time":4105,"x":47,"y":301,"deltaY":-36},{"type":"mousewheel","time":4146,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":4171,"x":47,"y":301,"deltaY":-3},{"type":"mousewheel","time":4197,"x":47,"y":301,"deltaY":-6},{"type":"mousewheel","time":4223,"x":47,"y":301,"deltaY":-9},{"type":"mousewheel","time":4250,"x":47,"y":301,"deltaY":-4},{"type":"mousewheel","time":4282,"x":47,"y":301,"deltaY":-5},{"type":"mousewheel","time":4311,"x":47,"y":301,"deltaY":-4},{"type":"mousewheel","time":4340,"x":47,"y":301,"deltaY":-3},{"type":"mousewheel","time":4367,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":4393,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":4420,"x":47,"y":301,"deltaY":0},{"type":"mousewheel","time":4619,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":4644,"x":47,"y":301,"deltaY":-2},{"type":"mousewheel","time":4670,"x":47,"y":301,"deltaY":-4},{"type":"mousewheel","time":4697,"x":47,"y":301,"deltaY":-7},{"type":"mousewheel","time":4730,"x":47,"y":301,"deltaY":-3},{"type":"mousewheel","time":4759,"x":47,"y":301,"deltaY":5},{"type":"mousewheel","time":4787,"x":47,"y":301,"deltaY":9},{"type":"mousewheel","time":4816,"x":47,"y":301,"deltaY":30},{"type":"mousewheel","time":4850,"x":47,"y":301,"deltaY":19},{"type":"mousewheel","time":4880,"x":47,"y":301,"deltaY":7},{"type":"mousewheel","time":4912,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":4947,"x":47,"y":301,"deltaY":-22},{"type":"mousewheel","time":4986,"x":47,"y":301,"deltaY":-59},{"type":"mousewheel","time":5020,"x":47,"y":301,"deltaY":-64},{"type":"mousewheel","time":5064,"x":47,"y":301,"deltaY":-25},{"type":"mousewheel","time":5097,"x":47,"y":301,"deltaY":-8},{"type":"mousewheel","time":5129,"x":47,"y":301,"deltaY":-18},{"type":"mousewheel","time":5163,"x":47,"y":301,"deltaY":-102},{"type":"mousewheel","time":5198,"x":47,"y":301,"deltaY":-45},{"type":"mousewheel","time":5235,"x":47,"y":301,"deltaY":-39},{"type":"mousewheel","time":5286,"x":47,"y":301,"deltaY":-47},{"type":"mousewheel","time":5339,"x":47,"y":301,"deltaY":-1},{"type":"mousewheel","time":5373,"x":47,"y":301,"deltaY":-18},{"type":"mousewheel","time":5408,"x":47,"y":301,"deltaY":-84},{"type":"mousewheel","time":5442,"x":47,"y":301,"deltaY":-67},{"type":"mousewheel","time":5485,"x":47,"y":301,"deltaY":-49},{"type":"mousewheel","time":5529,"x":47,"y":301,"deltaY":-240},{"type":"mousewheel","time":5573,"x":47,"y":301,"deltaY":-177},{"type":"mousewheel","time":5614,"x":47,"y":301,"deltaY":-153},{"type":"mousewheel","time":5653,"x":47,"y":301,"deltaY":-83},{"type":"mousewheel","time":5694,"x":47,"y":301,"deltaY":-71},{"type":"mousewheel","time":5762,"x":47,"y":301,"deltaY":-2},{"type":"mousewheel","time":5800,"x":47,"y":301,"deltaY":-88},{"type":"mousewheel","time":5838,"x":47,"y":301,"deltaY":-49},{"type":"mousewheel","time":5886,"x":47,"y":301,"deltaY":-39},{"type":"mousewheel","time":5927,"x":47,"y":301,"deltaY":-237},{"type":"mousewheel","time":5964,"x":47,"y":301,"deltaY":-99},{"type":"mousewheel","time":6005,"x":47,"y":301,"deltaY":-86},{"type":"mousewheel","time":6043,"x":47,"y":301,"deltaY":-105},{"type":"mousewheel","time":6083,"x":47,"y":301,"deltaY":-57},{"type":"mousewheel","time":6132,"x":47,"y":301,"deltaY":-46},{"type":"mousewheel","time":6217,"x":47,"y":301,"deltaY":1},{"type":"mousewheel","time":6251,"x":47,"y":301,"deltaY":5},{"type":"mousewheel","time":6295,"x":47,"y":301,"deltaY":42},{"type":"mousewheel","time":6330,"x":47,"y":301,"deltaY":95},{"type":"mousewheel","time":6370,"x":47,"y":301,"deltaY":41},{"type":"mousewheel","time":6403,"x":47,"y":301,"deltaY":147},{"type":"mousewheel","time":6436,"x":47,"y":301,"deltaY":95},{"type":"mousewheel","time":6470,"x":47,"y":301,"deltaY":98},{"type":"mousewheel","time":6510,"x":47,"y":301,"deltaY":86},{"type":"mousewheel","time":6546,"x":47,"y":301,"deltaY":105},{"type":"mousewheel","time":6586,"x":47,"y":301,"deltaY":57},{"type":"mousewheel","time":6620,"x":47,"y":301,"deltaY":46},{"type":"mousewheel","time":6661,"x":47,"y":301,"deltaY":39},{"type":"mousewheel","time":6696,"x":47,"y":301,"deltaY":47},{"type":"mousewheel","time":6733,"x":47,"y":301,"deltaY":25},{"type":"mousewheel","time":6775,"x":47,"y":301,"deltaY":11},{"type":"mousewheel","time":6835,"x":47,"y":301,"deltaY":2},{"type":"mousewheel","time":6871,"x":47,"y":301,"deltaY":66},{"type":"mousewheel","time":6913,"x":47,"y":301,"deltaY":127},{"type":"mousewheel","time":6949,"x":47,"y":301,"deltaY":46},{"type":"mousewheel","time":6985,"x":47,"y":301,"deltaY":73},{"type":"mousewheel","time":7022,"x":47,"y":301,"deltaY":413},{"type":"mousewheel","time":7068,"x":47,"y":301,"deltaY":157},{"type":"mousewheel","time":7105,"x":47,"y":301,"deltaY":200},{"type":"mousewheel","time":7146,"x":47,"y":301,"deltaY":113},{"type":"mousewheel","time":7180,"x":47,"y":301,"deltaY":141},{"type":"mousewheel","time":7215,"x":47,"y":301,"deltaY":77},{"type":"mousewheel","time":7251,"x":47,"y":301,"deltaY":65},{"type":"mousewheel","time":7295,"x":47,"y":301,"deltaY":54},{"type":"mousewheel","time":7333,"x":47,"y":301,"deltaY":66},{"type":"mousewheel","time":7381,"x":47,"y":301,"deltaY":36},{"type":"mousewheel","time":7418,"x":47,"y":301,"deltaY":30},{"type":"mousedown","time":7499,"x":47,"y":301},{"type":"mousemove","time":7513,"x":47,"y":308},{"type":"mousemove","time":7738,"x":40,"y":372},{"type":"mousemove","time":8035,"x":43,"y":366},{"type":"mouseup","time":8252,"x":43,"y":366},{"time":8253,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8267,"x":50,"y":328},{"type":"mousemove","time":8479,"x":52,"y":290},{"type":"mousewheel","time":8641,"x":52,"y":290,"deltaY":-1},{"type":"mousewheel","time":8674,"x":52,"y":290,"deltaY":-14},{"type":"mousewheel","time":8709,"x":52,"y":290,"deltaY":-60},{"type":"mousewheel","time":8748,"x":52,"y":290,"deltaY":-87},{"type":"mousewheel","time":8784,"x":52,"y":290,"deltaY":-63},{"type":"mousewheel","time":8820,"x":52,"y":290,"deltaY":-291},{"type":"mousewheel","time":8858,"x":52,"y":290,"deltaY":-203},{"type":"mousewheel","time":8891,"x":52,"y":290,"deltaY":-120},{"type":"mousewheel","time":8928,"x":52,"y":290,"deltaY":-104},{"type":"mousewheel","time":8970,"x":52,"y":290,"deltaY":-90},{"type":"mousewheel","time":9014,"x":52,"y":290,"deltaY":-110},{"type":"mousewheel","time":9048,"x":52,"y":290,"deltaY":-59},{"type":"mousewheel","time":9086,"x":52,"y":290,"deltaY":-50},{"type":"mousewheel","time":9125,"x":52,"y":290,"deltaY":-41},{"type":"mousewheel","time":9189,"x":52,"y":290,"deltaY":-1},{"type":"mousewheel","time":9228,"x":52,"y":290,"deltaY":-119},{"type":"mousewheel","time":9269,"x":52,"y":290,"deltaY":-68},{"type":"mousewheel","time":9319,"x":52,"y":290,"deltaY":-105},{"type":"mousewheel","time":9355,"x":52,"y":290,"deltaY":-722},{"type":"mousewheel","time":9397,"x":52,"y":290,"deltaY":-169},{"type":"mousewheel","time":9437,"x":52,"y":290,"deltaY":-151},{"type":"mousewheel","time":9477,"x":52,"y":290,"deltaY":-192},{"type":"mousewheel","time":9515,"x":52,"y":290,"deltaY":-106},{"type":"mousewheel","time":9562,"x":52,"y":290,"deltaY":-133},{"type":"mousewheel","time":9605,"x":52,"y":290,"deltaY":-73},{"type":"mousemove","time":9622,"x":60,"y":290},{"type":"mousemove","time":9831,"x":290,"y":393},{"type":"mousemove","time":10050,"x":305,"y":406},{"type":"mousedown","time":10249,"x":303,"y":414},{"type":"mousemove","time":10264,"x":303,"y":414},{"type":"mouseup","time":10440,"x":303,"y":414},{"time":10441,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10689,"x":303,"y":414},{"type":"mousemove","time":10901,"x":28,"y":317},{"type":"mousemove","time":11111,"x":42,"y":294},{"type":"mousewheel","time":11289,"x":43,"y":292,"deltaY":-1},{"type":"mousemove","time":11319,"x":43,"y":292},{"type":"mousewheel","time":11335,"x":43,"y":292,"deltaY":-4},{"type":"mousewheel","time":11362,"x":43,"y":292,"deltaY":-45},{"type":"mousewheel","time":11393,"x":43,"y":292,"deltaY":-28},{"type":"mousewheel","time":11427,"x":43,"y":292,"deltaY":-39},{"type":"mousewheel","time":11457,"x":43,"y":292,"deltaY":-184},{"type":"mousewheel","time":11486,"x":43,"y":292,"deltaY":-44},{"type":"mousewheel","time":11513,"x":43,"y":292,"deltaY":-92},{"type":"mousewheel","time":11545,"x":43,"y":292,"deltaY":-80},{"type":"mousewheel","time":11578,"x":43,"y":292,"deltaY":-35},{"type":"mousewheel","time":11627,"x":43,"y":292,"deltaY":-2},{"type":"mousewheel","time":11660,"x":43,"y":292,"deltaY":-21},{"type":"mousewheel","time":11693,"x":43,"y":292,"deltaY":-17},{"type":"mousewheel","time":11735,"x":43,"y":292,"deltaY":-34},{"type":"mousewheel","time":11767,"x":43,"y":292,"deltaY":-15},{"type":"mousewheel","time":11799,"x":43,"y":292,"deltaY":-66},{"type":"mousewheel","time":11829,"x":43,"y":292,"deltaY":-16},{"type":"mousewheel","time":11864,"x":43,"y":292,"deltaY":-35},{"type":"mousemove","time":11900,"x":52,"y":292},{"type":"mousewheel","time":11914,"x":43,"y":292,"deltaY":-16},{"type":"mousemove","time":12113,"x":315,"y":409},{"type":"mousemove","time":12322,"x":314,"y":416},{"type":"mousemove","time":12527,"x":301,"y":423},{"type":"mousemove","time":12739,"x":293,"y":412},{"type":"mousedown","time":12838,"x":291,"y":411},{"type":"mouseup","time":12961,"x":291,"y":411},{"time":12962,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13007,"x":291,"y":411},{"type":"mousemove","time":13410,"x":291,"y":410},{"type":"mousemove","time":13631,"x":287,"y":323},{"type":"mousewheel","time":13909,"x":287,"y":323,"deltaY":-1},{"type":"mousewheel","time":13947,"x":287,"y":323,"deltaY":-3},{"type":"mousewheel","time":13986,"x":287,"y":323,"deltaY":-9},{"type":"mousewheel","time":14026,"x":287,"y":323,"deltaY":-7},{"type":"mousewheel","time":14075,"x":287,"y":323,"deltaY":-3},{"type":"mousewheel","time":14114,"x":287,"y":323,"deltaY":-1}],"scrollY":890,"scrollX":0,"timestamp":1750343958054},{"name":"Action 4","ops":[{"type":"mousemove","time":790,"x":270,"y":380},{"type":"mousemove","time":991,"x":165,"y":385},{"type":"mousemove","time":1199,"x":136,"y":198},{"type":"mousemove","time":1403,"x":137,"y":178},{"type":"mousemove","time":1620,"x":139,"y":173},{"type":"valuechange","selector":"#main_in_matrix>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":2606,"target":"select"},{"time":2607,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2641,"x":141,"y":155},{"type":"mousemove","time":2843,"x":140,"y":163},{"type":"mousemove","time":3058,"x":138,"y":166},{"type":"mousemove","time":3287,"x":138,"y":167},{"type":"mousemove","time":5524,"x":229,"y":432},{"type":"mousewheel","time":5584,"x":229,"y":432,"deltaY":1},{"type":"mousewheel","time":5624,"x":229,"y":432,"deltaY":3},{"type":"mousewheel","time":5659,"x":229,"y":432,"deltaY":13},{"type":"mousewheel","time":5695,"x":229,"y":432,"deltaY":18},{"type":"mousewheel","time":5728,"x":229,"y":432,"deltaY":21},{"type":"mousewheel","time":5763,"x":229,"y":432,"deltaY":16},{"type":"mousewheel","time":5796,"x":229,"y":432,"deltaY":9},{"type":"mousewheel","time":5829,"x":229,"y":432,"deltaY":3},{"type":"mousewheel","time":5865,"x":229,"y":432,"deltaY":2},{"type":"mousewheel","time":5896,"x":229,"y":432,"deltaY":0},{"type":"mousewheel","time":5927,"x":229,"y":432,"deltaY":1},{"type":"mousewheel","time":5960,"x":229,"y":432,"deltaY":1},{"type":"mousewheel","time":5991,"x":229,"y":432,"deltaY":2},{"type":"mousewheel","time":6022,"x":229,"y":432,"deltaY":2},{"type":"mousewheel","time":6053,"x":229,"y":432,"deltaY":2},{"type":"mousewheel","time":6089,"x":229,"y":432,"deltaY":1},{"type":"mousewheel","time":6166,"x":229,"y":432,"deltaY":-1},{"type":"mousewheel","time":6199,"x":229,"y":432,"deltaY":-1},{"type":"mousewheel","time":6231,"x":229,"y":432,"deltaY":-14},{"type":"mousewheel","time":6264,"x":229,"y":432,"deltaY":-35},{"type":"mousewheel","time":6300,"x":229,"y":432,"deltaY":-20},{"type":"mousewheel","time":6332,"x":229,"y":432,"deltaY":-34},{"type":"mousewheel","time":6364,"x":229,"y":432,"deltaY":-8},{"type":"mousewheel","time":6397,"x":229,"y":432,"deltaY":-3},{"type":"mousewheel","time":6429,"x":229,"y":432,"deltaY":-4},{"type":"mousewheel","time":6462,"x":229,"y":432,"deltaY":-3},{"type":"mousewheel","time":6495,"x":229,"y":432,"deltaY":-2},{"type":"mousewheel","time":6534,"x":229,"y":432,"deltaY":-1},{"type":"mousemove","time":6774,"x":235,"y":432},{"type":"mousemove","time":6978,"x":540,"y":511},{"type":"mousemove","time":7195,"x":536,"y":515},{"type":"mousedown","time":7438,"x":536,"y":515},{"type":"mousemove","time":7453,"x":531,"y":515},{"type":"mousemove","time":7673,"x":426,"y":525},{"type":"mousemove","time":7878,"x":437,"y":555},{"type":"mousemove","time":8091,"x":522,"y":533},{"type":"mousemove","time":8310,"x":522,"y":529},{"type":"mousewheel","time":8476,"x":522,"y":529,"deltaY":1},{"type":"mouseup","time":8493,"x":522,"y":529},{"time":8494,"delay":400,"type":"screenshot-auto"},{"type":"mousewheel","time":8509,"x":522,"y":529,"deltaY":4},{"type":"mousewheel","time":8541,"x":522,"y":529,"deltaY":11},{"type":"mousewheel","time":8575,"x":522,"y":529,"deltaY":12},{"type":"mousewheel","time":8612,"x":522,"y":529,"deltaY":8},{"type":"mousewheel","time":8647,"x":522,"y":529,"deltaY":8},{"type":"mousewheel","time":8681,"x":522,"y":529,"deltaY":7},{"type":"mousewheel","time":8717,"x":522,"y":529,"deltaY":5},{"type":"mousewheel","time":8751,"x":522,"y":529,"deltaY":1},{"type":"mousewheel","time":8835,"x":522,"y":529,"deltaY":-1},{"type":"mousewheel","time":8867,"x":522,"y":529,"deltaY":-1},{"type":"mousewheel","time":8901,"x":522,"y":529,"deltaY":-10},{"type":"mousewheel","time":8935,"x":522,"y":529,"deltaY":-36},{"type":"mousewheel","time":8969,"x":522,"y":529,"deltaY":-53},{"type":"mousewheel","time":9002,"x":522,"y":529,"deltaY":-28},{"type":"mousewheel","time":9035,"x":522,"y":529,"deltaY":-14},{"type":"mousewheel","time":9074,"x":522,"y":529,"deltaY":-3},{"type":"mousemove","time":9207,"x":516,"y":529},{"type":"mousemove","time":9408,"x":341,"y":563},{"type":"mousedown","time":9619,"x":324,"y":565},{"type":"mousemove","time":9652,"x":324,"y":565},{"type":"mouseup","time":9733,"x":324,"y":565},{"time":9734,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10042,"x":324,"y":565},{"type":"mousedown","time":10102,"x":326,"y":562},{"type":"mouseup","time":10203,"x":327,"y":562},{"time":10204,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10265,"x":327,"y":562},{"type":"mousemove","time":10393,"x":329,"y":562},{"type":"mousemove","time":10609,"x":519,"y":437},{"type":"mousemove","time":10812,"x":547,"y":418},{"type":"mousedown","time":10954,"x":553,"y":411},{"type":"mousemove","time":11043,"x":553,"y":411},{"type":"mouseup","time":11058,"x":553,"y":411},{"time":11059,"delay":400,"type":"screenshot-auto"}],"scrollY":1245,"scrollX":0,"timestamp":1750343977599}] \ No newline at end of file diff --git a/test/runTest/actions/matrix3.json b/test/runTest/actions/matrix3.json new file mode 100644 index 0000000000..ba6fe8c4bf --- /dev/null +++ b/test/runTest/actions/matrix3.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":257,"x":280,"y":290},{"type":"mousemove","time":465,"x":155,"y":58},{"type":"mousemove","time":673,"x":135,"y":39},{"type":"mousemove","time":880,"x":127,"y":28},{"type":"valuechange","selector":"#main_layout_components_series>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":1719,"target":"select"},{"time":1720,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1732,"x":270,"y":36},{"type":"mousemove","time":1952,"x":274,"y":32},{"type":"valuechange","selector":"#main_layout_components_series>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"1","time":2952,"target":"select"},{"time":2953,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2962,"x":141,"y":37},{"type":"valuechange","selector":"#main_layout_components_series>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":4230,"target":"select"},{"time":4231,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4242,"x":319,"y":29},{"type":"mousemove","time":4478,"x":296,"y":26},{"type":"valuechange","selector":"#main_layout_components_series>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"0","time":5563,"target":"select"},{"time":5564,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5573,"x":199,"y":205},{"type":"mousemove","time":5783,"x":145,"y":327},{"type":"mousemove","time":5985,"x":488,"y":395},{"type":"mousemove","time":6210,"x":540,"y":418},{"type":"mousemove","time":6436,"x":659,"y":506},{"type":"mousemove","time":6676,"x":714,"y":523},{"type":"mousemove","time":6887,"x":747,"y":560},{"type":"mousemove","time":7096,"x":774,"y":578},{"type":"mousemove","time":7302,"x":783,"y":578},{"type":"mousedown","time":7569,"x":783,"y":578},{"type":"mousemove","time":7579,"x":782,"y":578},{"type":"mousemove","time":7862,"x":569,"y":499},{"type":"mousemove","time":8140,"x":568,"y":499},{"type":"mousemove","time":8410,"x":335,"y":377},{"type":"mousemove","time":8619,"x":510,"y":358},{"type":"mousemove","time":8885,"x":633,"y":439},{"type":"mousemove","time":9149,"x":691,"y":461},{"type":"mousemove","time":9436,"x":723,"y":512},{"type":"mousemove","time":9704,"x":760,"y":528},{"type":"mouseup","time":9898,"x":760,"y":528},{"time":9899,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9907,"x":735,"y":512},{"type":"mousemove","time":10114,"x":130,"y":210},{"type":"mousemove","time":10344,"x":114,"y":153},{"type":"mousemove","time":10586,"x":109,"y":154},{"type":"mousewheel","time":10638,"x":109,"y":154,"deltaY":1},{"type":"mousewheel","time":10700,"x":109,"y":154,"deltaY":12},{"type":"mousewheel","time":10763,"x":109,"y":154,"deltaY":122},{"type":"mousewheel","time":10822,"x":109,"y":154,"deltaY":20},{"type":"mousewheel","time":10878,"x":109,"y":154,"deltaY":36},{"type":"mousewheel","time":10932,"x":109,"y":154,"deltaY":351},{"type":"mousewheel","time":10986,"x":109,"y":154,"deltaY":107},{"type":"mousewheel","time":11043,"x":109,"y":154,"deltaY":106},{"type":"mousewheel","time":11095,"x":109,"y":154,"deltaY":57},{"type":"mousewheel","time":11151,"x":109,"y":154,"deltaY":55},{"type":"mousewheel","time":11224,"x":109,"y":154,"deltaY":-1},{"type":"mousewheel","time":11281,"x":109,"y":154,"deltaY":-19},{"type":"mousewheel","time":11337,"x":109,"y":154,"deltaY":-16},{"type":"mousewheel","time":11395,"x":109,"y":154,"deltaY":-3},{"type":"mousewheel","time":11454,"x":109,"y":154,"deltaY":0},{"type":"mousemove","time":11589,"x":109,"y":154},{"type":"mousemove","time":11801,"x":105,"y":253},{"type":"mousewheel","time":12015,"x":105,"y":253,"deltaY":1},{"type":"mousewheel","time":12074,"x":105,"y":253,"deltaY":25},{"type":"mousewheel","time":12130,"x":105,"y":253,"deltaY":51},{"type":"mousewheel","time":12185,"x":105,"y":253,"deltaY":15},{"type":"mousewheel","time":12241,"x":105,"y":253,"deltaY":14},{"type":"mousewheel","time":12298,"x":105,"y":253,"deltaY":133},{"type":"mousewheel","time":12371,"x":105,"y":253,"deltaY":39},{"type":"mousewheel","time":12433,"x":105,"y":253,"deltaY":47},{"type":"mousewheel","time":12493,"x":105,"y":253,"deltaY":20},{"type":"mousewheel","time":12547,"x":105,"y":253,"deltaY":6},{"type":"mousewheel","time":12614,"x":105,"y":253,"deltaY":-1},{"type":"mousewheel","time":12670,"x":105,"y":253,"deltaY":-75},{"type":"mousewheel","time":12728,"x":105,"y":253,"deltaY":-22},{"type":"mousewheel","time":12783,"x":105,"y":253,"deltaY":-2},{"type":"mousewheel","time":12836,"x":105,"y":253,"deltaY":-7},{"type":"mousewheel","time":12895,"x":105,"y":253,"deltaY":-65},{"type":"mousewheel","time":12952,"x":105,"y":253,"deltaY":-7},{"type":"mousemove","time":12984,"x":123,"y":252},{"type":"mousemove","time":13214,"x":207,"y":240},{"type":"mousemove","time":13520,"x":216,"y":242},{"type":"mousemove","time":13727,"x":169,"y":373},{"type":"mousemove","time":13937,"x":140,"y":401},{"type":"mousemove","time":14144,"x":150,"y":398},{"type":"mousemove","time":14351,"x":160,"y":395},{"type":"mousemove","time":14577,"x":160,"y":400},{"type":"mousemove","time":14773,"x":157,"y":400},{"type":"mousemove","time":14980,"x":144,"y":411},{"type":"mousedown","time":15199,"x":144,"y":411},{"type":"mousemove","time":15210,"x":139,"y":411},{"type":"mousemove","time":15473,"x":64,"y":405},{"type":"mouseup","time":15630,"x":64,"y":405},{"time":15631,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":15732,"x":92,"y":401},{"type":"mousemove","time":15938,"x":71,"y":408},{"type":"mousemove","time":16176,"x":60,"y":411},{"type":"mousemove","time":16399,"x":58,"y":410},{"type":"mousemove","time":16634,"x":65,"y":412},{"type":"mousedown","time":17021,"x":65,"y":412},{"type":"mousemove","time":17031,"x":70,"y":410},{"type":"mousemove","time":17286,"x":136,"y":411},{"type":"mouseup","time":17439,"x":136,"y":411},{"time":17440,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17505,"x":105,"y":462},{"type":"mousemove","time":17723,"x":102,"y":469},{"type":"mousedown","time":17948,"x":102,"y":469},{"type":"mousemove","time":17959,"x":102,"y":471},{"type":"mousemove","time":18172,"x":99,"y":511},{"type":"mouseup","time":18254,"x":99,"y":511},{"time":18255,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18392,"x":482,"y":268},{"type":"mousemove","time":18598,"x":513,"y":231},{"type":"mousemove","time":18820,"x":429,"y":269},{"type":"mousewheel","time":19025,"x":429,"y":269,"deltaY":1},{"type":"mousewheel","time":19062,"x":429,"y":269,"deltaY":4},{"type":"mousewheel","time":19099,"x":429,"y":269,"deltaY":11},{"type":"mousewheel","time":19140,"x":429,"y":269,"deltaY":12},{"type":"mousewheel","time":19181,"x":429,"y":269,"deltaY":4},{"type":"mousewheel","time":19217,"x":429,"y":269,"deltaY":3},{"type":"mousewheel","time":19257,"x":429,"y":269,"deltaY":2},{"type":"mousemove","time":19511,"x":431,"y":256},{"type":"mousemove","time":19734,"x":489,"y":158},{"type":"mousemove","time":19934,"x":492,"y":177},{"type":"mousemove","time":20151,"x":492,"y":179},{"type":"mousewheel","time":20206,"x":492,"y":179,"deltaY":1},{"type":"mousewheel","time":20241,"x":492,"y":179,"deltaY":4},{"type":"mousewheel","time":20281,"x":492,"y":179,"deltaY":12},{"type":"mousewheel","time":20320,"x":492,"y":179,"deltaY":16},{"type":"mousewheel","time":20366,"x":492,"y":179,"deltaY":13},{"type":"mousewheel","time":20419,"x":492,"y":179,"deltaY":4},{"type":"mousewheel","time":20643,"x":492,"y":179,"deltaY":-1},{"type":"mousewheel","time":20683,"x":492,"y":179,"deltaY":-1},{"type":"mousewheel","time":20727,"x":492,"y":179,"deltaY":-9},{"type":"mousewheel","time":20769,"x":492,"y":179,"deltaY":-11},{"type":"mousewheel","time":20808,"x":492,"y":179,"deltaY":-20},{"type":"mousewheel","time":20847,"x":492,"y":179,"deltaY":-12},{"type":"mousewheel","time":20885,"x":492,"y":179,"deltaY":-8},{"type":"mousewheel","time":20924,"x":492,"y":179,"deltaY":-9},{"type":"mousemove","time":21118,"x":496,"y":179},{"type":"mousemove","time":21328,"x":608,"y":195},{"type":"mousemove","time":21529,"x":624,"y":199},{"type":"mousemove","time":21743,"x":652,"y":219},{"type":"mousemove","time":21946,"x":677,"y":231},{"type":"mousemove","time":22152,"x":675,"y":274},{"type":"mousemove","time":22405,"x":668,"y":268},{"type":"mousewheel","time":22822,"x":668,"y":268,"deltaY":-1},{"type":"mousewheel","time":22908,"x":668,"y":268,"deltaY":-4},{"type":"mousewheel","time":22988,"x":668,"y":268,"deltaY":-4},{"type":"mousewheel","time":23069,"x":668,"y":268,"deltaY":-2},{"type":"mousewheel","time":23281,"x":668,"y":268,"deltaY":-1},{"type":"mousewheel","time":23366,"x":668,"y":268,"deltaY":-4},{"type":"mousewheel","time":23454,"x":668,"y":268,"deltaY":-1},{"type":"mousemove","time":23883,"x":665,"y":268},{"type":"mousemove","time":24087,"x":608,"y":370},{"type":"mousemove","time":24313,"x":606,"y":378},{"type":"mousemove","time":24363,"x":607,"y":379},{"type":"mousemove","time":24579,"x":622,"y":397},{"type":"mousemove","time":24797,"x":632,"y":397},{"type":"mousedown","time":24956,"x":638,"y":399},{"type":"mousemove","time":25041,"x":638,"y":399},{"type":"mouseup","time":25076,"x":638,"y":399},{"time":25077,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":25324,"x":638,"y":396},{"type":"mousemove","time":25560,"x":612,"y":345},{"type":"mousemove","time":25818,"x":605,"y":340},{"type":"mousemove","time":26028,"x":437,"y":378},{"type":"mousemove","time":26262,"x":424,"y":383},{"type":"mousemove","time":26462,"x":486,"y":477},{"type":"mousemove","time":26684,"x":666,"y":480},{"type":"mousemove","time":26897,"x":787,"y":511},{"type":"mousemove","time":27100,"x":764,"y":527},{"type":"mousemove","time":27317,"x":761,"y":528},{"type":"mousemove","time":27874,"x":761,"y":528},{"type":"mousemove","time":28083,"x":760,"y":507}],"scrollY":60,"scrollX":0,"timestamp":1750344079733},{"name":"Action 2","ops":[{"type":"mousedown","time":495,"x":474,"y":428},{"type":"mouseup","time":612,"x":474,"y":428},{"time":613,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":753,"x":475,"y":428},{"type":"mousemove","time":956,"x":507,"y":427},{"type":"mousemove","time":1165,"x":515,"y":426},{"type":"mousedown","time":1183,"x":515,"y":426},{"type":"mouseup","time":1313,"x":515,"y":426},{"time":1314,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1553,"x":517,"y":426},{"type":"mousedown","time":1681,"x":523,"y":426},{"type":"mousemove","time":1764,"x":523,"y":426},{"type":"mouseup","time":1814,"x":523,"y":426},{"time":1815,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2069,"x":522,"y":426},{"type":"mousemove","time":2269,"x":454,"y":434},{"type":"mousemove","time":2470,"x":365,"y":444},{"type":"mousedown","time":2664,"x":366,"y":427},{"type":"mousemove","time":2698,"x":366,"y":427},{"type":"mouseup","time":2782,"x":366,"y":427},{"time":2783,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2903,"x":366,"y":427},{"type":"mousemove","time":3113,"x":392,"y":427},{"type":"mousedown","time":3202,"x":392,"y":427},{"type":"mouseup","time":3330,"x":392,"y":427},{"time":3331,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3736,"x":392,"y":427},{"type":"mousedown","time":3791,"x":392,"y":427},{"type":"mouseup","time":3913,"x":392,"y":427},{"time":3914,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3955,"x":392,"y":427},{"type":"mousedown","time":4281,"x":392,"y":427},{"type":"mouseup","time":4414,"x":392,"y":427},{"time":4415,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4986,"x":392,"y":427},{"type":"mousemove","time":5186,"x":594,"y":401},{"type":"mousemove","time":5419,"x":714,"y":401},{"type":"mousedown","time":5464,"x":714,"y":401},{"type":"mouseup","time":5583,"x":714,"y":401},{"time":5584,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5651,"x":714,"y":401},{"type":"mousemove","time":7469,"x":713,"y":401},{"type":"mousemove","time":7670,"x":379,"y":377},{"type":"mousemove","time":7890,"x":351,"y":406},{"type":"mousemove","time":8112,"x":362,"y":425},{"type":"mousedown","time":8183,"x":363,"y":426},{"type":"mousemove","time":8316,"x":363,"y":426},{"type":"mouseup","time":8481,"x":363,"y":426},{"time":8482,"delay":400,"type":"screenshot-auto"}],"scrollY":454,"scrollX":0,"timestamp":1750344113153},{"name":"Action 3","ops":[{"type":"mousemove","time":134,"x":761,"y":465},{"type":"mousemove","time":334,"x":774,"y":530},{"type":"mousemove","time":542,"x":785,"y":579},{"type":"mousemove","time":759,"x":787,"y":583},{"type":"mousedown","time":1059,"x":787,"y":583},{"type":"mousemove","time":1068,"x":787,"y":583},{"type":"mousemove","time":1278,"x":726,"y":570},{"type":"mousemove","time":1483,"x":693,"y":571},{"type":"mousemove","time":1691,"x":671,"y":572},{"type":"mousemove","time":1901,"x":669,"y":572},{"type":"mousemove","time":2101,"x":687,"y":496},{"type":"mousemove","time":2309,"x":707,"y":446},{"type":"mousemove","time":2500,"x":709,"y":444},{"type":"mousemove","time":2701,"x":727,"y":400},{"type":"mousemove","time":2910,"x":731,"y":381},{"type":"mousemove","time":3117,"x":732,"y":359},{"type":"mousemove","time":3317,"x":723,"y":329},{"type":"mousemove","time":3527,"x":723,"y":329},{"type":"mousemove","time":3584,"x":723,"y":329},{"type":"mousemove","time":3783,"x":721,"y":397},{"type":"mousemove","time":3990,"x":699,"y":483},{"type":"mousemove","time":4192,"x":697,"y":490},{"type":"mousemove","time":4300,"x":697,"y":490},{"type":"mousemove","time":4501,"x":513,"y":522},{"type":"mousemove","time":4711,"x":458,"y":546},{"type":"mousemove","time":4983,"x":454,"y":548},{"type":"mousemove","time":5183,"x":334,"y":531},{"type":"mousemove","time":5395,"x":305,"y":532},{"type":"mousemove","time":5534,"x":305,"y":532},{"type":"mousemove","time":5744,"x":204,"y":527},{"type":"mousemove","time":5951,"x":156,"y":525},{"type":"mousemove","time":6152,"x":116,"y":527},{"type":"mousemove","time":6364,"x":79,"y":530},{"type":"mousemove","time":6567,"x":57,"y":536},{"type":"mousemove","time":6777,"x":44,"y":539},{"type":"mousemove","time":7000,"x":42,"y":540},{"type":"mousemove","time":7214,"x":34,"y":541},{"type":"mousemove","time":7433,"x":25,"y":542},{"type":"mousemove","time":7635,"x":30,"y":541},{"type":"mousemove","time":7835,"x":47,"y":541},{"type":"mousemove","time":8035,"x":63,"y":539},{"type":"mousemove","time":8235,"x":83,"y":535},{"type":"mousemove","time":8444,"x":137,"y":533},{"type":"mousemove","time":8651,"x":474,"y":510},{"type":"mousemove","time":8851,"x":490,"y":506},{"type":"mousemove","time":9051,"x":690,"y":501},{"type":"mousemove","time":9262,"x":744,"y":521},{"type":"mousemove","time":9467,"x":740,"y":539},{"type":"mousemove","time":9667,"x":756,"y":552},{"type":"mousemove","time":9869,"x":760,"y":559},{"type":"mousemove","time":10079,"x":761,"y":561},{"type":"mousemove","time":10377,"x":761,"y":562},{"type":"mouseup","time":10544,"x":761,"y":562},{"time":10545,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10554,"x":725,"y":520},{"type":"mousemove","time":10761,"x":267,"y":323},{"type":"mousemove","time":11034,"x":265,"y":320},{"type":"mousemove","time":11234,"x":240,"y":296},{"type":"mousemove","time":11444,"x":176,"y":214},{"type":"mousemove","time":11652,"x":152,"y":175},{"type":"mousemove","time":11861,"x":141,"y":155},{"type":"mousemove","time":12077,"x":141,"y":155},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":12784,"target":"select"},{"time":12785,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":12834,"x":139,"y":157},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"2","time":13866,"target":"select"},{"time":13867,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":13917,"x":142,"y":155},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"3","time":14885,"target":"select"},{"time":14886,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":14950,"x":144,"y":151},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"4","time":15934,"target":"select"},{"time":15935,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":16017,"x":142,"y":159},{"type":"mousemove","time":16227,"x":142,"y":158},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":17483,"target":"select"},{"time":17484,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":17494,"x":240,"y":94},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"1","time":18863,"target":"select"},{"time":18864,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":18901,"x":337,"y":163},{"type":"mousemove","time":19109,"x":338,"y":153},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(2)>select.test-inputs-select-select","value":"0","time":20223,"target":"select"},{"time":20224,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":20267,"x":482,"y":166},{"type":"mousemove","time":20467,"x":506,"y":163},{"type":"mousemove","time":20678,"x":509,"y":147},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"1","time":22423,"target":"select"},{"time":22424,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":22467,"x":489,"y":179},{"type":"mousemove","time":22668,"x":365,"y":296},{"type":"mousemove","time":22876,"x":312,"y":317},{"type":"mousemove","time":23093,"x":277,"y":337},{"type":"mousemove","time":23334,"x":278,"y":337},{"type":"mousemove","time":23535,"x":279,"y":332},{"type":"mousemove","time":23744,"x":103,"y":408},{"type":"mousemove","time":23951,"x":124,"y":437},{"type":"mousemove","time":24160,"x":122,"y":389},{"type":"mousemove","time":24376,"x":103,"y":380},{"type":"mousemove","time":24584,"x":107,"y":390},{"type":"mousemove","time":24784,"x":114,"y":443},{"type":"mousemove","time":24984,"x":114,"y":449},{"type":"mousemove","time":25185,"x":200,"y":380},{"type":"mousemove","time":25395,"x":204,"y":326},{"type":"mousemove","time":25600,"x":462,"y":195},{"type":"mousemove","time":25801,"x":495,"y":167},{"type":"mousemove","time":26011,"x":524,"y":154},{"type":"mousemove","time":26227,"x":515,"y":146},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(3)>select.test-inputs-select-select","value":"0","time":27968,"target":"select"},{"time":27969,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":27991,"x":579,"y":137},{"type":"mousedown","time":28095,"x":648,"y":139},{"type":"mouseup","time":28117,"x":648,"y":139},{"time":28118,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":28194,"x":648,"y":138},{"type":"mousemove","time":28401,"x":648,"y":149},{"type":"mousemove","time":28611,"x":648,"y":149},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"1","time":29318,"target":"select"},{"time":29319,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":29351,"x":585,"y":218},{"type":"mousemove","time":29561,"x":474,"y":314},{"type":"mousemove","time":29767,"x":417,"y":340},{"type":"mousemove","time":29988,"x":319,"y":414},{"type":"mousemove","time":30195,"x":273,"y":371},{"type":"mousemove","time":30396,"x":282,"y":310},{"type":"mousemove","time":30601,"x":321,"y":316},{"type":"mousemove","time":30811,"x":349,"y":316},{"type":"mousemove","time":31017,"x":271,"y":299},{"type":"mousemove","time":31218,"x":705,"y":187},{"type":"mousemove","time":31427,"x":709,"y":141},{"type":"mousemove","time":31643,"x":684,"y":157},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(4)>select.test-inputs-select-select","value":"0","time":32651,"target":"select"},{"time":32652,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":32686,"x":431,"y":185},{"type":"mousemove","time":32895,"x":181,"y":189},{"type":"mousemove","time":33101,"x":147,"y":183},{"type":"mousemove","time":33311,"x":118,"y":172},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(5)>select.test-inputs-select-select","value":"1","time":34202,"target":"select"},{"time":34203,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":34251,"x":239,"y":272},{"type":"mousemove","time":34460,"x":207,"y":256},{"type":"mousemove","time":34684,"x":239,"y":272},{"type":"mousemove","time":34894,"x":220,"y":186},{"type":"mousemove","time":35095,"x":220,"y":181},{"type":"mousemove","time":35311,"x":221,"y":176},{"type":"mousedown","time":35760,"x":221,"y":176},{"type":"mousemove","time":35770,"x":221,"y":176},{"type":"mousemove","time":35978,"x":223,"y":176},{"type":"mousemove","time":36193,"x":223,"y":176},{"type":"mousemove","time":36234,"x":224,"y":176},{"type":"mousemove","time":36446,"x":226,"y":177},{"type":"mousemove","time":36695,"x":228,"y":177},{"type":"mousemove","time":36917,"x":229,"y":177},{"type":"mousemove","time":37184,"x":229,"y":177},{"type":"mousemove","time":37384,"x":231,"y":177},{"type":"mousemove","time":37593,"x":234,"y":179},{"type":"mousemove","time":37828,"x":238,"y":180},{"type":"mousemove","time":38111,"x":239,"y":180},{"type":"mousemove","time":38317,"x":229,"y":181},{"type":"mousemove","time":38526,"x":229,"y":181},{"type":"mousemove","time":38761,"x":221,"y":181},{"type":"mousemove","time":38968,"x":221,"y":181},{"type":"mousemove","time":39168,"x":227,"y":181},{"type":"mousemove","time":39377,"x":228,"y":182},{"type":"mouseup","time":40163,"x":228,"y":182},{"time":40164,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":40175,"x":259,"y":181},{"type":"mousemove","time":40383,"x":405,"y":172},{"type":"mousemove","time":40599,"x":413,"y":171},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(6)>select.test-inputs-select-select","value":"1","time":41384,"target":"select"},{"time":41385,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":41435,"x":449,"y":183},{"type":"mousemove","time":41644,"x":501,"y":176},{"type":"mousemove","time":41862,"x":529,"y":171},{"type":"mousemove","time":42067,"x":521,"y":175},{"type":"mousedown","time":42478,"x":521,"y":175},{"type":"mousemove","time":42489,"x":521,"y":175},{"type":"mousemove","time":42694,"x":531,"y":177},{"type":"mousemove","time":42962,"x":533,"y":177},{"type":"mousemove","time":43165,"x":554,"y":178},{"type":"mousemove","time":43365,"x":571,"y":179},{"type":"mousemove","time":43568,"x":560,"y":181},{"type":"mousemove","time":43777,"x":547,"y":182},{"type":"mousemove","time":44061,"x":536,"y":182},{"type":"mousemove","time":44333,"x":535,"y":182},{"type":"mousemove","time":44544,"x":531,"y":182},{"type":"mousemove","time":44650,"x":531,"y":182},{"type":"mousemove","time":44861,"x":527,"y":182},{"type":"mousemove","time":45084,"x":526,"y":182},{"type":"mousemove","time":45284,"x":518,"y":182},{"type":"mousemove","time":45495,"x":518,"y":182},{"type":"mousemove","time":45710,"x":521,"y":183},{"type":"mousemove","time":46618,"x":520,"y":183},{"type":"mousemove","time":46828,"x":501,"y":182},{"type":"mousemove","time":47034,"x":501,"y":182},{"type":"mousemove","time":47236,"x":520,"y":182},{"type":"mousemove","time":47501,"x":520,"y":182},{"type":"mousemove","time":47711,"x":523,"y":183},{"type":"mousemove","time":47934,"x":526,"y":183},{"type":"mouseup","time":48310,"x":526,"y":183},{"time":48311,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":48322,"x":509,"y":185},{"type":"mousemove","time":48528,"x":347,"y":201},{"type":"mousemove","time":48735,"x":265,"y":183},{"type":"mousemove","time":48945,"x":214,"y":175},{"type":"mousemove","time":49151,"x":218,"y":175},{"type":"mousemove","time":49361,"x":230,"y":177},{"type":"mousedown","time":49594,"x":230,"y":177},{"type":"mousemove","time":49605,"x":230,"y":177},{"type":"mousemove","time":49811,"x":212,"y":178},{"type":"mousemove","time":50011,"x":202,"y":177},{"type":"mousemove","time":50212,"x":195,"y":177},{"type":"mousemove","time":50468,"x":195,"y":177},{"type":"mousemove","time":50678,"x":190,"y":177},{"type":"mousemove","time":50894,"x":187,"y":178},{"type":"mousemove","time":51135,"x":187,"y":178},{"type":"mousemove","time":51345,"x":207,"y":179},{"type":"mousemove","time":51561,"x":216,"y":179},{"type":"mousemove","time":51762,"x":218,"y":180},{"type":"mousemove","time":51884,"x":219,"y":180},{"type":"mousemove","time":52095,"x":220,"y":180},{"type":"mousemove","time":52300,"x":221,"y":181},{"type":"mousemove","time":52501,"x":224,"y":182},{"type":"mousemove","time":52835,"x":224,"y":182},{"type":"mousemove","time":53046,"x":226,"y":182},{"type":"mouseup","time":53628,"x":226,"y":182},{"time":53629,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":53640,"x":213,"y":187},{"type":"mousemove","time":53847,"x":176,"y":201},{"type":"mousemove","time":54062,"x":174,"y":201},{"type":"mousemove","time":54279,"x":174,"y":201},{"type":"mousedown","time":54411,"x":174,"y":201},{"type":"mousemove","time":54423,"x":174,"y":201},{"type":"mousemove","time":54629,"x":181,"y":201},{"type":"mousemove","time":54884,"x":186,"y":201},{"type":"mousemove","time":55095,"x":192,"y":202},{"type":"mousemove","time":55301,"x":183,"y":202},{"type":"mousemove","time":55505,"x":172,"y":203},{"type":"mousemove","time":55712,"x":163,"y":204},{"type":"mousemove","time":55942,"x":146,"y":204},{"type":"mousemove","time":56161,"x":129,"y":204},{"type":"mousemove","time":56361,"x":122,"y":204},{"type":"mousemove","time":56578,"x":122,"y":204},{"type":"mousemove","time":56617,"x":122,"y":204},{"type":"mousemove","time":56817,"x":126,"y":206},{"type":"mousemove","time":57034,"x":128,"y":207},{"type":"mousemove","time":57244,"x":129,"y":207},{"type":"mousemove","time":57460,"x":129,"y":208},{"type":"mousemove","time":57716,"x":130,"y":208},{"type":"mousemove","time":57932,"x":134,"y":208},{"type":"mousemove","time":58214,"x":136,"y":208},{"type":"mouseup","time":58729,"x":136,"y":208},{"time":58730,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":58742,"x":174,"y":205},{"type":"mousemove","time":58946,"x":341,"y":201},{"type":"mousemove","time":59150,"x":398,"y":194},{"type":"mousemove","time":59361,"x":399,"y":194},{"type":"mousedown","time":59628,"x":399,"y":194},{"type":"mousemove","time":59639,"x":399,"y":194},{"type":"mousemove","time":59845,"x":375,"y":195},{"type":"mousemove","time":60051,"x":370,"y":196},{"type":"mousemove","time":60268,"x":352,"y":198},{"type":"mousemove","time":60480,"x":339,"y":199},{"type":"mousemove","time":60683,"x":325,"y":199},{"type":"mousemove","time":60891,"x":378,"y":205},{"type":"mousemove","time":61116,"x":409,"y":205},{"type":"mousemove","time":61317,"x":423,"y":205},{"type":"mousemove","time":61529,"x":421,"y":203},{"type":"mousemove","time":61735,"x":393,"y":203},{"type":"mousemove","time":61943,"x":380,"y":203},{"type":"mousemove","time":62144,"x":369,"y":203},{"type":"mousemove","time":62351,"x":365,"y":203},{"type":"mousemove","time":62566,"x":360,"y":202},{"type":"mouseup","time":62861,"x":360,"y":202},{"time":62862,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":62874,"x":340,"y":199},{"type":"mousemove","time":63079,"x":208,"y":179},{"type":"mousemove","time":63283,"x":167,"y":167},{"type":"mousemove","time":63483,"x":150,"y":156},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"1","time":64941,"target":"select"},{"time":64942,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":65015,"x":190,"y":214},{"type":"mousemove","time":65216,"x":256,"y":224},{"type":"mousemove","time":65416,"x":197,"y":203},{"type":"mousemove","time":65616,"x":159,"y":196},{"type":"mousemove","time":65816,"x":139,"y":194},{"type":"mousemove","time":66027,"x":138,"y":194},{"type":"mousedown","time":66228,"x":138,"y":194},{"type":"mousemove","time":66240,"x":139,"y":194},{"type":"mousemove","time":66455,"x":159,"y":195},{"type":"mousemove","time":66665,"x":164,"y":195},{"type":"mousemove","time":66867,"x":167,"y":196},{"type":"mousemove","time":67078,"x":135,"y":195},{"type":"mousemove","time":67283,"x":123,"y":196},{"type":"mousemove","time":67516,"x":123,"y":196},{"type":"mousemove","time":67716,"x":129,"y":197},{"type":"mousemove","time":67928,"x":134,"y":199},{"type":"mousemove","time":68133,"x":137,"y":199},{"type":"mousemove","time":68344,"x":140,"y":200},{"type":"mousemove","time":68559,"x":142,"y":200},{"type":"mouseup","time":68777,"x":142,"y":200},{"time":68778,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":68790,"x":163,"y":200},{"type":"mousemove","time":68993,"x":306,"y":208},{"type":"mousemove","time":69199,"x":323,"y":207},{"type":"mousemove","time":69399,"x":347,"y":199},{"type":"mousemove","time":69600,"x":351,"y":199},{"type":"mousemove","time":69811,"x":352,"y":199},{"type":"mousedown","time":70012,"x":352,"y":199},{"type":"mousemove","time":70025,"x":353,"y":199},{"type":"mousemove","time":70229,"x":367,"y":198},{"type":"mousemove","time":70441,"x":381,"y":197},{"type":"mousemove","time":70642,"x":405,"y":196},{"type":"mousemove","time":70845,"x":412,"y":196},{"type":"mousemove","time":71054,"x":401,"y":198},{"type":"mousemove","time":71265,"x":375,"y":199},{"type":"mousemove","time":71466,"x":344,"y":198},{"type":"mousemove","time":71677,"x":323,"y":197},{"type":"mousemove","time":71882,"x":316,"y":195},{"type":"mousemove","time":72083,"x":350,"y":202},{"type":"mousemove","time":72299,"x":359,"y":202},{"type":"mousemove","time":72499,"x":363,"y":202},{"type":"mousemove","time":72699,"x":370,"y":202},{"type":"mousemove","time":72911,"x":370,"y":203},{"type":"mouseup","time":73244,"x":370,"y":203},{"time":73245,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":73256,"x":360,"y":203},{"type":"mousemove","time":73462,"x":220,"y":167},{"type":"mousemove","time":73666,"x":163,"y":150},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select>select.test-inputs-select-select","value":"0","time":76316,"target":"select"},{"time":76317,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":76366,"x":145,"y":178},{"type":"mousemove","time":76566,"x":147,"y":192},{"type":"mousemove","time":76767,"x":164,"y":217},{"type":"mousemove","time":76979,"x":189,"y":220},{"type":"mousemove","time":77195,"x":190,"y":220},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"1","time":78160,"target":"select"},{"time":78161,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":78249,"x":226,"y":219},{"type":"mousemove","time":78449,"x":226,"y":219},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"2","time":79664,"target":"select"},{"time":79665,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":79700,"x":237,"y":226},{"type":"mousemove","time":79913,"x":239,"y":221},{"type":"mousemove","time":80128,"x":239,"y":221},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"3","time":80924,"target":"select"},{"time":80925,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":80958,"x":243,"y":220},{"type":"mousemove","time":81162,"x":242,"y":224},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"5","time":82050,"target":"select"},{"time":82051,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":82100,"x":252,"y":219},{"type":"mousemove","time":82310,"x":252,"y":218},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"7","time":83249,"target":"select"},{"time":83250,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":83316,"x":261,"y":225},{"type":"mousemove","time":83527,"x":261,"y":222},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"9","time":84467,"target":"select"},{"time":84468,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":84532,"x":266,"y":212},{"type":"mousemove","time":84732,"x":266,"y":215},{"type":"mousemove","time":84944,"x":266,"y":217},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"11","time":86017,"target":"select"},{"time":86018,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":86099,"x":261,"y":226},{"type":"mousemove","time":86299,"x":252,"y":227},{"type":"mousemove","time":86510,"x":252,"y":215},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"8","time":87416,"target":"select"},{"time":87417,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":87453,"x":345,"y":187},{"type":"mousemove","time":87660,"x":485,"y":219},{"type":"mousemove","time":87878,"x":483,"y":221},{"type":"mousemove","time":88165,"x":482,"y":220},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"1","time":90099,"target":"select"},{"time":90100,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":90111,"x":480,"y":246},{"type":"mousemove","time":90899,"x":481,"y":245},{"type":"mousemove","time":91099,"x":485,"y":236},{"type":"mousemove","time":91310,"x":492,"y":222},{"type":"mousemove","time":91515,"x":310,"y":214},{"type":"mousemove","time":91716,"x":244,"y":223},{"type":"mousemove","time":91927,"x":244,"y":223},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(10)>select.test-inputs-select-select","value":"0","time":93332,"target":"select"},{"time":93333,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":93345,"x":347,"y":139},{"type":"mousemove","time":93546,"x":470,"y":190},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"2","time":95033,"target":"select"},{"time":95034,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":95098,"x":500,"y":221},{"type":"mousemove","time":95299,"x":500,"y":221},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"3","time":96284,"target":"select"},{"time":96285,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":96323,"x":509,"y":224},{"type":"mousemove","time":96532,"x":513,"y":218},{"type":"mousemove","time":96747,"x":513,"y":217},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"4","time":97704,"target":"select"},{"time":97705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":97741,"x":496,"y":250},{"type":"mousemove","time":97944,"x":505,"y":226},{"type":"mousemove","time":98145,"x":505,"y":219},{"type":"mousemove","time":98360,"x":505,"y":218},{"type":"mousemove","time":99316,"x":620,"y":218},{"type":"mousemove","time":99526,"x":620,"y":218},{"type":"mousedown","time":99761,"x":620,"y":218},{"type":"mousemove","time":99773,"x":621,"y":218},{"type":"mousemove","time":99978,"x":623,"y":218},{"type":"mousemove","time":100186,"x":625,"y":218},{"type":"mousemove","time":100402,"x":628,"y":218},{"type":"mousemove","time":100610,"x":630,"y":218},{"type":"mousemove","time":100998,"x":630,"y":218},{"type":"mousemove","time":101203,"x":622,"y":218},{"type":"mousemove","time":101415,"x":616,"y":219},{"type":"mousemove","time":101625,"x":612,"y":220},{"type":"mousemove","time":101827,"x":611,"y":220},{"type":"mousemove","time":101965,"x":611,"y":220},{"type":"mousemove","time":102177,"x":611,"y":220},{"type":"mouseup","time":102280,"x":611,"y":220},{"time":102281,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":102293,"x":514,"y":218},{"type":"mousemove","time":102498,"x":272,"y":236},{"type":"mousemove","time":102708,"x":208,"y":251},{"type":"mousedown","time":102852,"x":196,"y":253},{"type":"mousemove","time":102929,"x":196,"y":253},{"type":"mouseup","time":102994,"x":196,"y":253},{"time":102995,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":103199,"x":196,"y":253},{"type":"mousemove","time":103410,"x":198,"y":245},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(13)>select.test-inputs-select-select","value":"1","time":104406,"target":"select"},{"time":104407,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":104437,"x":198,"y":262},{"type":"mousemove","time":104646,"x":208,"y":251},{"type":"mousemove","time":104851,"x":205,"y":248},{"type":"mousemove","time":105062,"x":201,"y":245},{"type":"mousemove","time":105182,"x":201,"y":246},{"type":"mousemove","time":105383,"x":605,"y":237},{"type":"mousemove","time":105583,"x":629,"y":228},{"type":"mousemove","time":105794,"x":616,"y":213},{"type":"mousemove","time":105999,"x":568,"y":214},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"2","time":107903,"target":"select"},{"time":107904,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":107946,"x":450,"y":215},{"type":"mousemove","time":108147,"x":375,"y":259},{"type":"mousemove","time":108416,"x":372,"y":262},{"type":"mousemove","time":108616,"x":214,"y":254},{"type":"mousemove","time":108827,"x":194,"y":251},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(13)>select.test-inputs-select-select","value":"2","time":110085,"target":"select"},{"time":110086,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":110149,"x":188,"y":245},{"type":"mousemove","time":110361,"x":188,"y":245},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(13)>select.test-inputs-select-select","value":"3","time":111294,"target":"select"},{"time":111295,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":111332,"x":204,"y":243},{"type":"mousemove","time":111532,"x":295,"y":236},{"type":"mousemove","time":111732,"x":458,"y":246},{"type":"mousemove","time":111944,"x":473,"y":246},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(14)>select.test-inputs-select-select","value":"1","time":113021,"target":"select"},{"time":113022,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":113070,"x":459,"y":245},{"type":"mousemove","time":113281,"x":459,"y":246},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(14)>select.test-inputs-select-select","value":"2","time":114068,"target":"select"},{"time":114069,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":114121,"x":451,"y":238},{"type":"mousemove","time":114327,"x":449,"y":240},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(14)>select.test-inputs-select-select","value":"3","time":115284,"target":"select"},{"time":115285,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":115310,"x":450,"y":270},{"type":"mousemove","time":115511,"x":506,"y":270},{"type":"mousemove","time":115716,"x":675,"y":250},{"type":"mousemove","time":115916,"x":726,"y":229},{"type":"mousemove","time":116117,"x":730,"y":212},{"type":"mousemove","time":116327,"x":730,"y":212},{"type":"mousedown","time":116395,"x":730,"y":212},{"type":"mouseup","time":116528,"x":730,"y":212},{"time":116529,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":116565,"x":730,"y":212},{"type":"mousemove","time":117232,"x":730,"y":212},{"type":"mousemove","time":117433,"x":242,"y":191},{"type":"mousemove","time":117646,"x":294,"y":206},{"type":"mousemove","time":117848,"x":450,"y":225},{"type":"mousemove","time":118057,"x":493,"y":222},{"type":"mousemove","time":118261,"x":493,"y":222},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(11)>select.test-inputs-select-select","value":"0","time":119098,"target":"select"},{"time":119099,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":119113,"x":451,"y":210},{"type":"mousemove","time":119314,"x":236,"y":310},{"type":"mousemove","time":119632,"x":214,"y":311},{"type":"mousemove","time":119834,"x":214,"y":277},{"type":"mousemove","time":120047,"x":211,"y":255},{"type":"mousemove","time":120251,"x":201,"y":245},{"type":"mousemove","time":120460,"x":200,"y":244},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(13)>select.test-inputs-select-select","value":"2","time":121451,"target":"select"},{"time":121452,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":121480,"x":185,"y":241},{"type":"mousemove","time":121681,"x":182,"y":249},{"type":"mousemove","time":121896,"x":181,"y":249},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(13)>select.test-inputs-select-select","value":"1","time":122786,"target":"select"},{"time":122787,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":122800,"x":303,"y":221},{"type":"mousemove","time":123006,"x":447,"y":226},{"type":"mousemove","time":123211,"x":443,"y":239},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(14)>select.test-inputs-select-select","value":"2","time":124293,"target":"select"},{"time":124294,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":124335,"x":443,"y":234},{"type":"mousemove","time":124545,"x":443,"y":234},{"type":"valuechange","selector":"#main_text_overflow_and_tooltip>div.test-chart-block-left>div.test-inputs.test-buttons.test-inputs-style-compact>span.test-inputs-select:nth-child(14)>select.test-inputs-select-select","value":"1","time":125401,"target":"select"},{"time":125402,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":125429,"x":617,"y":244},{"type":"mousemove","time":125629,"x":685,"y":237},{"type":"mousemove","time":125833,"x":705,"y":229},{"type":"mousemove","time":126033,"x":705,"y":224},{"type":"mousedown","time":126162,"x":711,"y":215},{"type":"mousemove","time":126245,"x":711,"y":215},{"type":"mouseup","time":126297,"x":711,"y":215},{"time":126298,"delay":400,"type":"screenshot-auto"}],"scrollY":925.5,"scrollX":0,"timestamp":1750344403074}] \ No newline at end of file From 7974074c7dbc950bac16b2daad145f22b26a7400 Mon Sep 17 00:00:00 2001 From: 100pah Date: Fri, 20 Jun 2025 21:24:30 +0800 Subject: [PATCH 49/49] switch zrender dep to v6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ff68498b..e9cd3bf68b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "tslib": "2.3.0", - "zrender": "github:ecomfe/zrender#feat/ignore-host-silent" + "zrender": "github:ecomfe/zrender#v6" }, "devDependencies": { "@babel/code-frame": "7.10.4",