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
46 changes: 46 additions & 0 deletions __tests__/unit/shape/interval-color.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import { parseRadius } from '../../../src/shape/interval/color';

describe('parseRadius', () => {
describe('radius as a number', () => {
it('should apply the same radius to all four corners', () => {
expect(parseRadius(10)).toEqual([10, 10, 10, 10]);
});

it('should return all zeros for radius 0', () => {
expect(parseRadius(0)).toEqual([0, 0, 0, 0]);
});
});

describe('radius as an array [TL, TR, BR, BL]', () => {
it('should expand 4-element radius array to corners', () => {
expect(parseRadius([10, 10, 4, 4])).toEqual([10, 10, 4, 4]);
});

it('should fallback missing array elements to 0', () => {
expect(parseRadius([10, 5])).toEqual([10, 5, 0, 0]);
expect(parseRadius([8])).toEqual([8, 0, 0, 0]);
expect(parseRadius([])).toEqual([0, 0, 0, 0]);
});

it('should fallback undefined elements to 0', () => {
expect(parseRadius([10, undefined, 4])).toEqual([10, 0, 4, 0]);
});
});

describe('radius as undefined', () => {
it('should fallback to all zeros when radius is undefined', () => {
expect(parseRadius(undefined)).toEqual([0, 0, 0, 0]);
});
});

describe('individual corner override with ?? pattern', () => {
it('should allow explicit corner radius to override parsed array values', () => {
const [tl, tr, br, bl] = parseRadius([10, 10, 4, 4]);
expect(20 ?? tl).toBe(20); // explicit override
expect(undefined ?? tr).toBe(10); // fallback to parsed value
expect(undefined ?? br).toBe(4);
expect(undefined ?? bl).toBe(4);
Comment on lines +40 to +43
});
});
});
58 changes: 49 additions & 9 deletions src/shape/interval/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ import { sub } from '../../utils/vector';
import { clamp } from '../../utils/number';
import { applyStyle, getArcObject, reorder, toOpacityKey } from '../utils';

/**
* Parse radius into a 4-element array [topLeft, topRight, bottomRight, bottomLeft].
* - If radius is undefined, defaults to 0 for all corners.
* - If radius is a number, all corners use the same value.
* - If radius is an array, map to corners with 0 as fallback for missing elements.
*/
export function parseRadius(
radius: number | number[] | undefined,
): [number, number, number, number] {
if (radius === undefined) {
return [0, 0, 0, 0];
}
if (Array.isArray(radius)) {
return [radius[0] ?? 0, radius[1] ?? 0, radius[2] ?? 0, radius[3] ?? 0];
}
return [radius, radius, radius, radius];
}
Comment thread
lxfu1 marked this conversation as resolved.

export type ColorOptions = {
colorAttribute: 'fill' | 'stroke';
/**
Expand Down Expand Up @@ -40,15 +58,26 @@ export function rect(
insetTop = inset,
insetRight = inset,
insetBottom = inset,
radiusBottomLeft = radius,
radiusBottomRight = radius,
radiusTopLeft = radius,
radiusTopRight = radius,
radiusTopLeft: _radiusTopLeft,
radiusTopRight: _radiusTopRight,
radiusBottomRight: _radiusBottomRight,
radiusBottomLeft: _radiusBottomLeft,
minWidth = -Infinity,
maxWidth = Infinity,
minHeight = -Infinity,
...rest
} = style;

const [
defaultTopLeft,
defaultTopRight,
defaultBottomRight,
defaultBottomLeft,
] = parseRadius(radius);
const radiusTopLeft = _radiusTopLeft ?? defaultTopLeft;
const radiusTopRight = _radiusTopRight ?? defaultTopRight;
const radiusBottomRight = _radiusBottomRight ?? defaultBottomRight;
const radiusBottomLeft = _radiusBottomLeft ?? defaultBottomLeft;
if (!isPolar(coordinate) && !isHelix(coordinate)) {
const tpShape = !!isTranspose(coordinate);

Expand Down Expand Up @@ -97,7 +126,7 @@ export function rect(
const center = coordinate.getCenter() as Vector2;
const arcObject = getArcObject(coordinate, points, [y, y1]);
const path = arc()
.cornerRadius(radius as number)
.cornerRadius(parseRadius(radius)[0])
.padAngle((inset * Math.PI) / 180);
Comment on lines 128 to 130

return select(document.createElement('path', {}))
Expand Down Expand Up @@ -138,10 +167,10 @@ export const Color: SC<ColorOptions> = (options, context) => {
const {
stroke,
radius = defaultRadius,
radiusTopLeft = radius,
radiusTopRight = radius,
radiusBottomRight = radius,
radiusBottomLeft = radius,
radiusTopLeft: _radiusTopLeft,
radiusTopRight: _radiusTopRight,
radiusBottomRight: _radiusBottomRight,
radiusBottomLeft: _radiusBottomLeft,
innerRadius = 0,
innerRadiusTopLeft = innerRadius,
innerRadiusTopRight = innerRadius,
Expand All @@ -160,6 +189,17 @@ export const Color: SC<ColorOptions> = (options, context) => {
} = style;
const { color = defaultColor, opacity } = value;

const [
defaultTopLeft,
defaultTopRight,
defaultBottomRight,
defaultBottomLeft,
] = parseRadius(radius);
const radiusTopLeft = _radiusTopLeft ?? defaultTopLeft;
const radiusTopRight = _radiusTopRight ?? defaultTopRight;
const radiusBottomRight = _radiusBottomRight ?? defaultBottomRight;
const radiusBottomLeft = _radiusBottomLeft ?? defaultBottomLeft;

// Extended style, which is not supported by native g shape,
// should apply at first.
const standardDirRadius = [
Expand Down
21 changes: 17 additions & 4 deletions src/shape/interval/funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ShapeComponent as SC, Vector2 } from '../../runtime';
import { select } from '../../utils/selection';
import { applyStyle, reorder } from '../utils';
import { createRoundedPath } from '../../utils/path';
import { parseRadius } from './color';

export type FunnelOptions = {
adjustPoints?: (
Expand Down Expand Up @@ -74,10 +75,10 @@ export const Funnel: SC<FunnelOptions> = (options, context) => {
const {
adjustPoints = getFunnelPoints,
radius,
radiusTopLeft = radius,
radiusTopRight = radius,
radiusBottomRight = radius,
radiusBottomLeft = radius,
radiusTopLeft: _radiusTopLeft,
radiusTopRight: _radiusTopRight,
radiusBottomRight: _radiusBottomRight,
radiusBottomLeft: _radiusBottomLeft,
innerRadius = 0,
innerRadiusTopLeft = innerRadius,
innerRadiusTopRight = innerRadius,
Expand All @@ -88,6 +89,18 @@ export const Funnel: SC<FunnelOptions> = (options, context) => {
...style
} = options;
const { coordinate, document } = context;

const [
defaultTopLeft,
defaultTopRight,
defaultBottomRight,
defaultBottomLeft,
] = parseRadius(radius);
const radiusTopLeft = _radiusTopLeft ?? defaultTopLeft;
const radiusTopRight = _radiusTopRight ?? defaultTopRight;
const radiusBottomRight = _radiusBottomRight ?? defaultBottomRight;
const radiusBottomLeft = _radiusBottomLeft ?? defaultBottomLeft;

return (points, value, defaults, point2d) => {
const { index } = value;
const { color: defaultColor, ...rest } = defaults;
Expand Down
Loading