/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/util/jitter.ts
184 строки
6 KB
100pah
Fix bugs introduced by preceding commit.
24 мар 2026, 21:41
24 мар 2026, 21:41
7a9eda4
Код
Авторство
О чём код?
/* * 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 Axis from '../coord/Axis'; import { calcBandWidth } from '../coord/axisBand'; import type { AxisBaseModel } from '../coord/AxisBaseModel'; import Axis2D from '../coord/cartesian/Axis2D'; import { COORD_SYS_TYPE_CARTESIAN_2D } from '../coord/cartesian/GridModel'; import { COORD_SYS_TYPE_SINGLE } from '../coord/single/AxisModel'; import type SingleAxis from '../coord/single/SingleAxis'; import type SeriesModel from '../model/Series'; import { isOrdinalScale } from '../scale/helper'; import { makeInner } from './model'; export function needFixJitter(seriesModel: SeriesModel, axis: Axis): boolean { const coordinateSystem = seriesModel.coordinateSystem; const coordType = coordinateSystem && coordinateSystem.type; const baseAxis = coordinateSystem && coordinateSystem.getBaseAxis && coordinateSystem.getBaseAxis(); const scaleType = baseAxis && baseAxis.scale && baseAxis.scale.type; const seriesValid = coordType === COORD_SYS_TYPE_CARTESIAN_2D && scaleType === 'ordinal' || coordType === COORD_SYS_TYPE_SINGLE; const axisValid = (axis.model as AxisBaseModel).get('jitter') > 0; return seriesValid && axisValid; } export type JitterData = { fixedCoord: number, floatCoord: number, r: number }; const inner = makeInner<{ items: JitterData[] }, Axis2D | SingleAxis>(); /** * 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 !== 'ordinal') { return floatCoord; } } const axisModel = fixedAxis.model as AxisBaseModel; const jitter = axisModel.get('jitter'); if (!(jitter > 0)) { return floatCoord; } const jitterOverlap = axisModel.get('jitterOverlap'); const jitterMargin = axisModel.get('jitterMargin') || 0; // Get band width to limit jitter range const bandWidth = isOrdinalScale(fixedAxis.scale) ? calcBandWidth(fixedAxis).w : null; if (jitterOverlap) { return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius); } return fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, jitterMargin); } function fixJitterIgnoreOverlaps( floatCoord: number, jitter: number, bandWidth: number | null, radius: number ): number { // Don't clamp single axis if (bandWidth === null) { return floatCoord + (Math.random() - 0.5) * jitter; } const maxJitter = bandWidth - radius * 2; const actualJitter = Math.min(Math.max(0, jitter), maxJitter); return floatCoord + (Math.random() - 0.5) * actualJitter; } 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; // Try both positive and negative directions, choose the one with smaller movement const overlapA = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, 1); const overlapB = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, -1); const minFloat = Math.abs(overlapA - floatCoord) < Math.abs(overlapB - floatCoord) ? overlapA : overlapB; // Clamp only category axis const bandWidth = isOrdinalScale(fixedAxis.scale) ? calcBandWidth(fixedAxis).w : null; const distance = Math.abs(minFloat - floatCoord); 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); } // Add new point to array items.push({ fixedCoord: fixedCoord, floatCoord: minFloat, r: radius }); return minFloat; } function placeJitterOnDirection( items: JitterData[], fixedCoord: number, floatCoord: number, radius: number, jitter: number, margin: number, direction: 1 | -1 ): number { let y = floatCoord; // Check all existing items for overlap and find the maximum adjustment needed 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) { // Has overlap, calculate required adjustment const requiredY = item.floatCoord + Math.sqrt(r * r - dx * dx) * direction; // Check if this adjustment would move too far if (Math.abs(requiredY - floatCoord) > jitter / 2) { return Number.MAX_VALUE; // Give up } // Update y only when it's larger to the center if (direction === 1 && requiredY > y || direction === -1 && requiredY < y) { y = requiredY; // Loop from the start again i = -1; // Reset index to recheck all items continue; // Recalculate with the new y position } } } return y; }