UNPKG

@tanstack/charts

Version:

A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.

299 lines (298 loc) 8.46 kB
import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import * as React from "react"; import { Circle, ClipPath, Defs, G, Line, LinearGradient, Path, Rect, Stop, Svg, Text } from "react-native-svg"; function NativeChartSceneNodes({ scene, nodes, color, focusFill, fontScale, idPrefix, resolvePaint }) { const gradientIds = React.useMemo( () => new Set(scene.gradients.map((gradient) => gradient.id)), [scene.gradients] ); const paint = React.useCallback( (value) => resolveScenePaint( value, gradientIds, idPrefix, resolvePaint, color, focusFill ), [color, focusFill, gradientIds, idPrefix, resolvePaint] ); return /* @__PURE__ */ jsx(Fragment, { children: nodes.map( (node) => renderSceneNode(node, idPrefix, paint, positiveFinite(fontScale, 1)) ) }); } const NativeChartScene = React.memo(function NativeChartScene2({ scene, color, fontFamily, fontStyle, fontStretch, letterSpacing, fontScale, idPrefix, resolvePaint, focusFill, focusPresentation }) { const gradientIds = React.useMemo( () => new Set(scene.gradients.map((gradient) => gradient.id)), [scene.gradients] ); const paint = React.useCallback( (value) => resolveScenePaint( value, gradientIds, idPrefix, resolvePaint, color, focusFill ), [color, focusFill, gradientIds, idPrefix, resolvePaint] ); return /* @__PURE__ */ jsxs( Svg, { width: "100%", height: "100%", viewBox: `0 0 ${scene.width} ${scene.height}`, color, fontFamily, fontStyle, fontStretch, letterSpacing: letterSpacing === void 0 ? void 0 : letterSpacing * positiveFinite(fontScale, 1), pointerEvents: "none", accessible: false, children: [ scene.gradients.length ? /* @__PURE__ */ jsx(Defs, { children: scene.gradients.map((gradient) => /* @__PURE__ */ jsx( LinearGradient, { id: scopedId(idPrefix, gradient.id), x1: percent(gradient.x1 ?? 0), y1: percent(gradient.y1 ?? 1), x2: percent(gradient.x2 ?? 0), y2: percent(gradient.y2 ?? 0), children: gradient.stops.map((stop, index) => /* @__PURE__ */ jsx( Stop, { offset: percent(stop.offset), stopColor: paint(stop.color), stopOpacity: stop.opacity }, `${gradient.id}:${index}` )) }, gradient.id )) }) : null, scene.theme.background === "transparent" ? null : /* @__PURE__ */ jsx( Rect, { x: 0, y: 0, width: scene.width, height: scene.height, fill: paint(scene.theme.background) } ), focusPresentation?.under.map( (node) => renderSceneNode(node, idPrefix, paint, positiveFinite(fontScale, 1)) ), scene.nodes.map( (node) => renderSceneNode(node, idPrefix, paint, positiveFinite(fontScale, 1)) ), focusPresentation?.over.map( (node) => renderSceneNode(node, idPrefix, paint, positiveFinite(fontScale, 1)) ) ] } ); }); function renderSceneNode(node, idPrefix, paint, fontScale) { if (node.kind === "group" && node.focus) return null; const style = nativeSceneStyle(node.style, paint); switch (node.kind) { case "group": return renderGroup(node, idPrefix, paint, style, fontScale); case "rule": return /* @__PURE__ */ jsx( Line, { ...style, x1: node.x1, y1: node.y1, x2: node.x2, y2: node.y2 }, node.key ); case "polyline": return /* @__PURE__ */ jsx( Path, { ...style, d: node.path ?? pointsPath(node.points, false), vectorEffect: "non-scaling-stroke" }, node.key ); case "area": return /* @__PURE__ */ jsx( Path, { ...style, d: node.polygons !== void 0 ? polygonsPath(node.polygons) : node.path ?? pointsPath(node.points, true), fillRule: node.polygons === void 0 ? void 0 : "evenodd", vectorEffect: "non-scaling-stroke" }, node.key ); case "dot": return /* @__PURE__ */ jsx( Circle, { ...style, cx: node.x, cy: node.y, r: node.radius }, node.key ); case "rect": return /* @__PURE__ */ jsx( Rect, { ...style, x: node.x, y: node.y, width: node.width, height: node.height, rx: node.radius }, node.key ); case "label": return /* @__PURE__ */ jsx( Text, { ...style, x: node.x, y: node.y, textAnchor: node.anchor, alignmentBaseline: node.baseline === "auto" ? "baseline" : node.baseline, transform: node.rotate === void 0 ? void 0 : `rotate(${node.rotate} ${node.x} ${node.y})`, fontSize: (node.fontSize ?? 16) * fontScale, fontWeight: node.fontWeight, children: node.text }, node.key ); } } function renderGroup(node, idPrefix, paint, style, fontScale) { const clipId = node.clip ? scopedId(idPrefix, `clip-${stableId(node.key)}`) : void 0; const transform = node.translateX === void 0 && node.translateY === void 0 ? void 0 : `translate(${node.translateX ?? 0} ${node.translateY ?? 0})`; return /* @__PURE__ */ jsxs( G, { ...style, transform, clipPath: clipId ? `url(#${clipId})` : void 0, children: [ node.clip && clipId ? /* @__PURE__ */ jsx(Defs, { children: /* @__PURE__ */ jsx(ClipPath, { id: clipId, children: /* @__PURE__ */ jsx( Rect, { x: node.clip.x, y: node.clip.y, width: node.clip.width, height: node.clip.height } ) }) }) : null, node.children.map( (child) => renderSceneNode(child, idPrefix, paint, fontScale) ) ] }, node.key ); } function nativeSceneStyle(style, paint) { if (!style) return {}; return { fill: style.fill === void 0 ? void 0 : paint(style.fill), fillOpacity: style.fillOpacity, stroke: style.stroke === void 0 ? void 0 : paint(style.stroke), strokeOpacity: style.strokeOpacity, strokeWidth: style.strokeWidth, opacity: style.opacity, strokeLinecap: style.lineCap, strokeLinejoin: resolveNativeLineJoin(style.lineJoin), strokeDasharray: style.strokeDasharray }; } function resolveNativeLineJoin(lineJoin) { if (lineJoin === "arcs") return "round"; if (lineJoin === "miter-clip") return "miter"; return lineJoin; } function resolveScenePaint(value, gradientIds, idPrefix, resolvePaint, color, canvas) { const match = /^url\(#([\s\S]*)\)$/.exec(value); const id = match?.[1]; if (id !== void 0 && gradientIds.has(id)) { return `url(#${scopedId(idPrefix, id)})`; } return resolvePaint(value, { color, canvas }); } function pointsPath(points, close) { return `${points.map(([x, y], index) => `${index === 0 ? "M" : "L"}${x},${y}`).join("")}${close ? "Z" : ""}`; } function polygonsPath(polygons) { return polygons.flatMap((polygon) => polygon).filter((ring) => ring.length > 0).map((ring) => pointsPath(ring, true)).join(""); } function scopedId(prefix, id) { const encodedId = encodeResourceId(id); return prefix ? `${prefix}-${encodedId}` : encodedId; } function encodeResourceId(value) { if (!value) return "_"; return Array.from( value, (character) => /^[a-zA-Z0-9-]$/.test(character) ? character : `_x${character.codePointAt(0).toString(16)}_` ).join(""); } function stableId(value) { let hash = 2166136261; for (let index = 0; index < value.length; index += 1) { hash = Math.imul(hash ^ value.charCodeAt(index), 16777619); } return (hash >>> 0).toString(36); } function percent(value) { return `${Math.max(0, Math.min(1, value)) * 100}%`; } function positiveFinite(value, fallback) { return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback; } export { NativeChartScene, NativeChartSceneNodes, resolveNativeLineJoin };