@robr0/design-system 0.11.0 → 0.12.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.
Files changed (56) hide show
  1. package/README.md +4 -4
  2. package/charts.d.ts +1 -0
  3. package/charts.js +2 -0
  4. package/components/AppLayout/AppLayout.d.ts +7 -1
  5. package/components/AppLayout/AppLayout.js +2 -1
  6. package/components/AppSidebar/AppSidebar.css +104 -40
  7. package/components/AppSidebar/AppSidebar.d.ts +14 -1
  8. package/components/AppSidebar/AppSidebar.js +26 -17
  9. package/components/Badge/Badge.css +4 -5
  10. package/components/Chart/AreaChart.d.ts +5 -1
  11. package/components/Chart/AreaChart.js +5 -4
  12. package/components/Chart/BarChart.d.ts +3 -1
  13. package/components/Chart/BarChart.js +3 -3
  14. package/components/Chart/Chart.css +45 -0
  15. package/components/Chart/ComboChart.d.ts +40 -0
  16. package/components/Chart/ComboChart.js +142 -0
  17. package/components/Chart/LineChart.d.ts +3 -1
  18. package/components/Chart/LineChart.js +3 -3
  19. package/components/Chart/PieChart.d.ts +3 -1
  20. package/components/Chart/PieChart.js +3 -3
  21. package/components/Chart/RadarChart.d.ts +3 -1
  22. package/components/Chart/RadarChart.js +3 -3
  23. package/components/Chart/RadialChart.d.ts +7 -1
  24. package/components/Chart/RadialChart.js +51 -43
  25. package/components/Chart/ScatterChart.d.ts +3 -1
  26. package/components/Chart/ScatterChart.js +3 -3
  27. package/components/Chart/StackedBarChart.d.ts +3 -1
  28. package/components/Chart/StackedBarChart.js +3 -3
  29. package/components/Chart/Treemap.d.ts +3 -1
  30. package/components/Chart/Treemap.js +3 -3
  31. package/components/Chart/palette.js +1 -3
  32. package/components/FunnelChart/FunnelChart.css +41 -0
  33. package/components/FunnelChart/FunnelChart.d.ts +40 -0
  34. package/components/FunnelChart/FunnelChart.js +48 -0
  35. package/components/LegendTile/LegendTile.css +60 -0
  36. package/components/LegendTile/LegendTile.d.ts +30 -0
  37. package/components/LegendTile/LegendTile.js +21 -0
  38. package/components/Panel/Panel.css +26 -0
  39. package/components/Panel/Panel.d.ts +22 -0
  40. package/components/Panel/Panel.js +19 -0
  41. package/components/Stat/Stat.css +35 -2
  42. package/components/Stat/Stat.d.ts +3 -1
  43. package/components/Stat/Stat.js +7 -1
  44. package/components/registry.json +33 -0
  45. package/components/registry.json.d.ts +33 -0
  46. package/components/registry.json.js +1 -1
  47. package/index.d.ts +3 -0
  48. package/index.js +6 -0
  49. package/package.json +1 -1
  50. package/tokens/registry.json +4 -1
  51. package/tokens/registry.json.d.ts +4 -1
  52. package/tokens/registry.json.js +1 -1
  53. package/tokens/tokens-dark.css +3 -0
  54. package/tokens/tokens-light.css +11 -0
  55. package/tokens/tokens-primitives.css +2 -0
  56. package/tokens/tokens-typography.css +12 -5
