@spatika/react 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -3
- package/README.md +1 -1
- package/dist/composites/EventCalendar.js +3 -3
- package/dist/composites/GlassCard.d.ts +1 -1
- package/dist/composites/RichTextEditor.d.ts +1 -4
- package/dist/composites/RichTextEditor.d.ts.map +1 -1
- package/dist/composites/RichTextEditor.js +12 -19
- package/dist/composites/SurfaceCard.d.ts +1 -1
- package/dist/composites/Tag.d.ts +2 -2
- package/dist/index.d.ts +8 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -11
- package/dist/lib/breakpoints.d.ts +17 -0
- package/dist/lib/breakpoints.d.ts.map +1 -0
- package/dist/lib/breakpoints.js +23 -0
- package/dist/lib/create-theme.d.ts +162 -0
- package/dist/lib/create-theme.d.ts.map +1 -0
- package/dist/lib/create-theme.js +138 -0
- package/dist/lib/scheduler.js +2 -2
- package/dist/lib/themes.d.ts +17 -7
- package/dist/lib/themes.d.ts.map +1 -1
- package/dist/lib/themes.js +22 -8
- package/dist/lib/use-media-query.d.ts +9 -0
- package/dist/lib/use-media-query.d.ts.map +1 -1
- package/dist/lib/use-media-query.js +28 -1
- package/dist/primitives/Alert.d.ts +1 -1
- package/dist/primitives/Badge.d.ts +2 -2
- package/dist/primitives/Button.d.ts +2 -2
- package/dist/primitives/Container.d.ts +2 -2
- package/dist/primitives/Fab.d.ts +4 -4
- package/dist/primitives/Grid.d.ts +2 -1
- package/dist/primitives/Grid.d.ts.map +1 -1
- package/dist/primitives/Grid.js +10 -2
- package/dist/primitives/IconButton.d.ts +2 -2
- package/dist/primitives/ToggleButton.d.ts +1 -1
- package/dist/theme/SpatikaThemeProvider.d.ts +4 -5
- package/dist/theme/SpatikaThemeProvider.d.ts.map +1 -1
- package/dist/theme/SpatikaThemeProvider.js +28 -13
- package/package.json +4 -2
- package/skills/spatika-ui/SKILL.md +25 -6
- package/src/composites/EventCalendar.test.tsx +293 -0
- package/src/composites/EventCalendar.tsx +3 -3
- package/src/composites/RichTextEditor.test.tsx +51 -0
- package/src/composites/RichTextEditor.tsx +20 -84
- package/src/composites/chart-ui.test.tsx +21 -0
- package/src/composites/scheduler.test.tsx +0 -258
- package/src/index.test.ts +6 -0
- package/src/index.ts +138 -72
- package/src/lib/breakpoints.test.ts +29 -0
- package/src/lib/breakpoints.ts +28 -0
- package/src/lib/create-theme.test.ts +79 -0
- package/src/lib/create-theme.ts +249 -0
- package/src/lib/scheduler.test.ts +10 -0
- package/src/lib/scheduler.ts +2 -2
- package/src/lib/themes.test.ts +17 -0
- package/src/lib/themes.ts +43 -12
- package/src/lib/use-media-query.test.tsx +80 -0
- package/src/lib/use-media-query.ts +35 -1
- package/src/primitives/Grid.test.tsx +55 -0
- package/src/primitives/Grid.tsx +13 -3
- package/src/primitives/mui-more.test.tsx +17 -0
- package/src/theme/SpatikaThemeProvider.test.tsx +38 -0
- package/src/theme/SpatikaThemeProvider.tsx +43 -15
- package/src/composites/ChartDataGrid.test.tsx +0 -57
- package/src/composites/ChartDataGrid.tsx +0 -196
- package/src/composites/MapChart.test.tsx +0 -39
- package/src/composites/MapChart.tsx +0 -130
- package/src/composites/ScatterWebGL.test.tsx +0 -25
- package/src/composites/ScatterWebGL.tsx +0 -79
- package/src/composites/cartesian-charts.test.tsx +0 -136
- package/src/composites/cartesian-charts.tsx +0 -1355
- package/src/composites/chart-composition.test.tsx +0 -68
- package/src/composites/chart-composition.tsx +0 -1039
- package/src/composites/chart-interaction.ts +0 -136
- package/src/composites/chart-ui.tsx +0 -336
- package/src/composites/charts-3d.test.tsx +0 -25
- package/src/composites/charts-3d.tsx +0 -318
- package/src/composites/charts.test.tsx +0 -254
- package/src/composites/flow-charts.test.tsx +0 -98
- package/src/composites/flow-charts.tsx +0 -641
- package/src/composites/radial-charts.test.tsx +0 -89
- package/src/composites/radial-charts.tsx +0 -643
- package/src/lib/chart-export.test.ts +0 -20
- package/src/lib/chart-export.ts +0 -99
- package/src/lib/charts-platform.test.ts +0 -93
- package/src/lib/charts.test.ts +0 -132
- package/src/lib/charts.ts +0 -1021
- package/src/lib/geo.test.ts +0 -42
- package/src/lib/geo.ts +0 -138
- package/src/lib/iso.test.ts +0 -22
- package/src/lib/iso.ts +0 -102
- package/src/lib/webgl-scatter.test.ts +0 -36
- package/src/lib/webgl-scatter.ts +0 -230
package/src/lib/chart-export.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serialize a live chart SVG and download SVG / PNG.
|
|
3
|
-
* Computed CSS (theme variables) is baked in so the file looks the same outside the app.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
function bakeComputedStyles(live: SVGSVGElement, clone: SVGSVGElement) {
|
|
7
|
-
const liveNodes = [live, ...Array.from(live.querySelectorAll("*"))];
|
|
8
|
-
const cloneNodes = [clone, ...Array.from(clone.querySelectorAll("*"))];
|
|
9
|
-
for (let i = 0; i < liveNodes.length; i++) {
|
|
10
|
-
const source = liveNodes[i];
|
|
11
|
-
const target = cloneNodes[i] as SVGElement | undefined;
|
|
12
|
-
if (!source || !target || !(source instanceof Element)) continue;
|
|
13
|
-
const style = getComputedStyle(source);
|
|
14
|
-
if (style.fill && style.fill !== "none") target.setAttribute("fill", style.fill);
|
|
15
|
-
if (style.stroke && style.stroke !== "none") target.setAttribute("stroke", style.stroke);
|
|
16
|
-
if (style.strokeWidth) target.setAttribute("stroke-width", style.strokeWidth);
|
|
17
|
-
if (style.opacity && style.opacity !== "1") target.setAttribute("opacity", style.opacity);
|
|
18
|
-
if (style.fontSize) target.setAttribute("font-size", style.fontSize);
|
|
19
|
-
if (style.fontWeight) target.setAttribute("font-weight", style.fontWeight);
|
|
20
|
-
if (style.fontFamily) target.setAttribute("font-family", style.fontFamily);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function serializeChartSvg(svg: SVGSVGElement): string {
|
|
25
|
-
const clone = svg.cloneNode(true) as SVGSVGElement;
|
|
26
|
-
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
27
|
-
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
28
|
-
if (!clone.getAttribute("viewBox")) {
|
|
29
|
-
const width = svg.clientWidth || Number(svg.getAttribute("width")) || 320;
|
|
30
|
-
const height = svg.clientHeight || Number(svg.getAttribute("height")) || 240;
|
|
31
|
-
clone.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
32
|
-
}
|
|
33
|
-
bakeComputedStyles(svg, clone);
|
|
34
|
-
return `<?xml version="1.0" encoding="UTF-8"?>${new XMLSerializer().serializeToString(clone)}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function triggerDownload(blob: Blob, filename: string) {
|
|
38
|
-
const href = URL.createObjectURL(blob);
|
|
39
|
-
const link = document.createElement("a");
|
|
40
|
-
link.href = href;
|
|
41
|
-
link.download = filename;
|
|
42
|
-
link.rel = "noopener";
|
|
43
|
-
document.body.appendChild(link);
|
|
44
|
-
link.click();
|
|
45
|
-
link.remove();
|
|
46
|
-
window.setTimeout(() => URL.revokeObjectURL(href), 1_000);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function downloadChartSvg(svg: SVGSVGElement, filename = "chart.svg") {
|
|
50
|
-
const blob = new Blob([serializeChartSvg(svg)], { type: "image/svg+xml;charset=utf-8" });
|
|
51
|
-
triggerDownload(blob, filename);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function chartSvgToPngBlob(svg: SVGSVGElement): Promise<Blob> {
|
|
55
|
-
const xml = serializeChartSvg(svg);
|
|
56
|
-
const width = Math.max(1, svg.clientWidth || Number(svg.getAttribute("width")) || 320);
|
|
57
|
-
const height = Math.max(1, svg.clientHeight || Number(svg.getAttribute("height")) || 240);
|
|
58
|
-
const blob = new Blob([xml], { type: "image/svg+xml;charset=utf-8" });
|
|
59
|
-
const url = URL.createObjectURL(blob);
|
|
60
|
-
try {
|
|
61
|
-
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
62
|
-
const img = new Image();
|
|
63
|
-
img.onload = () => resolve(img);
|
|
64
|
-
img.onerror = () => reject(new Error("Failed to rasterize chart SVG"));
|
|
65
|
-
img.src = url;
|
|
66
|
-
});
|
|
67
|
-
const canvas = document.createElement("canvas");
|
|
68
|
-
canvas.width = Math.round(width * 2);
|
|
69
|
-
canvas.height = Math.round(height * 2);
|
|
70
|
-
const ctx = canvas.getContext("2d");
|
|
71
|
-
if (!ctx) throw new Error("Canvas is unavailable");
|
|
72
|
-
const surface = svg.parentElement;
|
|
73
|
-
ctx.fillStyle = surface ? getComputedStyle(surface).backgroundColor || "#ffffff" : "#ffffff";
|
|
74
|
-
if (ctx.fillStyle === "rgba(0, 0, 0, 0)" || ctx.fillStyle === "transparent") ctx.fillStyle = "#ffffff";
|
|
75
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
76
|
-
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
77
|
-
const png = await new Promise<Blob>((resolve, reject) => {
|
|
78
|
-
canvas.toBlob((next) => (next ? resolve(next) : reject(new Error("PNG export failed"))), "image/png");
|
|
79
|
-
});
|
|
80
|
-
return png;
|
|
81
|
-
} finally {
|
|
82
|
-
URL.revokeObjectURL(url);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function downloadChartPng(svg: SVGSVGElement, filename = "chart.png") {
|
|
87
|
-
const blob = await chartSvgToPngBlob(svg);
|
|
88
|
-
triggerDownload(blob, filename);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function exportChart(
|
|
92
|
-
svg: SVGSVGElement | null | undefined,
|
|
93
|
-
format: "svg" | "png",
|
|
94
|
-
filename?: string,
|
|
95
|
-
) {
|
|
96
|
-
if (!svg) return;
|
|
97
|
-
if (format === "svg") downloadChartSvg(svg, filename ?? "chart.svg");
|
|
98
|
-
else await downloadChartPng(svg, filename ?? "chart.png");
|
|
99
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { featureId, fitFeatures, geoPath, mercator } from "./geo";
|
|
3
|
-
import { ellipseSlicePath, isoBoxFaces, isoPoint } from "./iso";
|
|
4
|
-
import {
|
|
5
|
-
nearestScatterHit,
|
|
6
|
-
parseCssColor,
|
|
7
|
-
shouldUseWebGL,
|
|
8
|
-
WEBGL_AUTO_THRESHOLD,
|
|
9
|
-
} from "./webgl-scatter";
|
|
10
|
-
|
|
11
|
-
describe("geo", () => {
|
|
12
|
-
it("projects the equator to the mercator midpoint", () => {
|
|
13
|
-
const [x, y] = mercator([0, 0]);
|
|
14
|
-
expect(x).toBeCloseTo(0.5);
|
|
15
|
-
expect(y).toBeCloseTo(0.5);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("builds a closed polygon path fitted to a viewport", () => {
|
|
19
|
-
const features = [
|
|
20
|
-
{
|
|
21
|
-
type: "Feature" as const,
|
|
22
|
-
id: "box",
|
|
23
|
-
properties: { name: "Box" },
|
|
24
|
-
geometry: {
|
|
25
|
-
type: "Polygon" as const,
|
|
26
|
-
coordinates: [
|
|
27
|
-
[
|
|
28
|
-
[0, 0],
|
|
29
|
-
[10, 0],
|
|
30
|
-
[10, 10],
|
|
31
|
-
[0, 10],
|
|
32
|
-
[0, 0],
|
|
33
|
-
],
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
const project = fitFeatures(features, 200, 100, 10);
|
|
39
|
-
const d = geoPath(features[0]!.geometry, project);
|
|
40
|
-
expect(d.startsWith("M")).toBe(true);
|
|
41
|
-
expect(d.includes("Z")).toBe(true);
|
|
42
|
-
expect(featureId(features[0]!)).toBe("box");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("iso", () => {
|
|
47
|
-
it("projects isometric points with y up", () => {
|
|
48
|
-
const origin = isoPoint(0, 0, 0, 40, 80);
|
|
49
|
-
const raised = isoPoint(0, 10, 0, 40, 80);
|
|
50
|
-
expect(raised.y).toBeLessThan(origin.y);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("emits three faces for a box", () => {
|
|
54
|
-
const faces = isoBoxFaces(0, 0, 0, 10, 20, 8, 40, 80);
|
|
55
|
-
expect(faces.front.startsWith("M")).toBe(true);
|
|
56
|
-
expect(faces.top).not.toEqual(faces.front);
|
|
57
|
-
expect(faces.side).not.toEqual(faces.top);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("draws an elliptical pie slice", () => {
|
|
61
|
-
const d = ellipseSlicePath(50, 40, 30, 12, 0, 90);
|
|
62
|
-
expect(d.includes("A30 12")).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe("webgl scatter helpers", () => {
|
|
67
|
-
it("parses hex and rgb colors", () => {
|
|
68
|
-
expect(parseCssColor("#ff0000")).toEqual([1, 0, 0, 1]);
|
|
69
|
-
expect(parseCssColor("rgb(0, 128, 255)")).toEqual([0, 128 / 255, 1, 1]);
|
|
70
|
-
expect(parseCssColor("not-a-color")).toBeNull();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("selects webgl for auto clouds above the threshold", () => {
|
|
74
|
-
expect(shouldUseWebGL("svg", 10_000)).toBe(false);
|
|
75
|
-
expect(shouldUseWebGL("webgl", 3)).toBe(true);
|
|
76
|
-
expect(shouldUseWebGL("auto", WEBGL_AUTO_THRESHOLD)).toBe(true);
|
|
77
|
-
expect(shouldUseWebGL("auto", 10)).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("finds the nearest point within the hit radius", () => {
|
|
81
|
-
const hit = nearestScatterHit(
|
|
82
|
-
[
|
|
83
|
-
{ x: 10, y: 10, r: 4, seriesIndex: 0, pointIndex: 0 },
|
|
84
|
-
{ x: 40, y: 40, r: 4, seriesIndex: 1, pointIndex: 2 },
|
|
85
|
-
],
|
|
86
|
-
41,
|
|
87
|
-
39,
|
|
88
|
-
);
|
|
89
|
-
expect(hit?.seriesIndex).toBe(1);
|
|
90
|
-
expect(hit?.pointIndex).toBe(2);
|
|
91
|
-
expect(nearestScatterHit([{ x: 0, y: 0, r: 2, seriesIndex: 0, pointIndex: 0 }], 80, 80)).toBeNull();
|
|
92
|
-
});
|
|
93
|
-
});
|
package/src/lib/charts.test.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
arcPath,
|
|
4
|
-
axisDataFromDataset,
|
|
5
|
-
boxPlotStats,
|
|
6
|
-
chartColor,
|
|
7
|
-
extent,
|
|
8
|
-
formatChartNumber,
|
|
9
|
-
funnelLayout,
|
|
10
|
-
linePath,
|
|
11
|
-
niceTicks,
|
|
12
|
-
pieSlices,
|
|
13
|
-
seriesDataFromDataset,
|
|
14
|
-
stackSeries,
|
|
15
|
-
squarify,
|
|
16
|
-
waterfallBars,
|
|
17
|
-
wheelZoom,
|
|
18
|
-
} from "./charts";
|
|
19
|
-
|
|
20
|
-
describe("chart math", () => {
|
|
21
|
-
it("builds nice ticks across a domain", () => {
|
|
22
|
-
const ticks = niceTicks(0, 100, 5);
|
|
23
|
-
expect(ticks[0]).toBe(0);
|
|
24
|
-
expect(ticks[ticks.length - 1]).toBeGreaterThanOrEqual(100);
|
|
25
|
-
expect(ticks.length).toBeGreaterThan(2);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("stacks positive and negative series independently", () => {
|
|
29
|
-
const stacked = stackSeries([
|
|
30
|
-
[2, -1],
|
|
31
|
-
[3, -2],
|
|
32
|
-
]);
|
|
33
|
-
expect(stacked[0]![0]).toEqual({ start: 0, end: 2 });
|
|
34
|
-
expect(stacked[1]![0]).toEqual({ start: 2, end: 5 });
|
|
35
|
-
expect(stacked[0]![1]).toEqual({ start: 0, end: -1 });
|
|
36
|
-
expect(stacked[1]![1]).toEqual({ start: -1, end: -3 });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("lays out pie slices that sum to a full turn", () => {
|
|
40
|
-
const slices = pieSlices([1, 1, 2], {
|
|
41
|
-
cx: 50,
|
|
42
|
-
cy: 50,
|
|
43
|
-
innerRadius: 10,
|
|
44
|
-
outerRadius: 40,
|
|
45
|
-
});
|
|
46
|
-
expect(slices).toHaveLength(3);
|
|
47
|
-
expect(slices[2]!.percent).toBeCloseTo(0.5);
|
|
48
|
-
expect(slices.every((slice) => slice.path.length > 0)).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("draws linear and monotone line paths", () => {
|
|
52
|
-
const pts = [
|
|
53
|
-
{ x: 0, y: 10 },
|
|
54
|
-
{ x: 10, y: 0 },
|
|
55
|
-
{ x: 20, y: 8 },
|
|
56
|
-
];
|
|
57
|
-
expect(linePath(pts, "linear")).toContain("L");
|
|
58
|
-
expect(linePath(pts, "monotone")).toContain("C");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("computes box-plot fences and outliers", () => {
|
|
62
|
-
const stats = boxPlotStats([1, 2, 3, 4, 5, 100]);
|
|
63
|
-
expect(stats.median).toBe(3.5);
|
|
64
|
-
expect(stats.outliers).toEqual([100]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("builds waterfall running totals", () => {
|
|
68
|
-
const bars = waterfallBars([100, 20, -30, null]);
|
|
69
|
-
expect(bars[3]).toEqual({ index: 3, start: 0, end: 90, value: 90, total: true });
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("packs a treemap that fills the viewport", () => {
|
|
73
|
-
const rects = squarify(
|
|
74
|
-
[
|
|
75
|
-
{ label: "A", value: 6 },
|
|
76
|
-
{ label: "B", value: 4 },
|
|
77
|
-
],
|
|
78
|
-
0,
|
|
79
|
-
0,
|
|
80
|
-
100,
|
|
81
|
-
50,
|
|
82
|
-
);
|
|
83
|
-
const area = rects.reduce((sum, rect) => sum + rect.w * rect.h, 0);
|
|
84
|
-
expect(area).toBeCloseTo(5000, 0);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("lays out funnel rows from wide to narrow", () => {
|
|
88
|
-
const rows = funnelLayout(
|
|
89
|
-
[
|
|
90
|
-
{ label: "A", value: 10 },
|
|
91
|
-
{ label: "B", value: 5 },
|
|
92
|
-
],
|
|
93
|
-
100,
|
|
94
|
-
80,
|
|
95
|
-
);
|
|
96
|
-
expect(rows[0]!.topRight - rows[0]!.topLeft).toBeGreaterThan(rows[1]!.topRight - rows[1]!.topLeft);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("shares a max so comparison funnels stay on the same scale", () => {
|
|
100
|
-
const inner = funnelLayout([{ label: "A", value: 50 }], 100, 40, 6, 100);
|
|
101
|
-
const outer = funnelLayout([{ label: "A", value: 100 }], 100, 40, 6, 100);
|
|
102
|
-
expect(outer[0]!.topRight - outer[0]!.topLeft).toBe(100);
|
|
103
|
-
expect(inner[0]!.topRight - inner[0]!.topLeft).toBe(50);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("zooms a window around an anchor", () => {
|
|
107
|
-
const next = wheelZoom({ x: [0, 1] }, 0.5, 0.5);
|
|
108
|
-
expect(next.x[1] - next.x[0]).toBeCloseTo(0.5);
|
|
109
|
-
expect(next.x[0]).toBeGreaterThan(0);
|
|
110
|
-
expect(next.x[1]).toBeLessThan(1);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("reads series from a dataset", () => {
|
|
114
|
-
const dataset = [
|
|
115
|
-
{ month: "Jan", views: 10 },
|
|
116
|
-
{ month: "Feb", views: 20 },
|
|
117
|
-
];
|
|
118
|
-
expect(seriesDataFromDataset(dataset, "views")).toEqual([10, 20]);
|
|
119
|
-
expect(axisDataFromDataset(dataset, "month")).toEqual(["Jan", "Feb"]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("formats compact numbers and cycles chart colors", () => {
|
|
123
|
-
expect(formatChartNumber(1500)).toBe("1.5k");
|
|
124
|
-
expect(chartColor(0)).toBe("var(--chart-1)");
|
|
125
|
-
expect(chartColor(5)).toBe("var(--chart-1)");
|
|
126
|
-
expect(extent([3, 3])).not.toEqual([3, 3]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("splits a full-circle arc into two sweeps", () => {
|
|
130
|
-
expect(arcPath(0, 0, 0, 10, 0, 360).split("A").length).toBeGreaterThan(2);
|
|
131
|
-
});
|
|
132
|
-
});
|