This is an automated email from the ASF dual-hosted git repository.

plainheart pushed a commit to branch jitter-rng
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 20b64c18ac1010f9e9873401af52096c1dd83424
Author: plainheart <[email protected]>
AuthorDate: Wed Sep 16 17:15:13 2026 +0800

    feat(scatter): add `jitterRng` / `jitterSeed` option to configure a custom 
random number generator or a random seed for deterministic jitter layout
---
 src/chart/scatter/jitterLayout.ts | 33 +++++++++++++++++++++++++++++++--
 src/coord/axisCommonTypes.ts      |  2 ++
 src/util/jitter.ts                | 19 +++++++++++--------
 3 files changed, 44 insertions(+), 10 deletions(-)

diff --git a/src/chart/scatter/jitterLayout.ts 
b/src/chart/scatter/jitterLayout.ts
index a68ea6550..7dc332762 100644
--- a/src/chart/scatter/jitterLayout.ts
+++ b/src/chart/scatter/jitterLayout.ts
@@ -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 {
@@ -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;
@@ -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;
                             }
@@ -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;
                             }
diff --git a/src/coord/axisCommonTypes.ts b/src/coord/axisCommonTypes.ts
index 058dea900..89199a529 100644
--- a/src/coord/axisCommonTypes.ts
+++ b/src/coord/axisCommonTypes.ts
@@ -128,6 +128,8 @@ export interface AxisBaseOptionCommon extends 
ComponentOption,
     jitter?: number;
     jitterOverlap?: boolean;
     jitterMargin?: number;
+    jitterSeed?: string | number;
+    jitterRng?: () => number;
 
     breaks?: AxisBreakOption[];
     breakArea?: {
diff --git a/src/util/jitter.ts b/src/util/jitter.ts
index c0e1e394e..c68183189 100644
--- a/src/util/jitter.ts
+++ b/src/util/jitter.ts
@@ -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;
@@ -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(
@@ -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) {
@@ -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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to