8000 Fix/label sampling error for rich by xile611 · Pull Request #1856 · VisActor/VRender · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix/label sampling error for rich #1856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 10, 2025
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: disable sampling when label is rich text\n\n",
"type": "none",
"packageName": "@visactor/vrender-components"
}
],
"packageName": "@visactor/vrender-components",
"email": "dingling112@gmail.com"
}
79 changes: 41 additions & 38 deletions packages/vrender-components/src/axis/tick-data/continuous.ts
10000
Original file line number Diff line number Diff line change
Expand Up @@ -166,65 +166,68 @@ export const continuousTicks = (scale: ContinuousScale, op: ITickDataOpt): ITick
samplingScaleTicks.push(tick);
}
});
items = getCartesianLabelBounds(scale, samplingScaleTicks, op as ICartesianTickDataOpt).map(
items = getCartesianLabelBounds(scale, samplingScaleTicks, op as ICartesianTickDataOpt)?.map(
(bounds, i) =>
({
AABBBounds: bounds,
value: samplingScaleTicks[i]
} as ILabelItem<number>)
);
} else {
items = getCartesianLabelBounds(scale, scaleTicks, op as ICartesianTickDataOpt).map(
items = getCartesianLabelBounds(scale, scaleTicks, op as ICartesianTickDataOpt)?.map(
(bounds, i) =>
({
AABBBounds: bounds,
value: scaleTicks[i]
} as ILabelItem<number>)
);
}
const firstSourceItem = items[0];
const lastSourceItem = last(items);

const samplingMethod = breakData && breakData() ? methods.greedy : methods.parity; // 由于轴截断后刻度会存在不均匀的情况,所以不能使用 parity 算法
while (items.length >= 3 && hasOverlap(items as any, labelGap)) {
items = samplingMethod(items, labelGap);
}

const checkFirst = op.labelFirstVisible;
let checkLast = op.labelLastVisible; // 这里和 auto-hide 里的逻辑有差异,不根据 length 自动强制显示最后一个(会引起 vtable 较多 badcase)。
if (items) {
const firstSourceItem = items[0];
const lastSourceItem = last(items);

if (intersect(firstSourceItem as any, lastSourceItem as any, labelGap)) {
if (items.includes(lastSourceItem) && items.length > 1 && checkFirst && checkLast) {
items.splice(items.indexOf(lastSourceItem), 1);
checkLast = false;
const samplingMethod = breakData && breakData() ? methods.greedy : methods.parity; // 由于轴截断后刻度会存在不均匀的情况,所以不能使用 parity 算法
while (items.length >= 3 && hasOverlap(items as any, labelGap)) {
items = samplingMethod(items, labelGap);
}
}

forceItemVisible(firstSourceItem, items, checkFirst, (item: ILabelItem<number>) =>
intersect(item as any, firstSourceItem as any, labelGap)
);
forceItemVisible(
lastSourceItem,
items,
checkLast,
(item: ILabelItem<number>) =>
intersect(item as any, lastSourceItem as any, labelGap) ||
(checkFirst && item !== firstSourceItem ? intersect(item as any, firstSourceItem as any, labelGap) : false),
true
);

const ticks = items.map(item => item.value);

if (ticks.length < 3 && labelFlush) {
if (ticks.length > 1) {
ticks.pop();
const checkFirst = op.labelFirstVisible;
let checkLast = op.labelLastVisible; // 这里和 auto-hide 里的逻辑有差异,不根据 length 自动强制显示最后一个(会引起 vtable 较多 badcase)。

if (intersect(firstSourceItem as any, lastSourceItem as any, labelGap)) {
if (items.includes(lastSourceItem) && items.length > 1 && checkFirst && checkLast) {
items.splice(items.indexOf(lastSourceItem), 1);
checkLast = false;
}
}
if (last(ticks) !== last(scaleTicks)) {
ticks.push(last(scaleTicks));

forceItemVisible(firstSourceItem, items, checkFirst, (item: ILabelItem<number>) =>
intersect(item as any, firstSourceItem as any, labelGap)
);
forceItemVisible(
lastSourceItem,
items,
checkLast,
(item: ILabelItem<number>) =>
intersect(item as any, lastSourceItem as any, labelGap) ||
(checkFirst && item !== firstSourceItem ? intersect(item as any, firstSourceItem as any, labelGap) : false),
true
);

const ticks = items.map(item => item.value);

if (ticks.length < 3 && labelFlush) {
if (ticks.length > 1) {
ticks.pop();
}
if (last(ticks) !== last(scaleTicks)) {
ticks.push(last(scaleTicks));
}
}
}

scaleTicks = ticks;
scaleTicks = ticks;
}
}
}
return convertDomainToTickData(scaleTicks);
Expand Down
109 changes: 60 additions & 49 deletions packages/vrender-components/src/axis/tick-data/discrete/linear.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BandScale, IBaseScale } from '@visactor/vscale';
import { isFunction, isValid, maxInArray, minInArray, binaryFuzzySearchInNumberRange } from '@visactor/vutils';
import { isFunction, isValid, maxInArray, minInArray, binaryFuzzySearchInNumberRange, isNil } from '@visactor/vutils';
import type { ICartesianTickDataOpt, ITickData } from '../../type';
import { convertDomainToTickData, getCartesianLabelBounds, isAxisHorizontal } from '../util';

Expand All @@ -13,12 +13,15 @@ const getOneDimensionalLabelBounds = (
isHorizontal: boolean
): OneDimensionalBounds[] => {
const labelBoundsList = getCartesianLabelBounds(scale, domain, op);
return labelBoundsList.map(bounds => {
if (isHorizontal) {
return [bounds.x1, bounds.x2, bounds.width()];
}
return [bounds.y1, bounds.y2, bounds.height()];
});
return (
labelBoundsList &&
labelBoundsList.map(bounds => {
if (isHorizontal) {
return [bounds.x1, bounds.x2, bounds.width()];
}
return [bounds.y1, bounds.y2, bounds.height()];
})
);
};

/** 判断两个 bounds 是否有重叠情况 */
Expand Down Expand Up @@ -79,63 +82,71 @@ export const linearDiscreteTicks = (scale: BandScale, op: ICartesianTickDataOpt)
const rangeEnd = maxInArray(range);

if (domain.length <= rangeSize / fontSize) {
const incrementUnit = (rangeEnd - rangeStart) / domain.length;
const labelBoundsList = getOneDimensionalLabelBounds(scale, domain, op, isHorizontal);
const minBoundsLength = Math.min(...labelBoundsList.map(bounds => bounds[2]));

const stepResult = getStep(
domain,
labelBoundsList,
labelGap,
op.labelLastVisible,
Math.floor(minBoundsLength / incrementUnit), // 给step赋上合适的初值,有效改善外层循环次数
false
);

scaleTicks = (scale as BandScale).stepTicks(stepResult.step);
if (op.labelLastVisible) {
if (stepResult.delCount) {
scaleTicks = scaleTicks.slice(0, scaleTicks.length - stepResult.delCount);

if (labelBoundsList) {
const minBoundsLength = Math.min(...labelBoundsList.map(bounds => bounds[2]));

const incrementUnit = (rangeEnd - rangeStart) / domain.length;
const stepResult = getStep(
domain,
labelBoundsList,
labelGap,
op.labelLastVisible,
Math.floor(minBoundsLength / incrementUnit), // 给step赋上合适的初值,有效改善外层循环次数
false
);

scaleTicks = (scale as BandScale).stepTicks(stepResult.step);
if (op.labelLastVisible) {
if (stepResult.delCount) {
scaleTicks = scaleTicks.slice(0, scaleTicks.length - stepResult.delCount);
}
scaleTicks.push(domain[domain.length - 1]);
}
scaleTicks.push(domain[domain.length - 1]);
}
} else {
// only check first middle last, use the max size to sampling
const tempDomain = [domain[0], domain[Math.floor(domain.length / 2)], domain[domain.length - 1]];
const tempList = getOneDimensionalLabelBounds(scale, tempDomain, op, isHorizontal);
let maxBounds: >
tempList.forEach(current => {
if (!maxBounds) {
maxBounds = current;
return;
}
if (maxBounds[2] < current[2]) {
maxBounds = current;
}
});

const step =
rangeEnd - rangeStart - labelGap > 0
? Math.ceil((domain.length * (labelGap + maxBounds[2])) / (rangeEnd - rangeStart - labelGap))
: domain.length - 1;
if (tempList) {
let maxBounds: >
tempList.forEach(current => {
if (!maxBounds) {
maxBounds = current;
return;
}
if (maxBounds[2] < current[2]) {
maxBounds = current;
}
});

const step =
rangeEnd - rangeStart - labelGap > 0
? Math.ceil((domain.length * (labelGap + maxBounds[2])) / (rangeEnd - rangeStart - labelGap))
: domain.length - 1;

scaleTicks = (scale as BandScale).stepTicks(step);
scaleTicks = (scale as BandScale).stepTicks(step);

if (
op.labelLastVisible &&
(!scaleTicks.length || scaleTicks[scaleTicks.length - 1] !== domain[domain.length - 1])
) {
if (
scaleTicks.length &&
Math.abs(scale.scale(scaleTicks[scaleTicks.length - 1]) - scale.scale(domain[domain.length - 1])) <
maxBounds[2]
op.labelLastVisible &&
(!scaleTicks.length || scaleTicks[scaleTicks.length - 1] !== domain[domain.length - 1])
) {
scaleTicks = scaleTicks.slice(0, -1);
if (
scaleTicks.length &&
Math.abs(scale.scale(scaleTicks[scaleTicks.length - 1]) - scale.scale(domain[domain.length - 1])) <
maxBounds[2]
) {
scaleTicks = scaleTicks.slice(0, -1);
}
scaleTicks.push(domain[domain.length - 1]);
}
scaleTicks.push(domain[domain.length - 1]);
}
}
} else {
}

if (isNil(scaleTicks)) {
scaleTicks = scale.domain();
}

Expand Down
16 changes: 12 additions & 4 deletions packages/vrender-components/src/axis/tick-data/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IBaseScale } from '@visactor/vscale';
import { AABBBounds, degreeToRadian } from '@visactor/vutils 9E12 ';
import { AABBBounds, degreeToRadian, isPlainObject } from '@visactor/vutils';
import type { TextAlignType, TextBaselineType } from '@visactor/vrender-core';
import { initTextMeasure } from '../../util/text';
import type { ICartesianTickDataOpt, IOrientType, ITickData } from '../type';
Expand Down Expand Up @@ -74,9 +74,17 @@ export const getCartesianLabelBounds = (scale: IBaseScale, domain: any[], op: IC

const textMeasure = initTextMeasure(labelStyle);
const range = scale.range();
const labelBoundsList = domain.map((v: any, i: number) => {
let labelBoundsList: AABBBounds[] = [];

for (let i = 0; i < domain.length; i++) {
const v = domain[i];
const str = labelFormatter ? labelFormatter(v) : `${v}`;

if (isPlainObject(str)) {
labelBoundsList = undefined;
break;
}

// 估算文本宽高
const { width, height } = textMeasure.quickMeasure(str);
const textWidth = Math.max(width, MIN_TICK_GAP);
Expand Down Expand Up @@ -124,8 +132,8 @@ export const getCartesianLabelBounds = (scale: IBaseScale, domain: any[], op: IC
bounds.rotate(labelAngle, baseTextX, baseTextY);
}

return bounds;
});
labelBoundsList.push(bounds);
}

return labelBoundsList;
};
Expand Down
Loading
0