@@ -0,0 +1,41 @@
1
+ /* ============================================
2
+ FUNNEL CHART COMPONENT
3
+ Side-by-side vertical bars stepping down with
4
+ each stage's share of the first. Geometry and
5
+ colour arrive per stage as inline custom
6
+ properties (--funnel-stage-size, a clamped
7
+ percentage number; --funnel-stage-color, a
8
+ chart-series token), so the rules here stay
9
+ token-only.
10
+ ============================================ */
11
+
12
+ /* Base */
13
+
14
+ .ds-funnel-chart {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: var(--gap-xxs);
18
+ }
19
+
20
+ /* Parts */
21
+
22
+ .ds-funnel-chart__stage {
23
+ flex: 1;
24
+ min-width: 0;
25
+ height: calc(var(--funnel-stage-size) * 1%);
26
+ border-radius: var(--radius-sm);
27
+ background-color: var(--funnel-stage-color);
28
+ display: grid;
29
+ place-items: center;
30
+ }
31
+
32
+ .ds-funnel-chart__pct {
33
+ background-color: var(--color-bg-container-primary);
34
+ color: var(--color-text-primary);
35
+ border-radius: var(--radius-full);
36
+ padding: var(--padding-xxxs) var(--padding-xs);
37
+ font-family: var(--font-caption-family);
38
+ font-size: var(--font-caption-size);
39
+ font-weight: var(--font-paragraph-sm-em-weight);
40
+ line-height: var(--font-caption-line-height);
41
+ }
@@ -0,0 +1,40 @@
1
+ import { default as React } from 'react';
2
+ /** One stage of the funnel, ordered widest first. */
3
+ export interface FunnelStage {
4
+ /** Stage name, read into the chart's accessible label. */
5
+ label: string;
6
+ /** Raw count for the stage — drives the bar's height as a share of the first stage. */
7
+ value: number;
8
+ /**
9
+ * Preformatted value ("96.4K") for the accessible label and any consumer
10
+ * legend. Falls back to the raw `value`, locale-formatted.
11
+ */
12
+ displayValue?: string;
13
+ }
14
+ /** Props owned by FunnelChart itself — everything else falls through to the root div. */
15
+ type FunnelChartOwnProps = {
16
+ /** Ordered stages, first stage widest. Each later stage's height is its share of the first. */
17
+ data: FunnelStage[];
18
+ /** Chart height in pixels. */
19
+ height?: number;
20
+ /**
21
+ * Floor percentage for a stage's height, so steep drop-offs stay readable.
22
+ * Shares are clamped to the range from this floor up to 100.
23
+ */
24
+ minStageShare?: number;
25
+ /** Additional CSS classes */
26
+ className?: string;
27
+ };
28
+ export interface FunnelChartProps extends FunnelChartOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof FunnelChartOwnProps | 'children'> {
29
+ }
30
+ /**
31
+ * FunnelChart — ordered stages as side-by-side vertical bars whose heights
32
+ * step down with each stage's share of the first, each carrying a centred
33
+ * percentage pill. Pure JSX and CSS computed from props: no recharts, no
34
+ * hooks, no browser APIs, so it deliberately omits 'use client' and renders
35
+ * from a Server Component. Degenerate data stays safe: an empty array
36
+ * renders an empty root, and a zero or negative first value draws every
37
+ * stage at the floor height.
38
+ */
39
+ export declare const FunnelChart: React.ForwardRefExoticComponent<FunnelChartProps & React.RefAttributes<HTMLDivElement>>;
40
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./FunnelChart.css";
4
+ const SERIES_COUNT = 7;
5
+ const FunnelChart = React.forwardRef(
6
+ ({ data, height = 190, minStageShare = 16, className = "", style, ...rest }, ref) => {
7
+ const baseClass = "ds-funnel-chart";
8
+ const classes = [baseClass, className].filter(Boolean).join(" ");
9
+ const firstValue = data.length > 0 ? data[0].value : 0;
10
+ const clampShare = (share) => Math.min(Math.max(share, minStageShare), 100);
11
+ const stages = data.map((stage, i) => ({
12
+ ...stage,
13
+ share: clampShare(firstValue > 0 ? stage.value / firstValue * 100 : 0),
14
+ color: `var(--color-chart-series-${i % SERIES_COUNT + 1})`
15
+ }));
16
+ const ariaLabel = stages.length > 0 ? `Funnel: ${stages.map((s) => `${s.label} ${s.displayValue ?? s.value.toLocaleString()}`).join(", ")}` : "Funnel, no data";
17
+ return /* @__PURE__ */ jsx(
18
+ "div",
19
+ {
20
+ ...rest,
21
+ ref,
22
+ className: classes,
23
+ style: { ...style, height },
24
+ role: "img",
25
+ "aria-label": ariaLabel,
26
+ children: stages.map((stage, i) => /* @__PURE__ */ jsx(
27
+ "div",
28
+ {
29
+ className: `${baseClass}__stage`,
30
+ style: {
31
+ "--funnel-stage-size": stage.share,
32
+ "--funnel-stage-color": stage.color
33
+ },
34
+ children: /* @__PURE__ */ jsxs("span", { className: `${baseClass}__pct`, children: [
35
+ Math.round(firstValue > 0 ? stage.value / firstValue * 100 : 0),
36
+ "%"
37
+ ] })
38
+ },
39
+ `${stage.label}-${i}`
40
+ ))
41
+ }
42
+ );
43
+ }
44
+ );
45
+ FunnelChart.displayName = "FunnelChart";
46
+ export {
47
+ FunnelChart
48
+ };
@@ -0,0 +1,60 @@
1
+ /* ============================================
2
+ LEGEND TILE COMPONENT
3
+ The labelled value tile under a chart: an
4
+ inset surface one step below its panel, a
5
+ caption label with an optional series dot,
6
+ and the value beneath. The dot's colour
7
+ arrives per instance via the
8
+ --legend-tile-swatch custom property, set
9
+ inline by the component, so this file stays
10
+ token-only.
11
+ ============================================ */
12
+
13
+ /* Base */
14
+
15
+ .ds-legend-tile {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: var(--gap-xxs);
19
+ min-width: 0;
20
+ background-color: var(--color-bg-page-primary);
21
+ border-radius: var(--radius-md);
22
+ padding: var(--padding-sm) var(--padding-sm-md);
23
+ }
24
+
25
+ /* Parts */
26
+
27
+ .ds-legend-tile__label {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: var(--gap-xs);
31
+ min-width: 0;
32
+ font-family: var(--font-caption-family);
33
+ font-size: var(--font-caption-size);
34
+ font-weight: var(--font-caption-weight);
35
+ line-height: var(--font-caption-line-height);
36
+ letter-spacing: var(--font-caption-letter-spacing);
37
+ color: var(--color-text-secondary);
38
+ white-space: nowrap;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ }
42
+
43
+ /* Fixed 8px series dot — same literal as the chart tooltip's dot in
44
+ Chart.css, which is the reference for this element. */
45
+ .ds-legend-tile__dot {
46
+ width: 8px;
47
+ height: 8px;
48
+ border-radius: var(--radius-full);
49
+ flex-shrink: 0;
50
+ background-color: var(--legend-tile-swatch, var(--color-text-secondary));
51
+ }
52
+
53
+ .ds-legend-tile__value {
54
+ font-family: var(--font-paragraph-sm-em-family);
55
+ font-size: var(--font-paragraph-sm-em-size);
56
+ font-weight: var(--font-title-body-weight);
57
+ line-height: var(--font-paragraph-sm-em-line-height);
58
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
59
+ color: var(--color-text-primary);
60
+ }
@@ -0,0 +1,30 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by LegendTile itself — everything else falls through to the root div. */
3
+ type LegendTileOwnProps = {
4
+ /** The series name shown above the value. Truncates with an ellipsis rather than wrapping. */
5
+ label: string;
6
+ /**
7
+ * The reading for this series. Numbers are formatted with `toLocaleString()`;
8
+ * pass a string when the value carries its own formatting or a unit.
9
+ */
10
+ value: string | number;
11
+ /**
12
+ * Any CSS colour for the series dot — consumers typically pass a chart
13
+ * palette token, e.g. `var(--color-chart-series-1)`. When omitted, no dot
14
+ * renders.
15
+ */
16
+ swatch?: string;
17
+ /** Additional CSS classes */
18
+ className?: string;
19
+ };
20
+ export interface LegendTileProps extends LegendTileOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof LegendTileOwnProps> {
21
+ }
22
+ /**
23
+ * LegendTile — the labelled value tile that sits under a chart and ties a
24
+ * series to its number. An inset tile one surface step below its panel, with
25
+ * an optional series dot matching the chart's palette. Pure JSX and CSS: no
26
+ * charting library, no hooks, so it renders from a Server Component and a row
27
+ * of them costs nothing under a dense dashboard.
28
+ */
29
+ export declare const LegendTile: React.ForwardRefExoticComponent<LegendTileProps & React.RefAttributes<HTMLDivElement>>;
30
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./LegendTile.css";
4
+ const LegendTile = React.forwardRef(
5
+ ({ label, value, swatch, className = "", style, ...rest }, ref) => {
6
+ const baseClass = "ds-legend-tile";
7
+ const classes = [baseClass, className].filter(Boolean).join(" ");
8
+ const mergedStyle = swatch ? { ...style, "--legend-tile-swatch": swatch } : style;
9
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, style: mergedStyle, children: [
10
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__label`, children: [
11
+ swatch && /* @__PURE__ */ jsx("span", { className: `${baseClass}__dot`, "aria-hidden": "true" }),
12
+ label
13
+ ] }),
14
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: typeof value === "number" ? value.toLocaleString() : value })
15
+ ] });
16
+ }
17
+ );
18
+ LegendTile.displayName = "LegendTile";
19
+ export {
20
+ LegendTile
21
+ };
@@ -0,0 +1,26 @@
1
+ /* ============================================
2
+ PANEL COMPONENT
3
+ The plain dashboard surface
4
+ ============================================ */
5
+
6
+ .ds-panel {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--gap-md);
10
+ min-width: 0;
11
+ background-color: var(--color-bg-container-primary);
12
+ border-radius: var(--radius-xl);
13
+ padding: var(--padding-lg);
14
+ }
15
+
16
+ /* ============================================
17
+ PADDING VARIANTS
18
+ ============================================ */
19
+
20
+ .ds-panel--compact {
21
+ padding: var(--padding-md);
22
+ }
23
+
24
+ .ds-panel--flush {
25
+ padding: 0;
26
+ }
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Panel itself; everything else falls through to the <div>. */
3
+ type PanelOwnProps = {
4
+ /** Interior padding: 'default' uses --padding-lg, 'compact' uses --padding-md, 'none' removes it */
5
+ padding?: 'default' | 'compact' | 'none';
6
+ /** Additional CSS classes */
7
+ className?: string;
8
+ /** Panel content */
9
+ children?: React.ReactNode;
10
+ };
11
+ export interface PanelProps extends PanelOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof PanelOwnProps> {
12
+ }
13
+ /**
14
+ * Panel component: the plain dashboard surface.
15
+ * A rounded container on the primary container background with no border and
16
+ * no shadow: just the surface, ready for whatever a dashboard region holds.
17
+ * Content stacks vertically with a medium gap.
18
+ *
19
+ * Forwards a ref to the `<div>` element and spreads unrecognised props onto it.
20
+ */
21
+ export declare const Panel: React.ForwardRefExoticComponent<PanelProps & React.RefAttributes<HTMLDivElement>>;
22
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./Panel.css";
4
+ const Panel = React.forwardRef(
5
+ ({ padding = "default", className = "", children, ...rest }, ref) => {
6
+ const baseClass = "ds-panel";
7
+ const classes = [
8
+ baseClass,
9
+ padding === "compact" ? `${baseClass}--compact` : "",
10
+ padding === "none" ? `${baseClass}--flush` : "",
11
+ className
12
+ ].filter(Boolean).join(" ");
13
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, children });
14
+ }
15
+ );
16
+ Panel.displayName = "Panel";
17
+ export {
18
+ Panel
19
+ };
@@ -65,18 +65,51 @@
65
65
  line-height: 1;
66
66
  }
67
67
 
68
+ /* The trend tokens are theme-split for contrast: deep status inks on
69
+ light surfaces, the mint/coral accents on dark ones. */
68
70
  .ds-stat__delta--up {
69
- color: var(--color-core-accent-mint);
71
+ color: var(--color-trend-up);
70
72
  }
71
73
 
72
74
  .ds-stat__delta--down {
73
- color: var(--color-core-accent-coral);
75
+ color: var(--color-trend-down);
74
76
  }
75
77
 
76
78
  .ds-stat__delta--neutral {
77
79
  color: var(--color-text-tertiary);
78
80
  }
79
81
 
82
+ /* ============================================
83
+ INLINE DELTA — delta to the right of the
84
+ value, bottom-aligned to its baseline
85
+ ============================================ */
86
+
87
+ .ds-stat--delta-inline {
88
+ display: grid;
89
+ grid-template-columns: auto 1fr;
90
+ grid-template-areas:
91
+ 'value delta'
92
+ 'label label';
93
+ column-gap: var(--gap-sm);
94
+ row-gap: var(--gap-xxs);
95
+ }
96
+
97
+ .ds-stat--delta-inline .ds-stat__value {
98
+ grid-area: value;
99
+ }
100
+
101
+ .ds-stat--delta-inline .ds-stat__label {
102
+ grid-area: label;
103
+ }
104
+
105
+ .ds-stat--delta-inline .ds-stat__delta {
106
+ grid-area: delta;
107
+ align-self: end;
108
+ justify-self: start;
109
+ /* Optical baseline: compensates the display value's half-leading. */
110
+ margin-bottom: var(--gap-xs);
111
+ }
112
+
80
113
  /* Icons take the colour of the text around them. Explicit, not just
81
114
  inherited by default: a consumer stylesheet that sets a colour on
82
115
  .material-symbols-rounded itself (a common global default) would
@@ -7,6 +7,8 @@ export interface StatProps {
7
7
  delta?: string;
8
8
  /** Direction of the delta — colours it and adds an arrow */
9
9
  trend?: 'up' | 'down' | 'neutral';
10
+ /** Where the delta sits: stacked below the label, or inline to the right of the value, bottom-aligned */
11
+ deltaPlacement?: 'stacked' | 'inline';
10
12
  /** Stat size */
11
13
  size?: 'default' | 'large';
12
14
  /** Additional CSS classes */
@@ -16,4 +18,4 @@ export interface StatProps {
16
18
  * Stat component — a single headline metric with a label and an
17
19
  * optional trend delta. Compose several in a row for a metrics band.
18
20
  */
19
- export declare const Stat: ({ value, label, delta, trend, size, className, }: StatProps) => import("react").JSX.Element;
21
+ export declare const Stat: ({ value, label, delta, trend, deltaPlacement, size, className, }: StatProps) => import("react").JSX.Element;
@@ -11,11 +11,17 @@ const Stat = ({
11
11
  label,
12
12
  delta,
13
13
  trend = "neutral",
14
+ deltaPlacement = "stacked",
14
15
  size = "default",
15
16
  className = ""
16
17
  }) => {
17
18
  const baseClass = "ds-stat";
18
- const classes = [baseClass, `${baseClass}--${size}`, className].filter(Boolean).join(" ");
19
+ const classes = [
20
+ baseClass,
21
+ `${baseClass}--${size}`,
22
+ deltaPlacement === "inline" ? `${baseClass}--delta-inline` : "",
23
+ className
24
+ ].filter(Boolean).join(" ");
19
25
  return /* @__PURE__ */ jsxs("div", { className: classes, children: [
20
26
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: value }),
21
27
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label }),
@@ -292,6 +292,15 @@
292
292
  "category": "forms",
293
293
  "client": true
294
294
  },
295
+ {
296
+ "name": "ComboChart",
297
+ "label": "Combo chart",
298
+ "slug": "combo-chart",
299
+ "description": "Bar and line series in one chart, with an optional second y-axis for pairs in different units, like spend and ROAS.",
300
+ "category": "charts",
301
+ "client": false,
302
+ "folder": "Chart"
303
+ },
295
304
  {
296
305
  "name": "Combobox",
297
306
  "label": "Combobox",
@@ -460,6 +469,14 @@
460
469
  "category": "forms",
461
470
  "client": true
462
471
  },
472
+ {
473
+ "name": "FunnelChart",
474
+ "label": "Funnel chart",
475
+ "slug": "funnel-chart",
476
+ "description": "Ordered funnel stages as stepped bars with conversion percentages, each sized by its share of the first stage.",
477
+ "category": "charts",
478
+ "client": false
479
+ },
463
480
  {
464
481
  "name": "Globe",
465
482
  "label": "Globe",
@@ -500,6 +517,14 @@
500
517
  "category": "data-display",
501
518
  "client": false
502
519
  },
520
+ {
521
+ "name": "LegendTile",
522
+ "label": "Legend tile",
523
+ "slug": "legend-tile",
524
+ "description": "The labelled value tile under a chart: a series dot, the series name, and its reading, on an inset fill.",
525
+ "category": "charts",
526
+ "client": false
527
+ },
503
528
  {
504
529
  "name": "LineChart",
505
530
  "label": "Line chart",
@@ -597,6 +622,14 @@
597
622
  "category": "navigation",
598
623
  "client": true
599
624
  },
625
+ {
626
+ "name": "Panel",
627
+ "label": "Panel",
628
+ "slug": "panel",
629
+ "description": "The plain dashboard surface: a rounded container with no border or shadow, just padding and a gap.",
630
+ "category": "layout",
631
+ "client": false
632
+ },
600
633
  {
601
634
  "name": "PieChart",
602
635
  "label": "Pie chart",
@@ -292,6 +292,15 @@ declare const _default: {
292
292
  "category": "forms",
293
293
  "client": true
294
294
  },
295
+ {
296
+ "name": "ComboChart",
297
+ "label": "Combo chart",
298
+ "slug": "combo-chart",
299
+ "description": "Bar and line series in one chart, with an optional second y-axis for pairs in different units, like spend and ROAS.",
300
+ "category": "charts",
301
+ "client": false,
302
+ "folder": "Chart"
303
+ },
295
304
  {
296
305
  "name": "Combobox",
297
306
  "label": "Combobox",
@@ -460,6 +469,14 @@ declare const _default: {
460
469
  "category": "forms",
461
470
  "client": true
462
471
  },
472
+ {
473
+ "name": "FunnelChart",
474
+ "label": "Funnel chart",
475
+ "slug": "funnel-chart",
476
+ "description": "Ordered funnel stages as stepped bars with conversion percentages, each sized by its share of the first stage.",
477
+ "category": "charts",
478
+ "client": false
479
+ },
463
480
  {
464
481
  "name": "Globe",
465
482
  "label": "Globe",
@@ -500,6 +517,14 @@ declare const _default: {
500
517
  "category": "data-display",
501
518
  "client": false
502
519
  },
520
+ {
521
+ "name": "LegendTile",
522
+ "label": "Legend tile",
523
+ "slug": "legend-tile",
524
+ "description": "The labelled value tile under a chart: a series dot, the series name, and its reading, on an inset fill.",
525
+ "category": "charts",
526
+ "client": false
527
+ },
503
528
  {
504
529
  "name": "LineChart",
505
530
  "label": "Line chart",
@@ -597,6 +622,14 @@ declare const _default: {
597
622
  "category": "navigation",
598
623
  "client": true
599
624
  },
625
+ {
626
+ "name": "Panel",
627
+ "label": "Panel",
628
+ "slug": "panel",
629
+ "description": "The plain dashboard surface: a rounded container with no border or shadow, just padding and a gap.",
630
+ "category": "layout",
631
+ "client": false
632
+ },
600
633
  {
601
634
  "name": "PieChart",
602
635
  "label": "Pie chart",