From ecfe5832db45d9261898bedf02c9628d85dbd7c5 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 16 May 2024 18:52:07 +0800 Subject: [PATCH 1/8] feat(scatter): support jitter for scatters #18432 --- src/coord/axisCommonTypes.ts | 1 + src/coord/axisDefault.ts | 1 + src/coord/cartesian/Cartesian2D.ts | 19 ++- src/coord/single/Single.ts | 7 + test/scatter-jitter.html | 262 +++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 test/scatter-jitter.html diff --git a/src/coord/axisCommonTypes.ts b/src/coord/axisCommonTypes.ts index fbfaf9cf43..e051d08b52 100644 --- a/src/coord/axisCommonTypes.ts +++ b/src/coord/axisCommonTypes.ts @@ -81,6 +81,7 @@ export interface AxisBaseOptionCommon extends ComponentOption, */ max?: ScaleDataValue | 'dataMax' | ((extent: {min: number, max: number}) => ScaleDataValue); + jitter?: number; } export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { diff --git a/src/coord/axisDefault.ts b/src/coord/axisDefault.ts index 4d2c6674b3..8e03ca133b 100644 --- a/src/coord/axisDefault.ts +++ b/src/coord/axisDefault.ts @@ -113,6 +113,7 @@ const categoryAxis: AxisBaseOption = zrUtil.merge({ boundaryGap: true, // Set false to faster category collection. deduplication: null, + jitter: 0, // splitArea: { // show: false // }, diff --git a/src/coord/cartesian/Cartesian2D.ts b/src/coord/cartesian/Cartesian2D.ts index 4072684d14..1351a60850 100644 --- a/src/coord/cartesian/Cartesian2D.ts +++ b/src/coord/cartesian/Cartesian2D.ts @@ -133,11 +133,26 @@ class Cartesian2D extends Cartesian implements CoordinateSystem { } const xAxis = this.getAxis('x'); const yAxis = this.getAxis('y'); - out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); - out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); + out[0] = this._fixJitter( + xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)), + xAxis + ); + out[1] = this._fixJitter( + yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)), + yAxis + ); return out; } + protected _fixJitter(coord: number, axis: Axis2D): number { + const jitter = axis.model.get('jitter'); + const scaleType = axis.scale.type; + if (jitter > 0 && (scaleType === 'category' || scaleType === 'ordinal')) { + return coord + (Math.random() - 0.5) * jitter; + } + return coord; + } + clampData(data: ScaleDataValue[], out?: number[]): number[] { const xScale = this.getAxis('x').scale; const yScale = this.getAxis('y').scale; diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts index c9226b5aae..80133df606 100644 --- a/src/coord/single/Single.ts +++ b/src/coord/single/Single.ts @@ -238,6 +238,13 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); + + const jitter = axis.model.get('jitter'); + if (jitter) { + const diff = (Math.random() - 0.5) * jitter; + pt[1 - idx] += diff; + } + return pt; } diff --git a/test/scatter-jitter.html b/test/scatter-jitter.html new file mode 100644 index 0000000000..700a8ba83e --- /dev/null +++ b/test/scatter-jitter.html @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + From 1ccceac62ffde8fc507c1ad861f15d52a0732702 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Fri, 24 May 2024 15:20:41 +0800 Subject: [PATCH 2/8] feat(jitter): support overlapping API --- src/chart/helper/SymbolDraw.ts | 9 +++- src/coord/axisCommonTypes.ts | 2 + src/coord/axisDefault.ts | 2 + src/coord/cartesian/Cartesian2D.ts | 24 +++------ src/coord/single/AxisModel.ts | 6 ++- src/util/jitter.ts | 83 ++++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/util/jitter.ts diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index 93e13848e1..8bfc2fe362 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -41,6 +41,7 @@ import { ScatterSeriesOption } from '../scatter/ScatterSeries'; import { getLabelStatesModels } from '../../label/labelStyle'; import Element from 'zrender/src/Element'; import SeriesModel from '../../model/Series'; +import { fixJitter, needFixJitter } from '../../util/jitter'; interface UpdateOpt { isIgnore?(idx: number): boolean @@ -182,17 +183,21 @@ class SymbolDraw { opt = normalizeUpdateOpt(opt); const group = this.group; - const seriesModel = data.hostModel; + const seriesModel = data.hostModel as SeriesModel; const oldData = this._data; const SymbolCtor = this._SymbolCtor; const disableAnimation = opt.disableAnimation; + const hasJitter = needFixJitter(seriesModel); const seriesScope = makeSeriesScope(data); const symbolUpdateOpt = { disableAnimation }; const getSymbolPoint = opt.getSymbolPoint || function (idx: number) { - return data.getItemLayout(idx); + const layout = data.getItemLayout(idx); + return hasJitter + ? fixJitter(layout as [number, number]) + : layout; }; diff --git a/src/coord/axisCommonTypes.ts b/src/coord/axisCommonTypes.ts index e051d08b52..47ea789d72 100644 --- a/src/coord/axisCommonTypes.ts +++ b/src/coord/axisCommonTypes.ts @@ -82,6 +82,8 @@ export interface AxisBaseOptionCommon extends ComponentOption, max?: ScaleDataValue | 'dataMax' | ((extent: {min: number, max: number}) => ScaleDataValue); jitter?: number; + jitterOverlap?: boolean; + jitterMargin?: number; } export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { diff --git a/src/coord/axisDefault.ts b/src/coord/axisDefault.ts index 8e03ca133b..b63168d4eb 100644 --- a/src/coord/axisDefault.ts +++ b/src/coord/axisDefault.ts @@ -114,6 +114,8 @@ const categoryAxis: AxisBaseOption = zrUtil.merge({ // Set false to faster category collection. deduplication: null, jitter: 0, + jitterOverlap: true, + jitterMargin: 5, // splitArea: { // show: false // }, diff --git a/src/coord/cartesian/Cartesian2D.ts b/src/coord/cartesian/Cartesian2D.ts index 1351a60850..b34f4786e2 100644 --- a/src/coord/cartesian/Cartesian2D.ts +++ b/src/coord/cartesian/Cartesian2D.ts @@ -28,6 +28,7 @@ import Grid from './Grid'; import Scale from '../../scale/Scale'; import { invert } from 'zrender/src/core/matrix'; import { applyTransform } from 'zrender/src/core/vector'; +import { fixJitter } from '../../util/jitter'; export const cartesian2DDimensions = ['x', 'y']; @@ -133,26 +134,15 @@ class Cartesian2D extends Cartesian implements CoordinateSystem { } const xAxis = this.getAxis('x'); const yAxis = this.getAxis('y'); - out[0] = this._fixJitter( - xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)), - xAxis - ); - out[1] = this._fixJitter( - yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)), - yAxis - ); + out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); + out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); + // const xCoord = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); + // const yCoord = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); + // out[0] = fixJitter(xAxis, xCoord, yCoord, 0); + // out[1] = fixJitter(yAxis, yCoord, xCoord, 0); return out; } - protected _fixJitter(coord: number, axis: Axis2D): number { - const jitter = axis.model.get('jitter'); - const scaleType = axis.scale.type; - if (jitter > 0 && (scaleType === 'category' || scaleType === 'ordinal')) { - return coord + (Math.random() - 0.5) * jitter; - } - return coord; - } - clampData(data: ScaleDataValue[], out?: number[]): number[] { const xScale = this.getAxis('x').scale; const yScale = this.getAxis('y').scale; diff --git a/src/coord/single/AxisModel.ts b/src/coord/single/AxisModel.ts index bcd85268fd..b4b6ebd0cc 100644 --- a/src/coord/single/AxisModel.ts +++ b/src/coord/single/AxisModel.ts @@ -97,7 +97,11 @@ class SingleAxisModel extends ComponentModel type: 'dashed', opacity: 0.2 } - } + }, + + jitter: 0, + jitterOverlap: true, + jitterMargin: 5, }; } diff --git a/src/util/jitter.ts b/src/util/jitter.ts new file mode 100644 index 0000000000..87437ee288 --- /dev/null +++ b/src/util/jitter.ts @@ -0,0 +1,83 @@ +import { AxisBaseModel } from '../coord/AxisBaseModel'; +import Axis2D from '../coord/cartesian/Axis2D'; +import SingleAxis from '../coord/single/SingleAxis'; +import SeriesModel from '../model/Series'; +import { makeInner } from './model'; + +export function needFixJitter(seriesModel: SeriesModel): boolean { + const { coordinateSystem } = seriesModel; + const { type: coordType } = coordinateSystem; + const baseAxis = coordinateSystem.getBaseAxis(); + const { type: scaleType } = baseAxis.scale; + + return coordType === 'cartesian2d' + && (scaleType === 'category' || scaleType === 'ordinal') + || coordType === 'single'; +} + +export function fixJitter(itemLayout: [number, number]) { + + return itemLayout; +} + +// type JitterParams = { +// baseCoord: number, +// otherCoord: number, +// r: number +// }; + +// const inner = makeInner<{ items: JitterParams[] }, Axis2D | SingleAxis>(); + +// export function fixJitter( +// baseAxis: Axis2D | SingleAxis, +// baseCoord: number, +// otherCoord: number, +// radius: number +// ): number { +// if (baseAxis instanceof Axis2D) { +// const scaleType = baseAxis.scale.type; +// if (scaleType !== 'category' && scaleType !== 'ordinal') { +// return baseCoord; +// } +// } +// const axisModel = baseAxis.model as AxisBaseModel; +// const jitter = axisModel.get('jitter'); +// const jitterOverlap = axisModel.get('jitterOverlap'); +// const jitterMargin = axisModel.get('jitterMargin') || 0; +// if (jitter > 0) { +// if (jitterOverlap) { +// return fixJitterIgnoreOverlaps(baseCoord, jitter); +// } +// else { +// return fixJitterAvoidOverlaps(baseAxis, baseCoord, otherCoord, radius, jitterMargin); +// } +// } +// return baseCoord; +// } + +// function fixJitterIgnoreOverlaps(baseCoord: number, jitter: number): number { +// return baseCoord + (Math.random() - 0.5) * jitter; +// } + +// function fixJitterAvoidOverlaps( +// baseAxis: Axis2D | SingleAxis, +// baseCoord: number, +// otherCoord: number, +// radius: number, +// margin: number +// ): number { +// const store = inner(baseAxis); +// if (!store.items) { +// store.items = []; +// } +// const items = store.items; +// console.log(items); + +// if (items.length === 0) { +// items.push({ baseCoord: baseCoord, otherCoord: otherCoord, r: radius }); +// return baseCoord; +// } +// else { +// return 0; +// } +// } From ae6f8bd774448cfa6d65e96dc0e13d0915041554 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Mon, 27 May 2024 18:18:03 +0800 Subject: [PATCH 3/8] test(jitter): support overlapping API --- test/at.html | 95 +++++++ test/bar-stack-minHeight.html | 206 ++++++++++++++ test/label-emphasis.html | 90 ++++++ test/legend-emphasis.html | 99 +++++++ test/line-stack-connectNulls.html | 103 +++++++ test/radar-zero.html | 117 ++++++++ test/scatter-jitter.html | 51 +++- test/tmp-bug-svg.html | 267 ++++++++++++++++++ ...eemap-option-label-position-with-rich.html | 95 +++++++ 9 files changed, 1119 insertions(+), 4 deletions(-) create mode 100644 test/at.html create mode 100644 test/bar-stack-minHeight.html create mode 100644 test/label-emphasis.html create mode 100644 test/legend-emphasis.html create mode 100644 test/line-stack-connectNulls.html create mode 100644 test/radar-zero.html create mode 100644 test/tmp-bug-svg.html create mode 100644 test/treemap-option-label-position-with-rich.html diff --git a/test/at.html b/test/at.html new file mode 100644 index 0000000000..5c0976447b --- /dev/null +++ b/test/at.html @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/bar-stack-minHeight.html b/test/bar-stack-minHeight.html new file mode 100644 index 0000000000..56805e9045 --- /dev/null +++ b/test/bar-stack-minHeight.html @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/test/label-emphasis.html b/test/label-emphasis.html new file mode 100644 index 0000000000..4992e651bf --- /dev/null +++ b/test/label-emphasis.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/legend-emphasis.html b/test/legend-emphasis.html new file mode 100644 index 0000000000..dbff03d5c8 --- /dev/null +++ b/test/legend-emphasis.html @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/line-stack-connectNulls.html b/test/line-stack-connectNulls.html new file mode 100644 index 0000000000..f35ad3ac5e --- /dev/null +++ b/test/line-stack-connectNulls.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/radar-zero.html b/test/radar-zero.html new file mode 100644 index 0000000000..2c47387640 --- /dev/null +++ b/test/radar-zero.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/test/scatter-jitter.html b/test/scatter-jitter.html index 700a8ba83e..f6a066760c 100644 --- a/test/scatter-jitter.html +++ b/test/scatter-jitter.html @@ -38,6 +38,7 @@
+
@@ -52,7 +53,7 @@ - + + + + diff --git a/test/tmp-bug-svg.html b/test/tmp-bug-svg.html new file mode 100644 index 0000000000..61106b2e27 --- /dev/null +++ b/test/tmp-bug-svg.html @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + 111 + + + 222 + + + 3333 + + + 444 + + + 555 + + + + +
+ + + +
+ + +
+ + + + + + + + diff --git a/test/treemap-option-label-position-with-rich.html b/test/treemap-option-label-position-with-rich.html new file mode 100644 index 0000000000..274b866e85 --- /dev/null +++ b/test/treemap-option-label-position-with-rich.html @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + +
+
+ + + + From ef777eb4eae44d3b1d87e5229c6170160ab94396 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 29 May 2024 18:04:37 +0800 Subject: [PATCH 4/8] feat(jitter): support beeswarm-like jitter --- src/chart/helper/SymbolDraw.ts | 42 +++++++- src/coord/axisDefault.ts | 2 +- src/coord/cartesian/Cartesian2D.ts | 4 - src/coord/single/AxisModel.ts | 2 +- src/coord/single/Single.ts | 6 -- src/util/jitter.ts | 167 ++++++++++++++++++----------- 6 files changed, 145 insertions(+), 78 deletions(-) diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index 8bfc2fe362..efaafca5c4 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -41,7 +41,9 @@ import { ScatterSeriesOption } from '../scatter/ScatterSeries'; import { getLabelStatesModels } from '../../label/labelStyle'; import Element from 'zrender/src/Element'; import SeriesModel from '../../model/Series'; -import { fixJitter, needFixJitter } from '../../util/jitter'; +import { JitterData, fixJitter, needFixJitter } from '../../util/jitter'; +import Axis2D from '../../coord/cartesian/Axis2D'; +import SingleAxis from '../../coord/single/SingleAxis'; interface UpdateOpt { isIgnore?(idx: number): boolean @@ -187,7 +189,11 @@ class SymbolDraw { const oldData = this._data; const SymbolCtor = this._SymbolCtor; const disableAnimation = opt.disableAnimation; - const hasJitter = needFixJitter(seriesModel); + const coord = seriesModel.coordinateSystem; + const baseAxis = coord.getBaseAxis(); + const hasJitter = needFixJitter(seriesModel, baseAxis); + // TODO: what if symbolSize is from visualMap? + const symbolSize = seriesModel.get('symbolSize' as any); const seriesScope = makeSeriesScope(data); @@ -195,9 +201,35 @@ class SymbolDraw { const getSymbolPoint = opt.getSymbolPoint || function (idx: number) { const layout = data.getItemLayout(idx); - return hasJitter - ? fixJitter(layout as [number, number]) - : layout; + + // return layout + if (hasJitter) { + const dim = baseAxis.dim; + const orient = (baseAxis as SingleAxis).orient; + const isSingleY = orient === 'horizontal' && baseAxis.type !== 'category' + || orient === 'vertical' && baseAxis.type === 'category'; + if (dim === 'y' || dim === 'single' && isSingleY) { + // x is fixed, and y is floating + const jittered = fixJitter( + baseAxis as Axis2D | SingleAxis, + layout[0], + layout[1], + symbolSize / 2 + ); + return [layout[0], jittered]; + } + else if (dim === 'x' || dim === 'single' && !isSingleY) { + // y is fixed, and x is floating + const jittered = fixJitter( + baseAxis as Axis2D | SingleAxis, + layout[1], + layout[0], + symbolSize / 2 + ); + return [jittered, layout[1]]; + } + } + return layout; }; diff --git a/src/coord/axisDefault.ts b/src/coord/axisDefault.ts index b63168d4eb..6683b36669 100644 --- a/src/coord/axisDefault.ts +++ b/src/coord/axisDefault.ts @@ -115,7 +115,7 @@ const categoryAxis: AxisBaseOption = zrUtil.merge({ deduplication: null, jitter: 0, jitterOverlap: true, - jitterMargin: 5, + jitterMargin: 2, // splitArea: { // show: false // }, diff --git a/src/coord/cartesian/Cartesian2D.ts b/src/coord/cartesian/Cartesian2D.ts index b34f4786e2..d0eefc7aca 100644 --- a/src/coord/cartesian/Cartesian2D.ts +++ b/src/coord/cartesian/Cartesian2D.ts @@ -136,10 +136,6 @@ class Cartesian2D extends Cartesian implements CoordinateSystem { const yAxis = this.getAxis('y'); out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); - // const xCoord = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); - // const yCoord = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); - // out[0] = fixJitter(xAxis, xCoord, yCoord, 0); - // out[1] = fixJitter(yAxis, yCoord, xCoord, 0); return out; } diff --git a/src/coord/single/AxisModel.ts b/src/coord/single/AxisModel.ts index b4b6ebd0cc..4301959c8f 100644 --- a/src/coord/single/AxisModel.ts +++ b/src/coord/single/AxisModel.ts @@ -101,7 +101,7 @@ class SingleAxisModel extends ComponentModel jitter: 0, jitterOverlap: true, - jitterMargin: 5, + jitterMargin: 2, }; } diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts index 80133df606..46ffe59dda 100644 --- a/src/coord/single/Single.ts +++ b/src/coord/single/Single.ts @@ -239,12 +239,6 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); - const jitter = axis.model.get('jitter'); - if (jitter) { - const diff = (Math.random() - 0.5) * jitter; - pt[1 - idx] += diff; - } - return pt; } diff --git a/src/util/jitter.ts b/src/util/jitter.ts index 87437ee288..60bf84e464 100644 --- a/src/util/jitter.ts +++ b/src/util/jitter.ts @@ -1,83 +1,128 @@ +import Axis from '../coord/Axis'; import { AxisBaseModel } from '../coord/AxisBaseModel'; import Axis2D from '../coord/cartesian/Axis2D'; import SingleAxis from '../coord/single/SingleAxis'; import SeriesModel from '../model/Series'; import { makeInner } from './model'; -export function needFixJitter(seriesModel: SeriesModel): boolean { +export function needFixJitter(seriesModel: SeriesModel, axis: Axis): boolean { const { coordinateSystem } = seriesModel; const { type: coordType } = coordinateSystem; const baseAxis = coordinateSystem.getBaseAxis(); const { type: scaleType } = baseAxis.scale; - - return coordType === 'cartesian2d' + const seriesValid = coordType === 'cartesian2d' && (scaleType === 'category' || scaleType === 'ordinal') || coordType === 'single'; + + const axisValid = (axis.model as AxisBaseModel).get('jitter') > 0; + return seriesValid && axisValid; } -export function fixJitter(itemLayout: [number, number]) { +export type JitterData = { + fixedCoord: number, + floatCoord: number, + r: number +}; + +const inner = makeInner<{ items: JitterData[] }, Axis2D | SingleAxis>(); - return itemLayout; +/** + * Fix jitter for overlapping data points. + * + * @param fixedAxis The axis whose coord doesn't change with jitter. + * @param fixedCoord The coord of fixedAxis. + * @param floatCoord The coord of the other axis, which should be changed with jittering. + * @param radius The radius of the data point, considering the symbol is a circle. + * @returns updated floatCoord. + */ +export function fixJitter( + fixedAxis: Axis2D | SingleAxis, + fixedCoord: number, + floatCoord: number, + radius: number +): number { + if (fixedAxis instanceof Axis2D) { + const scaleType = fixedAxis.scale.type; + if (scaleType !== 'category' && scaleType !== 'ordinal') { + return floatCoord; + } + } + const axisModel = fixedAxis.model as AxisBaseModel; + const jitter = axisModel.get('jitter'); + const jitterOverlap = axisModel.get('jitterOverlap'); + const jitterMargin = axisModel.get('jitterMargin') || 0; + if (jitter > 0) { + if (jitterOverlap) { + return fixJitterIgnoreOverlaps(floatCoord, jitter); + } + else { + return fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, jitterMargin); + } + } + return floatCoord; } -// type JitterParams = { -// baseCoord: number, -// otherCoord: number, -// r: number -// }; +function fixJitterIgnoreOverlaps(floatCoord: number, jitter: number): number { + return floatCoord + (Math.random() - 0.5) * jitter; +} -// const inner = makeInner<{ items: JitterParams[] }, Axis2D | SingleAxis>(); +function fixJitterAvoidOverlaps( + fixedAxis: Axis2D | SingleAxis, + fixedCoord: number, + floatCoord: number, + radius: number, + jitter: number, + margin: number +): number { + const store = inner(fixedAxis); + if (!store.items) { + store.items = []; + } + const items = store.items; -// export function fixJitter( -// baseAxis: Axis2D | SingleAxis, -// baseCoord: number, -// otherCoord: number, -// radius: number -// ): number { -// if (baseAxis instanceof Axis2D) { -// const scaleType = baseAxis.scale.type; -// if (scaleType !== 'category' && scaleType !== 'ordinal') { -// return baseCoord; -// } -// } -// const axisModel = baseAxis.model as AxisBaseModel; -// const jitter = axisModel.get('jitter'); -// const jitterOverlap = axisModel.get('jitterOverlap'); -// const jitterMargin = axisModel.get('jitterMargin') || 0; -// if (jitter > 0) { -// if (jitterOverlap) { -// return fixJitterIgnoreOverlaps(baseCoord, jitter); -// } -// else { -// return fixJitterAvoidOverlaps(baseAxis, baseCoord, otherCoord, radius, jitterMargin); -// } -// } -// return baseCoord; -// } + const floatA = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, 1); + const floatB = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, -1); + let minFloat = Math.abs(floatA - floatCoord) < Math.abs(floatB - floatCoord) ? floatA : floatB; + if (Math.abs(minFloat - floatCoord) > jitter / 2) { + // If the new item is moved too far, then give up. + // Fall back to random jitter. + minFloat = fixJitterIgnoreOverlaps(floatCoord, jitter); + } -// function fixJitterIgnoreOverlaps(baseCoord: number, jitter: number): number { -// return baseCoord + (Math.random() - 0.5) * jitter; -// } + items.push({ fixedCoord, floatCoord: minFloat, r: radius }); + return minFloat; +} -// function fixJitterAvoidOverlaps( -// baseAxis: Axis2D | SingleAxis, -// baseCoord: number, -// otherCoord: number, -// radius: number, -// margin: number -// ): number { -// const store = inner(baseAxis); -// if (!store.items) { -// store.items = []; -// } -// const items = store.items; -// console.log(items); +function placeJitterOnDirection( + items: JitterData[], + fixedCoord: number, + floatCoord: number, + radius: number, + jitter: number, + margin: number, + direction: 1 | -1 +) { + // Check for overlap with previous items. + let y = floatCoord; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const dx = fixedCoord - item.fixedCoord; + const dy = y - item.floatCoord; + const d2 = dx * dx + dy * dy; + const r = radius + item.r + margin; + if (d2 < r * r) { + // Overlap. Try to move the new item along otherCoord direction. + const newY = item.floatCoord + Math.sqrt(r * r - dx * dx) * direction; + if (direction > 0 && newY > y || direction < 0 && newY < y) { + y = newY; + } -// if (items.length === 0) { -// items.push({ baseCoord: baseCoord, otherCoord: otherCoord, r: radius }); -// return baseCoord; -// } -// else { -// return 0; -// } -// } + if (Math.abs(newY - floatCoord) > jitter / 2) { + // If the new item is moved too far, then give up. + // Fall back to random jitter. + return Number.MAX_VALUE; + } + } + } + return y; +} From b581789992eda9f86dee5460b6fb9221e59e85db Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 30 May 2024 19:29:10 +0800 Subject: [PATCH 5/8] feat(jitter): support beeswarm-like jitter --- src/chart/helper/SymbolDraw.ts | 13 +- src/coord/cartesian/Cartesian2D.ts | 1 - src/coord/single/Single.ts | 1 - src/util/jitter.ts | 1 + test/at.html | 95 ------ test/bar-stack-minHeight.html | 206 ------------- test/label-emphasis.html | 90 ------ test/legend-emphasis.html | 99 ------ test/line-stack-connectNulls.html | 103 ------- test/radar-zero.html | 117 ------- test/scatter-jitter.html | 291 ++++++++++++++++-- test/tmp-bug-svg.html | 267 ---------------- ...eemap-option-label-position-with-rich.html | 95 ------ 13 files changed, 268 insertions(+), 1111 deletions(-) delete mode 100644 test/at.html delete mode 100644 test/bar-stack-minHeight.html delete mode 100644 test/label-emphasis.html delete mode 100644 test/legend-emphasis.html delete mode 100644 test/line-stack-connectNulls.html delete mode 100644 test/radar-zero.html delete mode 100644 test/tmp-bug-svg.html delete mode 100644 test/treemap-option-label-position-with-rich.html diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index efaafca5c4..c2ede521a0 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -41,7 +41,7 @@ import { ScatterSeriesOption } from '../scatter/ScatterSeries'; import { getLabelStatesModels } from '../../label/labelStyle'; import Element from 'zrender/src/Element'; import SeriesModel from '../../model/Series'; -import { JitterData, fixJitter, needFixJitter } from '../../util/jitter'; +import { fixJitter, needFixJitter } from '../../util/jitter'; import Axis2D from '../../coord/cartesian/Axis2D'; import SingleAxis from '../../coord/single/SingleAxis'; @@ -192,8 +192,6 @@ class SymbolDraw { const coord = seriesModel.coordinateSystem; const baseAxis = coord.getBaseAxis(); const hasJitter = needFixJitter(seriesModel, baseAxis); - // TODO: what if symbolSize is from visualMap? - const symbolSize = seriesModel.get('symbolSize' as any); const seriesScope = makeSeriesScope(data); @@ -201,6 +199,11 @@ class SymbolDraw { const getSymbolPoint = opt.getSymbolPoint || function (idx: number) { const layout = data.getItemLayout(idx); + const rawSize = data.getItemVisual(idx, 'symbolSize'); + const size = rawSize instanceof Array ? (rawSize[1] + rawSize[0]) / 2 : rawSize; + if (data.get('single', idx) === 90.956) { + debugger + } // return layout if (hasJitter) { @@ -214,7 +217,7 @@ class SymbolDraw { baseAxis as Axis2D | SingleAxis, layout[0], layout[1], - symbolSize / 2 + size / 2 ); return [layout[0], jittered]; } @@ -224,7 +227,7 @@ class SymbolDraw { baseAxis as Axis2D | SingleAxis, layout[1], layout[0], - symbolSize / 2 + size / 2 ); return [jittered, layout[1]]; } diff --git a/src/coord/cartesian/Cartesian2D.ts b/src/coord/cartesian/Cartesian2D.ts index d0eefc7aca..4072684d14 100644 --- a/src/coord/cartesian/Cartesian2D.ts +++ b/src/coord/cartesian/Cartesian2D.ts @@ -28,7 +28,6 @@ import Grid from './Grid'; import Scale from '../../scale/Scale'; import { invert } from 'zrender/src/core/matrix'; import { applyTransform } from 'zrender/src/core/vector'; -import { fixJitter } from '../../util/jitter'; export const cartesian2DDimensions = ['x', 'y']; diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts index 46ffe59dda..c9226b5aae 100644 --- a/src/coord/single/Single.ts +++ b/src/coord/single/Single.ts @@ -238,7 +238,6 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); - return pt; } diff --git a/src/util/jitter.ts b/src/util/jitter.ts index 60bf84e464..04bf47bb83 100644 --- a/src/util/jitter.ts +++ b/src/util/jitter.ts @@ -115,6 +115,7 @@ function placeJitterOnDirection( const newY = item.floatCoord + Math.sqrt(r * r - dx * dx) * direction; if (direction > 0 && newY > y || direction < 0 && newY < y) { y = newY; + i = 0; // Back to check from the first item. } if (Math.abs(newY - floatCoord) > jitter / 2) { diff --git a/test/at.html b/test/at.html deleted file mode 100644 index 5c0976447b..0000000000 --- a/test/at.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - diff --git a/test/bar-stack-minHeight.html b/test/bar-stack-minHeight.html deleted file mode 100644 index 56805e9045..0000000000 --- a/test/bar-stack-minHeight.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- -
- -
- - - - - - - - - - - - - - - - - - diff --git a/test/label-emphasis.html b/test/label-emphasis.html deleted file mode 100644 index 4992e651bf..0000000000 --- a/test/label-emphasis.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - diff --git a/test/legend-emphasis.html b/test/legend-emphasis.html deleted file mode 100644 index dbff03d5c8..0000000000 --- a/test/legend-emphasis.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - diff --git a/test/line-stack-connectNulls.html b/test/line-stack-connectNulls.html deleted file mode 100644 index f35ad3ac5e..0000000000 --- a/test/line-stack-connectNulls.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - diff --git a/test/radar-zero.html b/test/radar-zero.html deleted file mode 100644 index 2c47387640..0000000000 --- a/test/radar-zero.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - diff --git a/test/scatter-jitter.html b/test/scatter-jitter.html index f6a066760c..0f9221f853 100644 --- a/test/scatter-jitter.html +++ b/test/scatter-jitter.html @@ -39,21 +39,21 @@
+
+
+
+
- - - - + + - + + + + diff --git a/test/tmp-bug-svg.html b/test/tmp-bug-svg.html deleted file mode 100644 index 61106b2e27..0000000000 --- a/test/tmp-bug-svg.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - 111 - - - 222 - - - 3333 - - - 444 - - - 555 - - - - -
- - - -
- - -
- - - - - - - - diff --git a/test/treemap-option-label-position-with-rich.html b/test/treemap-option-label-position-with-rich.html deleted file mode 100644 index 274b866e85..0000000000 --- a/test/treemap-option-label-position-with-rich.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - -
-
- - - - From 52f5ef46b75b521dac1a8ce1edb9ea8ed0ac5f40 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 30 May 2024 19:35:42 +0800 Subject: [PATCH 6/8] feat(jitter): fix lint and unit test --- src/chart/helper/SymbolDraw.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index c2ede521a0..9f4ad08c4d 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -190,8 +190,8 @@ class SymbolDraw { const SymbolCtor = this._SymbolCtor; const disableAnimation = opt.disableAnimation; const coord = seriesModel.coordinateSystem; - const baseAxis = coord.getBaseAxis(); - const hasJitter = needFixJitter(seriesModel, baseAxis); + const baseAxis = coord.getBaseAxis ? coord.getBaseAxis() : null; + const hasJitter = baseAxis && needFixJitter(seriesModel, baseAxis); const seriesScope = makeSeriesScope(data); @@ -201,9 +201,6 @@ class SymbolDraw { const layout = data.getItemLayout(idx); const rawSize = data.getItemVisual(idx, 'symbolSize'); const size = rawSize instanceof Array ? (rawSize[1] + rawSize[0]) / 2 : rawSize; - if (data.get('single', idx) === 90.956) { - debugger - } // return layout if (hasJitter) { From b95d3791b6d3e3957f59ec6569ccf6c1106d3b9f Mon Sep 17 00:00:00 2001 From: Ovilia Date: Tue, 4 Jun 2024 17:36:09 +0800 Subject: [PATCH 7/8] refactor(jitter): fix import type --- src/chart/helper/SymbolDraw.ts | 4 ++-- src/util/jitter.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index 9f4ad08c4d..939e940aaa 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -42,8 +42,8 @@ import { getLabelStatesModels } from '../../label/labelStyle'; import Element from 'zrender/src/Element'; import SeriesModel from '../../model/Series'; import { fixJitter, needFixJitter } from '../../util/jitter'; -import Axis2D from '../../coord/cartesian/Axis2D'; -import SingleAxis from '../../coord/single/SingleAxis'; +import type Axis2D from '../../coord/cartesian/Axis2D'; +import type SingleAxis from '../../coord/single/SingleAxis'; interface UpdateOpt { isIgnore?(idx: number): boolean diff --git a/src/util/jitter.ts b/src/util/jitter.ts index 04bf47bb83..6e9fba3601 100644 --- a/src/util/jitter.ts +++ b/src/util/jitter.ts @@ -1,8 +1,8 @@ -import Axis from '../coord/Axis'; -import { AxisBaseModel } from '../coord/AxisBaseModel'; +import type Axis from '../coord/Axis'; +import type { AxisBaseModel } from '../coord/AxisBaseModel'; import Axis2D from '../coord/cartesian/Axis2D'; -import SingleAxis from '../coord/single/SingleAxis'; -import SeriesModel from '../model/Series'; +import type SingleAxis from '../coord/single/SingleAxis'; +import type SeriesModel from '../model/Series'; import { makeInner } from './model'; export function needFixJitter(seriesModel: SeriesModel, axis: Axis): boolean { From e9fdf5fe0a1bc85a23740acd17d8ca0e29c74e9f Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 31 Jul 2024 19:53:44 +0800 Subject: [PATCH 8/8] test: add violin demo --- test/violin.html | 208 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 test/violin.html diff --git a/test/violin.html b/test/violin.html new file mode 100644 index 0000000000..0c656cb78e --- /dev/null +++ b/test/violin.html @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +