@funnest/charts-viz 0.2.1 → 0.4.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/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export { niceStep, niceDomain, ticks, logTicks, sampleIndices } from './ticks';
9
9
  export { linePath, areaPath, barPath, segments, type XY, type Curve } from './path';
10
10
  export { extent, maxLength, stack, stackedExtent, percentages, nearestIndex, seriesChange, type SeriesData, type StackedSeries, type PercentageSegment, } from './data';
11
11
  export { DEFAULT_PALETTE, seriesColor } from './palette';
12
+ export { arcs, arcPath, arcCentroid, treemap, type ArcSegment, type TreemapRect } from './shapes';
package/dist/index.js CHANGED
@@ -276,8 +276,138 @@ function seriesChange(data, end = data.length - 1) {
276
276
  // src/palette.ts
277
277
  var DEFAULT_PALETTE = ["#6C4DFF", "#F59E0B", "#14B8A6", "#EC4899", "#0EA5E9", "#84CC16", "#A16207", "#64748B"];
278
278
  var seriesColor = (index, palette = DEFAULT_PALETTE) => palette[(index % palette.length + palette.length) % palette.length];
279
+
280
+ // src/shapes.ts
281
+ var TAU = Math.PI * 2;
282
+ var f2 = (n) => Math.round(n * 100) / 100;
283
+ function arcs(items, padAngle = 0) {
284
+ const total = items.reduce((t, i) => t + Math.max(0, i.value), 0);
285
+ const visible = items.filter((i) => i.value > 0).length;
286
+ const pad = visible > 1 ? padAngle : 0;
287
+ let a = 0;
288
+ return items.map((i) => {
289
+ const fraction = total > 0 ? Math.max(0, i.value) / total : 0;
290
+ const span = fraction * TAU;
291
+ const p = Math.min(pad, span * 0.8);
292
+ const seg = { id: i.id, value: i.value, fraction, startAngle: a + p / 2, endAngle: a + span - p / 2 };
293
+ a += span;
294
+ return seg;
295
+ });
296
+ }
297
+ var pt = (cx, cy, r, a) => [cx + r * Math.sin(a), cy - r * Math.cos(a)];
298
+ function arcPath(cx, cy, innerRadius, outerRadius, startAngle, endAngle, cornerRadius = 0) {
299
+ const span = endAngle - startAngle;
300
+ if (span <= 0 || outerRadius <= 0) return "";
301
+ if (span >= TAU - 1e-6) {
302
+ const [x0, y0] = pt(cx, cy, outerRadius, 0);
303
+ const [x1, y1] = pt(cx, cy, outerRadius, Math.PI);
304
+ let d2 = `M${f2(x0)},${f2(y0)}A${f2(outerRadius)},${f2(outerRadius)} 0 1 1 ${f2(x1)},${f2(y1)}A${f2(outerRadius)},${f2(outerRadius)} 0 1 1 ${f2(x0)},${f2(y0)}Z`;
305
+ if (innerRadius > 0) {
306
+ const [i0x, i0y] = pt(cx, cy, innerRadius, 0);
307
+ const [i1x, i1y] = pt(cx, cy, innerRadius, Math.PI);
308
+ d2 += `M${f2(i0x)},${f2(i0y)}A${f2(innerRadius)},${f2(innerRadius)} 0 1 0 ${f2(i1x)},${f2(i1y)}A${f2(innerRadius)},${f2(innerRadius)} 0 1 0 ${f2(i0x)},${f2(i0y)}Z`;
309
+ }
310
+ return d2;
311
+ }
312
+ const large = span > Math.PI ? 1 : 0;
313
+ const thick = outerRadius - innerRadius;
314
+ const cr = Math.max(0, Math.min(cornerRadius, thick / 2, (innerRadius || outerRadius) * span / 2 - 0.01));
315
+ if (cr <= 0.01) {
316
+ const [ox0, oy0] = pt(cx, cy, outerRadius, startAngle);
317
+ const [ox1, oy1] = pt(cx, cy, outerRadius, endAngle);
318
+ let d2 = `M${f2(ox0)},${f2(oy0)}A${f2(outerRadius)},${f2(outerRadius)} 0 ${large} 1 ${f2(ox1)},${f2(oy1)}`;
319
+ if (innerRadius > 0) {
320
+ const [ix1, iy1] = pt(cx, cy, innerRadius, endAngle);
321
+ const [ix0, iy0] = pt(cx, cy, innerRadius, startAngle);
322
+ d2 += `L${f2(ix1)},${f2(iy1)}A${f2(innerRadius)},${f2(innerRadius)} 0 ${large} 0 ${f2(ix0)},${f2(iy0)}Z`;
323
+ } else d2 += `L${f2(cx)},${f2(cy)}Z`;
324
+ return d2;
325
+ }
326
+ const ao = cr / (outerRadius - cr);
327
+ const ai = innerRadius > 0 ? cr / (innerRadius + cr) : 0;
328
+ const [a1x, a1y] = pt(cx, cy, outerRadius - cr, startAngle);
329
+ const [b1x, b1y] = pt(cx, cy, outerRadius, startAngle + ao);
330
+ const [b2x, b2y] = pt(cx, cy, outerRadius, endAngle - ao);
331
+ const [a2x, a2y] = pt(cx, cy, outerRadius - cr, endAngle);
332
+ let d = `M${f2(a1x)},${f2(a1y)}A${f2(cr)},${f2(cr)} 0 0 1 ${f2(b1x)},${f2(b1y)}`;
333
+ d += `A${f2(outerRadius)},${f2(outerRadius)} 0 ${span - 2 * ao > Math.PI ? 1 : 0} 1 ${f2(b2x)},${f2(b2y)}`;
334
+ d += `A${f2(cr)},${f2(cr)} 0 0 1 ${f2(a2x)},${f2(a2y)}`;
335
+ if (innerRadius > 0) {
336
+ const [c2x, c2y] = pt(cx, cy, innerRadius + cr, endAngle);
337
+ const [d2x, d2y] = pt(cx, cy, innerRadius, endAngle - ai);
338
+ const [d1x, d1y] = pt(cx, cy, innerRadius, startAngle + ai);
339
+ const [c1x, c1y] = pt(cx, cy, innerRadius + cr, startAngle);
340
+ d += `L${f2(c2x)},${f2(c2y)}A${f2(cr)},${f2(cr)} 0 0 1 ${f2(d2x)},${f2(d2y)}`;
341
+ d += `A${f2(innerRadius)},${f2(innerRadius)} 0 ${span - 2 * ai > Math.PI ? 1 : 0} 0 ${f2(d1x)},${f2(d1y)}`;
342
+ d += `A${f2(cr)},${f2(cr)} 0 0 1 ${f2(c1x)},${f2(c1y)}Z`;
343
+ } else d += `L${f2(cx)},${f2(cy)}Z`;
344
+ return d;
345
+ }
346
+ function arcCentroid(cx, cy, r, startAngle, endAngle) {
347
+ const [x, y] = pt(cx, cy, r, (startAngle + endAngle) / 2);
348
+ return { x, y };
349
+ }
350
+ function treemap(items, width, height, gap = 0) {
351
+ const list = items.filter((i2) => i2.value > 0).sort((a, b) => b.value - a.value);
352
+ const total = list.reduce((t, i2) => t + i2.value, 0);
353
+ if (!total || width <= 0 || height <= 0) return [];
354
+ const scale = width * height / total;
355
+ const out = [];
356
+ let x = 0;
357
+ let y = 0;
358
+ let w = width;
359
+ let h = height;
360
+ let i = 0;
361
+ const worst = (row, side) => {
362
+ const s = row.reduce((t, v) => t + v, 0);
363
+ const max = Math.max(...row);
364
+ const min = Math.min(...row);
365
+ return Math.max(side * side * max / (s * s), s * s / (side * side * min));
366
+ };
367
+ while (i < list.length) {
368
+ const side = Math.min(w, h);
369
+ const row = [list[i].value * scale];
370
+ let j = i + 1;
371
+ while (j < list.length) {
372
+ const next = [...row, list[j].value * scale];
373
+ if (worst(next, side) > worst(row, side)) break;
374
+ row.push(list[j].value * scale);
375
+ j++;
376
+ }
377
+ const sum = row.reduce((t, v) => t + v, 0);
378
+ const thick = sum / side;
379
+ let off = 0;
380
+ row.forEach((area, k) => {
381
+ const len = area / thick;
382
+ const item = list[i + k];
383
+ const r = w >= h ? { x, y: y + off, width: thick, height: len } : { x: x + off, y, width: len, height: thick };
384
+ out.push({ id: item.id, value: item.value, ...r });
385
+ off += len;
386
+ });
387
+ if (w >= h) {
388
+ x += thick;
389
+ w -= thick;
390
+ } else {
391
+ y += thick;
392
+ h -= thick;
393
+ }
394
+ i = j;
395
+ }
396
+ if (gap <= 0) return out;
397
+ const g = gap / 2;
398
+ return out.map((r) => ({
399
+ ...r,
400
+ x: r.x + (r.x > 0.5 ? g : 0),
401
+ y: r.y + (r.y > 0.5 ? g : 0),
402
+ width: Math.max(0, r.width - (r.x > 0.5 ? g : 0) - (r.x + r.width < width - 0.5 ? g : 0)),
403
+ height: Math.max(0, r.height - (r.y > 0.5 ? g : 0) - (r.y + r.height < height - 0.5 ? g : 0))
404
+ }));
405
+ }
279
406
  export {
280
407
  DEFAULT_PALETTE,
408
+ arcCentroid,
409
+ arcPath,
410
+ arcs,
281
411
  areaPath,
282
412
  bandScale,
283
413
  barPath,
@@ -298,5 +428,6 @@ export {
298
428
  seriesColor,
299
429
  stack,
300
430
  stackedExtent,
301
- ticks
431
+ ticks,
432
+ treemap
302
433
  };
@@ -0,0 +1,45 @@
1
+ /** Pie/donut and treemap geometry. Pure math, no DOM. */
2
+ export interface ArcSegment {
3
+ id: string;
4
+ value: number;
5
+ /** Share of the total, 0–1. */
6
+ fraction: number;
7
+ /** Radians, clockwise from 12 o'clock. */
8
+ startAngle: number;
9
+ endAngle: number;
10
+ }
11
+ /**
12
+ * Split a full circle into segments by value (negatives count as 0).
13
+ * `padAngle` (radians) is the gap between neighbours; it is taken out of each segment.
14
+ */
15
+ export declare function arcs(items: readonly {
16
+ id: string;
17
+ value: number;
18
+ }[], padAngle?: number): ArcSegment[];
19
+ /**
20
+ * SVG path for a ring segment (a pie slice when `innerRadius` is 0).
21
+ * `cornerRadius` softens the four corners.
22
+ */
23
+ export declare function arcPath(cx: number, cy: number, innerRadius: number, outerRadius: number, startAngle: number, endAngle: number, cornerRadius?: number): string;
24
+ /** Point at the middle of a segment, `r` from the center (labels, hover offsets). */
25
+ export declare function arcCentroid(cx: number, cy: number, r: number, startAngle: number, endAngle: number): {
26
+ x: number;
27
+ y: number;
28
+ };
29
+ export interface TreemapRect {
30
+ id: string;
31
+ value: number;
32
+ x: number;
33
+ y: number;
34
+ width: number;
35
+ height: number;
36
+ }
37
+ /**
38
+ * Squarified treemap (Bruls, Huizing, van Wijk): rectangles with areas proportional to
39
+ * value and aspect ratios close to 1. Items are laid out largest first; non-positive
40
+ * values are dropped.
41
+ */
42
+ export declare function treemap(items: readonly {
43
+ id: string;
44
+ value: number;
45
+ }[], width: number, height: number, gap?: number): TreemapRect[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnest/charts-viz",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Platform-neutral chart math (scales, ticks, paths, scrubbing) shared by Funnest Charts web and React Native components",
5
5
  "keywords": [
6
6
  "chart",