Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/chart/scatter/jitterLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ import createRenderPlanner from '../helper/createRenderPlanner';
import { COORD_SYS_TYPE_CARTESIAN_2D } from '../../coord/cartesian/GridModel';
import { COORD_SYS_TYPE_SINGLE } from '../../coord/single/AxisModel';
import { validateUpstreamOutputRange } from '../../util/model';
import type { AxisBaseModel } from '../../coord/AxisBaseModel';
import { isString, reduce } from 'zrender/src/core/util';

const DEFAULT_JITTER_SEED = 'echarts-jitter';
const DEFAULT_JITTER_RNG = createRNG(DEFAULT_JITTER_SEED);

/**
* Mulberry32 RNG
*/
function createRNG(seed: string | number) {
let state = isString(seed)
? reduce(seed.split(''), (h, c) => Math.imul(31, h) + c.charCodeAt(0) | 0, 0)
: (Number(seed) | 0);
return function () {
let t = state += 0x6D2B79F5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}

export default function jitterLayout(): StageHandler {
return {
Expand Down Expand Up @@ -58,6 +78,15 @@ export default function jitterLayout(): StageHandler {
return;
}

const baseAxisModel = baseAxis.model as AxisBaseModel;
let jitterRng = baseAxisModel.get('jitterRng');
if (!jitterRng) {
const jitterSeed = baseAxisModel.get('jitterSeed');

jitterRng = jitterSeed == null || jitterSeed === DEFAULT_JITTER_SEED
? DEFAULT_JITTER_RNG
: createRNG(jitterSeed);
}
return {
progress(params, data): void {
const points = data.getLayout('points') as Float32Array;
Expand All @@ -79,7 +108,7 @@ export default function jitterLayout(): StageHandler {

if (jitterOnY) {
// x is fixed, and y is floating
const jittered = fixJitter(baseAxis, layout[0], layout[1], size / 2);
const jittered = fixJitter(baseAxis, layout[0], layout[1], size / 2, jitterRng);
if (hasPoints) {
points[offset + 1] = jittered;
}
Expand All @@ -89,7 +118,7 @@ export default function jitterLayout(): StageHandler {
}
else if (jitterOnX) {
// y is fixed, and x is floating
const jittered = fixJitter(baseAxis, layout[1], layout[0], size / 2);
const jittered = fixJitter(baseAxis, layout[1], layout[0], size / 2, jitterRng);
if (hasPoints) {
points[offset] = jittered;
}
Expand Down
2 changes: 2 additions & 0 deletions src/coord/axisCommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export interface AxisBaseOptionCommon extends ComponentOption,
jitter?: number;
jitterOverlap?: boolean;
jitterMargin?: number;
jitterSeed?: string | number;
jitterRng?: () => number;

breaks?: AxisBreakOption[];
breakArea?: {
Expand Down
19 changes: 11 additions & 8 deletions src/util/jitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export function fixJitter(
fixedAxis: Axis2D | SingleAxis,
fixedCoord: number,
floatCoord: number,
radius: number
radius: number,
rng: () => number
): number {
if (fixedAxis instanceof Axis2D) {
const scaleType = fixedAxis.scale.type;
Expand All @@ -81,24 +82,25 @@ export function fixJitter(
? calcBandWidth(fixedAxis).w
: null;
if (jitterOverlap) {
return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius);
return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius, rng);
}
return fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, jitterMargin);
return fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, jitterMargin, rng);
}

function fixJitterIgnoreOverlaps(
floatCoord: number,
jitter: number,
bandWidth: number | null,
radius: number
radius: number,
rng: () => number
): number {
// Don't clamp single axis
if (bandWidth === null) {
return floatCoord + (Math.random() - 0.5) * jitter;
return floatCoord + (rng() - 0.5) * jitter;
}
const maxJitter = bandWidth - radius * 2;
const actualJitter = Math.min(Math.max(0, jitter), maxJitter);
return floatCoord + (Math.random() - 0.5) * actualJitter;
return floatCoord + (rng() - 0.5) * actualJitter;
}

function fixJitterAvoidOverlaps(
Expand All @@ -107,7 +109,8 @@ function fixJitterAvoidOverlaps(
floatCoord: number,
radius: number,
jitter: number,
margin: number
margin: number,
rng: () => number
): number {
const store = inner(fixedAxis);
if (!store.items) {
Expand All @@ -129,7 +132,7 @@ function fixJitterAvoidOverlaps(
if (distance > jitter / 2 || (bandWidth && distance > bandWidth / 2 - radius)) {
// If the new item is moved too far, then give up.
// Fall back to random jitter.
return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius);
return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius, rng);
}

// Add new point to array
Expand Down
Loading