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 02edddaef7cebae1053f1ed1dc392395559964e2
Author: plainheart <[email protected]>
AuthorDate: Wed Sep 16 17:15:13 2026 +0800

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

diff --git a/src/chart/scatter/jitterLayout.ts 
b/src/chart/scatter/jitterLayout.ts
index a68ea6550..44d9b1024 100644
--- a/src/chart/scatter/jitterLayout.ts
+++ b/src/chart/scatter/jitterLayout.ts
@@ -26,6 +26,7 @@ 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';
 
 export default function jitterLayout(): StageHandler {
     return {
@@ -58,6 +59,8 @@ export default function jitterLayout(): StageHandler {
                 return;
             }
 
+            const jitterRng = (baseAxis.model as 
AxisBaseModel).get('jitterRng');
+
             return {
                 progress(params, data): void {
                     const points = data.getLayout('points') as Float32Array;
@@ -79,7 +82,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 +92,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..cd57a673d 100644
--- a/src/coord/axisCommonTypes.ts
+++ b/src/coord/axisCommonTypes.ts
@@ -128,6 +128,7 @@ export interface AxisBaseOptionCommon extends 
ComponentOption,
     jitter?: number;
     jitterOverlap?: boolean;
     jitterMargin?: number;
+    jitterRng?: () => number;
 
     breaks?: AxisBreakOption[];
     breakArea?: {
diff --git a/src/util/jitter.ts b/src/util/jitter.ts
index c0e1e394e..352d3f971 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;
@@ -80,25 +81,27 @@ export function fixJitter(
     const bandWidth = isOrdinalScale(fixedAxis.scale)
         ? calcBandWidth(fixedAxis).w
         : null;
+    !rng && (rng = Math.random);
     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 +110,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 +133,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