├── .gitignore
├── LICENSE.md
├── README.md
├── area-charts
├── 1_AreaChart.tsx
├── 2_AreaChartFull.tsx
├── 3_AreaChartGradient.tsx
└── 4_AreaChartSemiFilled.tsx
├── bar-charts
├── 11_BarChartMultiVertical_DIV.tsx
├── 12_BarChartTripleFlagsHorizontal_DIV.tsx
├── 14_BarChartLine.tsx
├── 15_BarChartBenchmark.tsx
├── 1_BarChartHorizontal_DIV.tsx
├── 2_BarChartHorizontalLogo_DIV.tsx
├── 3_BarChartGradient_DIV.tsx
├── 4_BarChartBreakdown.tsx
├── 5_BarChartThinBreakdown.tsx
├── 6_BarChartThinHorizontal_DIV.tsx
├── 7_BarChartFlagsHorizontal.tsx
└── 9_BarChartVertical_DIV.tsx
├── helpers
└── ClientTooltip.tsx
├── line-charts
├── 1_LineChart.tsx
├── 2_LineChartCurved.tsx
├── 3_LineChartMultiple.tsx
├── 4_LineChartLabelsCurved.tsx
├── 5_LineChartStocksCurved.tsx
├── 6_LineChartStep.tsx
├── 7_LineChartPulse.tsx
└── 8_LineChartFull.tsx
├── other-charts
├── 4_BubbleChart_DIV.tsx
└── 5_FunnelChart.tsx
├── pie-charts
├── 1_PieChart.tsx
├── 2_PieChartStocks.tsx
├── 3_PieChartLabels.tsx
├── 4_DonutChart.tsx
├── 5_DonutChartCenterText.tsx
├── 6_DonutChartHalf.tsx
├── 7_DonutChartFillableHalf.tsx
└── 8_DonutChartFillable.tsx
├── radar-charts
├── 6_RadarChart.tsx
└── 8_RadarChartRounded.tsx
├── scatter-charts
├── 1_ScatterChart.tsx
├── 2_ScatterChartInteractive.tsx
├── 5_ScatterChartMulticlass.tsx
└── 6_ScatterChartStocks.tsx
└── treemap-charts
├── 1_TreemapChart_DIV.tsx
├── 2_TreemapChartImages_DIV.tsx
└── 3_TreemapChartGradient_DIV.tsx
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Filipe Sommer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rosen - A React Charting Library
2 |
3 | A lightweight and easy-to-use charting library built with D3.js and Tailwind. Just copy-paste the code and install the required dependencies to get started!
4 |
5 | ## Installation
6 |
7 | First, install D3.js by running:
8 |
9 | ```sh
10 | npm install d3
11 | ```
12 |
13 | If you're using TypeScript, install the D3 types as well:
14 |
15 | ```sh
16 | npm install --save-dev @types/d3
17 | ```
18 |
19 | ## Usage
20 |
21 | Simply copy and paste the provided charting code into your project and ensure that D3.js is installed.
22 |
23 | ## Features
24 |
25 | - Simple copy-paste integration
26 | - Uses D3.js for powerful and flexible charting
27 | - Works with JavaScript and TypeScript
28 |
29 | ## License
30 |
31 | MIT
32 |
--------------------------------------------------------------------------------
/area-charts/2_AreaChartFull.tsx:
--------------------------------------------------------------------------------
1 | import { scaleTime, scaleLinear, line as d3line, max, area as d3area, curveMonotoneX } from "d3";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | const sales = [
5 | { date: "2023-04-30", value: 4 },
6 | { date: "2023-05-01", value: 6 },
7 | { date: "2023-05-02", value: 8 },
8 | { date: "2023-05-03", value: 7 },
9 | { date: "2023-05-04", value: 10 },
10 | { date: "2023-05-05", value: 12 },
11 | { date: "2023-05-06", value: 10.5 },
12 | { date: "2023-05-07", value: 6 },
13 | { date: "2023-05-08", value: 8 },
14 | { date: "2023-05-09", value: 7.5 },
15 | { date: "2023-05-10", value: 6 },
16 | { date: "2023-05-11", value: 8 },
17 | { date: "2023-05-12", value: 9 },
18 | { date: "2023-05-13", value: 10 },
19 | { date: "2023-05-14", value: 17 },
20 | { date: "2023-05-15", value: 14 },
21 | { date: "2023-05-16", value: 15 },
22 | { date: "2023-05-17", value: 20 },
23 | { date: "2023-05-18", value: 18 },
24 | { date: "2023-05-19", value: 16 },
25 | { date: "2023-05-20", value: 15 },
26 | { date: "2023-05-21", value: 16 },
27 | { date: "2023-05-22", value: 13 },
28 | { date: "2023-05-23", value: 11 },
29 | { date: "2023-05-24", value: 11 },
30 | { date: "2023-05-25", value: 13 },
31 | { date: "2023-05-26", value: 12 },
32 | { date: "2023-05-27", value: 9 },
33 | { date: "2023-05-28", value: 8 },
34 | { date: "2023-05-29", value: 10 },
35 | { date: "2023-05-30", value: 11 },
36 | { date: "2023-05-31", value: 8 },
37 | { date: "2023-06-01", value: 9 },
38 | { date: "2023-06-02", value: 10 },
39 | { date: "2023-06-03", value: 12 },
40 | { date: "2023-06-04", value: 13 },
41 | { date: "2023-06-05", value: 15 },
42 | { date: "2023-06-06", value: 13.5 },
43 | { date: "2023-06-07", value: 13 },
44 | { date: "2023-06-08", value: 13 },
45 | { date: "2023-06-09", value: 14 },
46 | { date: "2023-06-10", value: 13 },
47 | { date: "2023-06-11", value: 12.5 },
48 | ];
49 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
50 |
51 | export function Live2_AreaChartFull() {
52 | let xScale = scaleTime()
53 | .domain([data[0].date, data[data.length - 1].date])
54 | .range([0, 100]);
55 |
56 | let yScale = scaleLinear()
57 | .domain([0, max(data.map((d) => d.value)) ?? 0])
58 | .range([100, 0]);
59 |
60 | let line = d3line<(typeof data)[number]>()
61 | .x((d) => xScale(d.date))
62 | .y((d) => yScale(d.value))
63 | .curve(curveMonotoneX);
64 |
65 | // Area generator
66 | let area = d3area<(typeof data)[number]>()
67 | .x((d) => xScale(d.date))
68 | .y0(yScale(0))
69 | .y1((d) => yScale(d.value))
70 | .curve(curveMonotoneX);
71 |
72 | let areaPath = area(data) ?? undefined;
73 |
74 | let d = line(data);
75 |
76 | if (!d) {
77 | return null;
78 | }
79 |
80 | return (
81 |
82 |
88 | {/* Chart area */}
89 |
156 |
157 | {/* X axis */}
158 | {data.map((day, i) => {
159 | // show 1 every x labels
160 | if (i % 6 !== 0 || i === 0 || i >= data.length - 3) return;
161 | return (
162 |
170 | {day.date.toLocaleDateString("en-US", {
171 | month: "short",
172 | day: "numeric",
173 | })}
174 |
175 | );
176 | })}
177 |
178 | {/* Y axis */}
179 | {yScale
180 | .ticks(8)
181 | .map(yScale.tickFormat(8, "d"))
182 | .map((value, i) => {
183 | if (i < 1) return;
184 | return (
185 |
193 | {value}
194 |
195 | );
196 | })}
197 |
198 | );
199 | }
200 |
--------------------------------------------------------------------------------
/area-charts/3_AreaChartGradient.tsx:
--------------------------------------------------------------------------------
1 | import { scaleTime, scaleLinear, line as d3line, max, area as d3area, curveMonotoneX } from "d3";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | const sales = [
5 | { date: "2023-04-30", value: 4 },
6 | { date: "2023-05-01", value: 6 },
7 | { date: "2023-05-02", value: 8 },
8 | { date: "2023-05-03", value: 7 },
9 | { date: "2023-05-04", value: 10 },
10 | { date: "2023-05-05", value: 12 },
11 | { date: "2023-05-06", value: 10.5 },
12 | { date: "2023-05-07", value: 6 },
13 | { date: "2023-05-08", value: 8 },
14 | { date: "2023-05-09", value: 7.5 },
15 | { date: "2023-05-10", value: 6 },
16 | { date: "2023-05-11", value: 8 },
17 | { date: "2023-05-12", value: 9 },
18 | { date: "2023-05-13", value: 10 },
19 | { date: "2023-05-14", value: 17 },
20 | { date: "2023-05-15", value: 14 },
21 | { date: "2023-05-16", value: 15 },
22 | { date: "2023-05-17", value: 20 },
23 | { date: "2023-05-18", value: 18 },
24 | { date: "2023-05-19", value: 16 },
25 | { date: "2023-05-20", value: 15 },
26 | { date: "2023-05-21", value: 16 },
27 | { date: "2023-05-22", value: 13 },
28 | { date: "2023-05-23", value: 11 },
29 | { date: "2023-05-24", value: 11 },
30 | { date: "2023-05-25", value: 13 },
31 | { date: "2023-05-26", value: 12 },
32 | { date: "2023-05-27", value: 9 },
33 | { date: "2023-05-28", value: 8 },
34 | { date: "2023-05-29", value: 10 },
35 | { date: "2023-05-30", value: 11 },
36 | { date: "2023-05-31", value: 8 },
37 | { date: "2023-06-01", value: 9 },
38 | { date: "2023-06-02", value: 10 },
39 | { date: "2023-06-03", value: 12 },
40 | { date: "2023-06-04", value: 13 },
41 | { date: "2023-06-05", value: 15 },
42 | { date: "2023-06-06", value: 13.5 },
43 | { date: "2023-06-07", value: 13 },
44 | { date: "2023-06-08", value: 13 },
45 | { date: "2023-06-09", value: 14 },
46 | { date: "2023-06-10", value: 13 },
47 | { date: "2023-06-11", value: 12.5 },
48 | ];
49 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
50 |
51 | export function Live3_AreaChartGradient() {
52 | let xScale = scaleTime()
53 | .domain([data[0].date, data[data.length - 1].date])
54 | .range([0, 100]);
55 |
56 | let yScale = scaleLinear()
57 | .domain([0, max(data.map((d) => d.value)) ?? 0])
58 | .range([100, 0]);
59 |
60 | let line = d3line<(typeof data)[number]>()
61 | .x((d) => xScale(d.date))
62 | .y((d) => yScale(d.value))
63 | .curve(curveMonotoneX);
64 |
65 | // Area generator
66 | let area = d3area<(typeof data)[number]>()
67 | .x((d) => xScale(d.date))
68 | .y0(yScale(0))
69 | .y1((d) => yScale(d.value))
70 | .curve(curveMonotoneX);
71 |
72 | let areaPath = area(data) ?? undefined;
73 |
74 | let d = line(data);
75 |
76 | if (!d) {
77 | return null;
78 | }
79 |
80 | return (
81 |
82 |
88 | {/* Chart area */}
89 |
171 |
172 | {/* X axis */}
173 | {data.map((day, i) => {
174 | // show 1 every x labels
175 | if (i % 6 !== 0 || i === 0 || i >= data.length - 3) return;
176 | return (
177 |
185 | {day.date.toLocaleDateString("en-US", {
186 | month: "short",
187 | day: "numeric",
188 | })}
189 |
190 | );
191 | })}
192 |
193 | {/* Y axis */}
194 | {yScale
195 | .ticks(8)
196 | .map(yScale.tickFormat(8, "d"))
197 | .map((value, i) => {
198 | if (i < 1) return;
199 | return (
200 |
208 | {value}
209 |
210 | );
211 | })}
212 |
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/area-charts/4_AreaChartSemiFilled.tsx:
--------------------------------------------------------------------------------
1 | import { scaleTime, scaleLinear, line as d3line, max, area as d3area, curveMonotoneX } from "d3";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | const sales = [
5 | { date: "2023-04-30", value: 4 },
6 | { date: "2023-05-01", value: 6 },
7 | { date: "2023-05-02", value: 8 },
8 | { date: "2023-05-03", value: 7 },
9 | { date: "2023-05-04", value: 10 },
10 | { date: "2023-05-05", value: 12 },
11 | { date: "2023-05-06", value: 10.5 },
12 | { date: "2023-05-07", value: 6 },
13 | { date: "2023-05-08", value: 8 },
14 | { date: "2023-05-09", value: 7.5 },
15 | { date: "2023-05-10", value: 6 },
16 | { date: "2023-05-11", value: 8 },
17 | { date: "2023-05-12", value: 9 },
18 | { date: "2023-05-13", value: 10 },
19 | { date: "2023-05-14", value: 17 },
20 | { date: "2023-05-15", value: 14 },
21 | { date: "2023-05-16", value: 15 },
22 | { date: "2023-05-17", value: 20 },
23 | { date: "2023-05-18", value: 18 },
24 | { date: "2023-05-19", value: 16 },
25 | { date: "2023-05-20", value: 15 },
26 | { date: "2023-05-21", value: 16 },
27 | { date: "2023-05-22", value: 13 },
28 | { date: "2023-05-23", value: 11 },
29 | { date: "2023-05-24", value: 11 },
30 | { date: "2023-05-25", value: 13 },
31 | { date: "2023-05-26", value: 12 },
32 | { date: "2023-05-27", value: 9 },
33 | { date: "2023-05-28", value: 8 },
34 | { date: "2023-05-29", value: 10 },
35 | { date: "2023-05-30", value: 11 },
36 | { date: "2023-05-31", value: 8 },
37 | { date: "2023-06-01", value: 9 },
38 | { date: "2023-06-02", value: 10 },
39 | { date: "2023-06-03", value: 12 },
40 | { date: "2023-06-04", value: 13 },
41 | { date: "2023-06-05", value: 15 },
42 | { date: "2023-06-06", value: 13.5 },
43 | { date: "2023-06-07", value: 13 },
44 | { date: "2023-06-08", value: 13 },
45 | { date: "2023-06-09", value: 14 },
46 | { date: "2023-06-10", value: 13 },
47 | { date: "2023-06-11", value: 12.5 },
48 | ];
49 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
50 |
51 | export function Live4_AreaChartSemiFilled() {
52 | let xScale = scaleTime()
53 | .domain([data[0].date, data[data.length - 1].date])
54 | .range([0, 100]);
55 |
56 | let yScale = scaleLinear()
57 | .domain([0, max(data.map((d) => d.value)) ?? 0])
58 | .range([100, 0]);
59 |
60 | let line = d3line<(typeof data)[number]>()
61 | .x((d) => xScale(d.date))
62 | .y((d) => yScale(d.value))
63 | .curve(curveMonotoneX);
64 |
65 | // Area generator
66 | let area = d3area<(typeof data)[number]>()
67 | .x((d) => xScale(d.date))
68 | .y0(yScale(0))
69 | .y1((d) => yScale(d.value))
70 | .curve(curveMonotoneX);
71 |
72 | let areaPath = area(data) ?? undefined;
73 |
74 | let d = line(data);
75 |
76 | if (!d) {
77 | return null;
78 | }
79 |
80 | return (
81 |
82 |
88 | {/* Chart area */}
89 |
171 |
172 | {/* X axis */}
173 | {data.map((day, i) => {
174 | // show 1 every x labels
175 | if (i % 6 !== 0 || i === 0 || i >= data.length - 3) return;
176 | return (
177 |
185 | {day.date.toLocaleDateString("en-US", {
186 | month: "short",
187 | day: "numeric",
188 | })}
189 |
190 | );
191 | })}
192 |
193 | {/* Y axis */}
194 | {yScale
195 | .ticks(8)
196 | .map(yScale.tickFormat(8, "d"))
197 | .map((value, i) => {
198 | if (i < 1) return;
199 | return (
200 |
208 | {value}
209 |
210 | );
211 | })}
212 |
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/bar-charts/11_BarChartMultiVertical_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const dataExample = [
6 | { key: "Jan 2020", values: [11.1, 9.5] },
7 | { key: "Feb 2020", values: [18.3, 16.7] },
8 | { key: "Mar 2020", values: [25.1, 19.5] },
9 | { key: "Apr 2020", values: [35.5, 24.9] },
10 | { key: "May 2020", values: [31.7, 28.1] },
11 | { key: "Jun 2020", values: [25.8, 20.2] },
12 | { key: "Jul 2020", values: [15.8, 10.2] },
13 | { key: "Aug 2020", values: [24.8, 17.2] },
14 | { key: "Sep 2020", values: [32.5, 23.9] },
15 | { key: "Oct 2020", values: [36.7, 27.1] },
16 | { key: "Nov 2020", values: [34.7, 28.1] },
17 | { key: "Dec 2020", values: [42.7, 33.1] },
18 | { key: "Jan 2021", values: [39.7, 36.1] },
19 | ];
20 |
21 | const PX_BETWEEN_BARS = 5;
22 |
23 | export function Live11_BarChartMultiVertical_DIV() {
24 | const data = dataExample;
25 | const numBars = data[0].values.length; // Get the number of bars
26 |
27 | // Upkey scales
28 | const xScale = scaleBand()
29 | .domain(data.map((d) => d.key))
30 | .range([0, 100])
31 | .padding(0.4);
32 |
33 | const yScale = scaleLinear()
34 | .domain([0, max(data.flatMap((d) => d.values)) ?? 0])
35 | .range([100, 0]);
36 |
37 | // Generate an array of colors for the bars
38 | const colors = ["#B89DFB", "#e7deff"];
39 |
40 | return (
41 |
52 | {/* Y axis */}
53 |
61 | {yScale
62 | .ticks(8)
63 | .map(yScale.tickFormat(8, "d"))
64 | .map((value, i) => (
65 |
72 | {value}
73 |
74 | ))}
75 |
76 |
77 | {/* Chart Area */}
78 |
87 |
88 |
110 |
111 | {/* Bars */}
112 | {data.map((d, index) => (
113 |
114 |
115 |
123 | {d.values.map((value, barIndex) => {
124 | const barHeight = 100 - yScale(value);
125 | const barWidth = (100 - PX_BETWEEN_BARS * (numBars - 1)) / numBars;
126 | const barXPosition = barIndex * (barWidth + PX_BETWEEN_BARS);
127 |
128 | return (
129 |
140 | );
141 | })}
142 |
143 |
144 |
145 |
146 | {d.key}
147 |
148 |
149 | {d.values.map((value, index) => (
150 |
151 |
155 |
{value}
156 |
157 | ))}
158 |
159 |
160 |
161 | ))}
162 | {/* X Axis (Labels) */}
163 | {data.map((entry, i) => {
164 | const xPosition = xScale(entry.key)! + xScale.bandwidth() / 2;
165 |
166 | return (
167 |
176 |
177 | {entry.key.slice(0, 10) + (entry.key.length > 10 ? "..." : "")}
178 |
179 |
180 | );
181 | })}
182 |
183 |
184 |
185 | );
186 | }
187 |
--------------------------------------------------------------------------------
/bar-charts/12_BarChartTripleFlagsHorizontal_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { key: "European Union", values: [15, 25, 33], flag: "eu" },
7 | { key: "United States", values: [11, 22, 29], flag: "us" },
8 | { key: "China", values: [5, 16, 21], flag: "cn" },
9 | { key: "Philippines", values: [4, 11, 19], flag: "ph" },
10 | ];
11 | const barColors = ["#F8ED53", "#E7E7F5", "#EEBA6B"];
12 | const PX_BETWEEN_BARS = 0.2;
13 |
14 | export function Live12_BarChartTripleFlagsHorizontal_DIV() {
15 | // Scales
16 | const yScale = scaleBand()
17 | .domain(data.map((d) => d.key))
18 | .range([0, 100]) // Represents the height of the chart
19 | .padding(0.25);
20 |
21 | const xScale = scaleLinear()
22 | .domain([0, max(data.flatMap((d) => d.values)) ?? 0])
23 | .range([0, 100]); // Represents the width of the chart
24 |
25 | const numBars = data[0].values.length; // Assuming all entries have the same number of bars
26 | return (
27 |
38 | {/* X Axis (Images) */}
39 |
45 | {data.map((entry, i) => (
46 |
54 |

59 |
60 | ))}
61 |
62 |
63 | {/* Chart Area */}
64 |
74 |
75 |
97 |
98 | {/* Bars with Rounded Right Corners */}
99 | {data.map((d, index) => {
100 | return (
101 |
102 |
103 |
111 | {d.values.map((value, barIndex) => {
112 | const barHeight = (100 - PX_BETWEEN_BARS * (numBars - 1)) / numBars;
113 | const barYPosition = barIndex * (barHeight + PX_BETWEEN_BARS);
114 | return (
115 |
125 | );
126 | })}
127 |
128 |
129 |
130 | {d.key}
131 |
132 | {d.values.map((value, index) => (
133 |
134 |
138 |
{value}
139 |
140 | ))}
141 |
142 |
143 |
144 | );
145 | })}
146 |
147 |
148 | {/* X Axis (Labels) */}
149 |
150 | {data.map((entry, i) => (
151 |
152 | {entry.values.map((value, barIndex) => {
153 | const barPosition = (barIndex + 0.5) / numBars;
154 | return (
155 | xScale(value) > 0 && (
156 |
164 | {value}
165 |
166 | )
167 | );
168 | })}
169 |
170 | ))}
171 |
172 |
173 | );
174 | }
175 |
--------------------------------------------------------------------------------
/bar-charts/14_BarChartLine.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max, line as d3_line, min, curveMonotoneX } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { key: "Jan", metric1: 18.1, metric2: 700 },
7 | { key: "Feb", metric1: 14.3, metric2: 1000 },
8 | { key: "Mar", metric1: 27.1, metric2: 900 },
9 | { key: "Apr", metric1: 40, metric2: 1000 },
10 | { key: "May", metric1: 12.7, metric2: 500 },
11 | { key: "Jun", metric1: 22.8, metric2: 600 },
12 | { key: "Jul", metric1: 17.8, metric2: 900 },
13 | { key: "Aug", metric1: 5.8, metric2: 800 },
14 | { key: "Sep", metric1: 42, metric2: 700 },
15 | { key: "Oct", metric1: 12.7, metric2: 900 },
16 | { key: "Nov", metric1: 24.7, metric2: 1000 },
17 | { key: "Dec", metric1: 19.7, metric2: 1220 },
18 | ];
19 |
20 | export function Live14_BarChartLine() {
21 | const minBars = 10;
22 | const filledData = [
23 | ...data,
24 | ...Array.from({ length: Math.max(0, minBars - data.length) }, (_, i) => ({
25 | key: `Empty ${i + 1}`,
26 | metric1: 0,
27 | metric2: 0,
28 | })),
29 | ];
30 | const maxMetric1 = max(data.map((d) => d.metric1)) ?? 0;
31 | const maxMetric2 = max(data.map((d) => d.metric2)) ?? 0;
32 | const minMetric2 = min(data.map((d) => d.metric2)) ?? 0;
33 |
34 | const xScale = scaleBand()
35 | .domain(data.map((d) => d.key))
36 | .range([0, 100])
37 | .padding(0.3);
38 |
39 | const yScaleMetric1 = scaleLinear().domain([0, maxMetric1]).range([100, 0]);
40 |
41 | const yScaleMetric2 = scaleLinear().domain([minMetric2, maxMetric2]).range([100, 0]);
42 |
43 | const line = d3_line<(typeof data)[number]>()
44 | .x((d) => {
45 | const xPosition = xScale(d.key) ?? 0;
46 | const bandwidth = xScale.bandwidth() ?? 0;
47 | return xPosition + bandwidth / 2;
48 | })
49 | .y((d) => yScaleMetric2(d.metric2))
50 | .curve(curveMonotoneX);
51 |
52 | const d = line(data);
53 |
54 | return (
55 |
66 | {/* Left Y-axis */}
67 |
76 | {yScaleMetric1
77 | .ticks(8)
78 | .map(yScaleMetric1.tickFormat(8, "d"))
79 | .map((value, i) => (
80 |
88 | {value}
89 |
90 | ))}
91 |
92 |
93 | {/* Right Y-axis */}
94 |
103 | {yScaleMetric2
104 | .ticks(8)
105 | .map(yScaleMetric2.tickFormat(8, "d"))
106 | .map((value, i) => (
107 |
115 | {value}
116 |
117 | ))}
118 |
119 |
120 | {/* Chart Area */}
121 |
131 | {/* Bars */}
132 |
133 | {filledData.map((d, index) => {
134 | const barWidth = xScale.bandwidth();
135 | const barHeight = yScaleMetric1(0) - yScaleMetric1(d.metric1);
136 |
137 | return (
138 | <>
139 |
140 |
141 | {/* Full height invisible bar */}
142 |
151 |
152 |
153 | {d.key}
154 |
155 |
156 |
157 |
{d.metric1}
158 |
159 |
160 |
161 |
{d.metric2}
162 |
163 |
164 |
165 |
166 |
167 | {/* Real bar */}
168 |
177 | >
178 | );
179 | })}
180 | {/* X Axis (Labels) */}
181 | {data.map((entry, i) => {
182 | const xPosition = xScale(entry.key)! + xScale.bandwidth() / 2;
183 |
184 | return (
185 |
194 |
195 | {entry.key}
196 |
197 |
198 | );
199 | })}
200 |
201 |
202 | {/* Line */}
203 |
217 |
218 |
219 | );
220 | }
221 |
--------------------------------------------------------------------------------
/bar-charts/15_BarChartBenchmark.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | const data = [
5 | { key: "Model 0", value: 85.8 },
6 | { key: "Model A", value: 34.3 },
7 | { key: "Model B", value: 27.1 },
8 | { key: "Model C", value: 22.5 },
9 | ];
10 |
11 | export function Live15_BarChartBenchmark() {
12 | return (
13 |
14 | {/* Bars */}
15 | {data.map((d, index) => {
16 | return (
17 |
18 |
19 | <>
20 |
27 | {d.key}
28 |
29 |
30 |
34 |
d.value))) * 100}%`,
42 | }}
43 | />
44 |
45 |
52 | {d.value}
53 |
54 |
55 | >
56 |
57 |
58 | {d.key}
59 | {d.value}
60 |
61 |
62 | );
63 | })}
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/bar-charts/1_BarChartHorizontal_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { key: "Technology", value: 38.1 },
7 | { key: "Financials", value: 25.3 },
8 | { key: "Energy", value: 23.1 },
9 | { key: "Cyclical", value: 19.5 },
10 | { key: "Defensive", value: 14.7 },
11 | { key: "Utilities", value: 5.8 },
12 | ].toSorted((a, b) => b.value - a.value);
13 |
14 | export function Live1_BarChartHorizontal_DIV() {
15 | // Scales
16 | const yScale = scaleBand()
17 | .domain(data.map((d) => d.key))
18 | .range([0, 100])
19 | .padding(0.175);
20 |
21 | const xScale = scaleLinear()
22 | .domain([0, max(data.map((d) => d.value)) ?? 0])
23 | .range([0, 100]);
24 |
25 | const longestWord = max(data.map((d) => d.key.length)) || 1;
26 | return (
27 |
38 | {/* Chart Area */}
39 |
49 | {/* Bars with Rounded Right Corners */}
50 | {data.map((d, index) => {
51 | const barWidth = xScale(d.value);
52 | const barHeight = yScale.bandwidth();
53 |
54 | return (
55 |
56 |
57 |
68 |
69 |
70 |
71 |
72 |
73 |
{d.key}
74 |
{d.value}%
75 |
76 |
77 |
78 |
79 | );
80 | })}
81 |
103 | {/* X Axis (Values) */}
104 | {xScale.ticks(4).map((value, i) => (
105 |
113 | {value}
114 |
115 | ))}
116 |
117 |
118 | {/* Y Axis (Letters) */}
119 |
126 | {data.map((entry, i) => (
127 |
135 | {entry.key}
136 |
137 | ))}
138 |
139 |
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/bar-charts/3_BarChartGradient_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { key: "Technology", value: 38.1, color: "from-pink-300 to-pink-400" },
7 | { key: "Financials", value: 25.3, color: "from-purple-300 to-purple-400" },
8 | { key: "Energy", value: 23.1, color: "from-indigo-300 to-indigo-400" },
9 | { key: "Cyclical", value: 19.5, color: "from-sky-300 to-sky-400" },
10 | { key: "Defensive", value: 14.7, color: "from-orange-200 to-orange-300" },
11 | { key: "Utilities", value: 5.8, color: "from-lime-300 to-lime-400" },
12 | ].toSorted((a, b) => b.value - a.value);
13 |
14 | export function Live3_BarChartGradient_DIV() {
15 | // Scales
16 | const yScale = scaleBand()
17 | .domain(data.map((d) => d.key))
18 | .range([0, 100])
19 | .padding(0.175);
20 |
21 | const xScale = scaleLinear()
22 | .domain([0, max(data.map((d) => d.value)) ?? 0])
23 | .range([0, 100]);
24 |
25 | const longestWord = max(data.map((d) => d.key.length)) || 1;
26 | return (
27 |
38 | {/* Chart Area */}
39 |
49 | {/* Bars with Rounded Right Corners */}
50 | {data.map((d, index) => {
51 | const barWidth = xScale(d.value);
52 | const barHeight = yScale.bandwidth();
53 |
54 | return (
55 |
56 |
57 |
68 |
69 |
70 | {d.key}
71 | {d.value}
72 |
73 |
74 | );
75 | })}
76 |
98 | {/* X Axis (Values) */}
99 | {xScale.ticks(4).map((value, i) => (
100 |
108 | {value}
109 |
110 | ))}
111 |
112 |
113 | {/* Y Axis (Letters) */}
114 |
121 | {data.map((entry, i) => (
122 |
130 | {entry.key}
131 |
132 | ))}
133 |
134 |
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/bar-charts/4_BarChartBreakdown.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 |
3 | const data = [
4 | {
5 | key: "Utils",
6 | value: 17.1,
7 | color: "from-fuchsia-300/80 to-fuchsia-400/80 dark:from-fuchsia-500 dark:to-fuchsia-700",
8 | },
9 | {
10 | key: "Tech",
11 | value: 14.3,
12 | color: "from-violet-300 to-violet-400 dark:from-violet-500 dark:to-violet-700",
13 | },
14 | {
15 | key: "Energy",
16 | value: 27.1,
17 | color: "from-blue-300 to-blue-400 dark:from-blue-500 dark:to-blue-700",
18 | },
19 | {
20 | key: "Cyclicals",
21 | value: 42.5,
22 | color: "from-sky-300 to-sky-400 dark:from-sky-500 dark:to-sky-700",
23 | },
24 | {
25 | key: "Fuel",
26 | value: 12.7,
27 | color: "from-orange-200 to-orange-300 dark:from-amber-500 dark:to-amber-700",
28 | },
29 | ];
30 |
31 | export function Live4_BarChartBreakdown() {
32 | const gap = 0.3; // gap between bars
33 | const totalValue = data.reduce((acc, d) => acc + d.value, 0);
34 | const barHeight = 54;
35 | const totalWidth = totalValue + gap * (data.length - 1);
36 | let cumulativeWidth = 0;
37 |
38 | const cornerRadius = 4; // Adjust this value to change the roundness
39 |
40 | return (
41 |
53 | {/* Chart Area */}
54 |
63 | {/* Bars with Gradient Fill and Lighter Stroke for 3D Effect */}
64 | {data.map((d, index) => {
65 | const barWidth = (d.value / totalWidth) * 100;
66 | const xPosition = cumulativeWidth;
67 | cumulativeWidth += barWidth + gap;
68 |
69 | return (
70 |
80 |
88 |
100 | {d.key}
101 |
102 |
115 | {d.value}
116 |
117 |
118 | );
119 | })}
120 |
121 |
122 | );
123 | }
124 |
--------------------------------------------------------------------------------
/bar-charts/5_BarChartThinBreakdown.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 |
3 | const data = [
4 | {
5 | key: "Tech",
6 | value: 17.1,
7 | color: "from-fuchsia-300/80 to-fuchsia-400/80 dark:from-fuchsia-500 dark:to-fuchsia-700",
8 | },
9 | {
10 | key: "Utilities",
11 | value: 14.3,
12 | color: "from-violet-300 to-violet-400 dark:from-violet-500 dark:to-violet-700",
13 | },
14 | {
15 | key: "Energy",
16 | value: 27.1,
17 | color: "from-blue-300 to-blue-400 dark:from-blue-500 dark:to-blue-700",
18 | },
19 | {
20 | key: "Cyclicals",
21 | value: 42.5,
22 | color: "from-sky-300 to-sky-400 dark:from-sky-500 dark:to-sky-700",
23 | },
24 | {
25 | key: "Fuel",
26 | value: 12.7,
27 | color: "from-orange-200 to-orange-300 dark:from-amber-500 dark:to-amber-700",
28 | },
29 | ];
30 |
31 | export function Live5_BarChartThinBreakdown() {
32 | const gap = 0.3; // gap between bars
33 | const totalValue = data.reduce((acc, d) => acc + d.value, 0);
34 | const barHeight = 12;
35 | const totalWidth = totalValue + gap * (data.length - 1);
36 | let cumulativeWidth = 0;
37 |
38 | const cornerRadius = 4; // Adjust this value to change the roundness
39 |
40 | return (
41 |
53 | {/* Chart Area */}
54 |
63 | {/* Bars with Gradient Fill */}
64 | {data.map((d, index) => {
65 | const barWidth = (d.value / totalWidth) * 100;
66 | const xPosition = cumulativeWidth;
67 | cumulativeWidth += barWidth + gap;
68 |
69 | return (
70 |
80 |
88 |
95 | {d.key}
96 |
97 |
98 | );
99 | })}
100 |
101 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/bar-charts/6_BarChartThinHorizontal_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { key: "France", value: 38.1 },
7 | { key: "Spain", value: 25.3 },
8 | { key: "Italy", value: 23.1 },
9 | { key: "Portugal", value: 19.5 },
10 | { key: "Germany", value: 14.7 },
11 | { key: "Netherlands", value: 6.1 },
12 | { key: "Belgium", value: 10.8 },
13 | { key: "Austria", value: 7.8 },
14 | { key: "Greece", value: 6.8 },
15 | { key: "Luxembourg", value: 5.5 },
16 | { key: "Cyprus", value: 4.8 },
17 | { key: "Malta", value: 3.5 },
18 | { key: "Slovenia", value: 3.8 },
19 | { key: "Estonia", value: 8.8 },
20 | { key: "Latvia", value: 15.8 },
21 | { key: "Lithuania", value: 12.8 },
22 | { key: "Croatia", value: 5.8 },
23 | ].toSorted((a, b) => b.value - a.value);
24 |
25 | export function Live6_BarChartThinHorizontal_DIV() {
26 | // Scales
27 | const yScale = scaleBand()
28 | .domain(data.map((d) => d.key))
29 | .range([0, 100])
30 | .padding(0.6);
31 |
32 | const xScale = scaleLinear()
33 | .domain([0, max(data.map((d) => d.value)) ?? 0])
34 | .range([0, 100]);
35 |
36 | const longestWord = max(data.map((d) => d.key.length)) || 1;
37 | return (
38 |
49 | {/* Chart Area */}
50 |
60 | {/* Tooltip for each data point */}
61 | {data.map((d, index) => {
62 | const barWidth = xScale(d.value);
63 | const barHeight = yScale.bandwidth();
64 | const hoverColor =
65 | barWidth > 50
66 | ? "hover:bg-pink-200/40"
67 | : barWidth > 25
68 | ? "hover:bg-purple-200/40"
69 | : barWidth > 10
70 | ? "hover:bg-indigo-200/40"
71 | : "hover:bg-sky-200/40";
72 | return (
73 |
74 |
75 |
86 |
87 |
88 | {d.key}
89 | {d.value}
90 |
91 |
92 | );
93 | })}
94 | {/* End of Tooltip*/}
95 |
96 | {/* Bars with Rounded Right Corners */}
97 | {data.map((d, index) => {
98 | const barWidth = xScale(d.value);
99 | const barHeight = yScale.bandwidth();
100 | const barColor =
101 | barWidth > 50
102 | ? "bg-pink-300 dark:bg-pink-500"
103 | : barWidth > 25
104 | ? "bg-purple-300 dark:bg-purple-500"
105 | : barWidth > 10
106 | ? "bg-indigo-300 dark:bg-indigo-500"
107 | : "bg-sky-300 dark:bg-sky-500";
108 | return (
109 |
110 |
120 | {/* Tip of the bar */}
121 |
133 |
134 | );
135 | })}
136 |
159 | {/* X Axis (Values) */}
160 | {xScale.ticks(4).map((value, i) => (
161 |
169 | {value}
170 |
171 | ))}
172 |
173 |
174 | {/* Y Axis (Letters) */}
175 |
182 | {data.map((entry, i) => (
183 |
191 | {entry.key}
192 |
193 | ))}
194 |
195 |
196 | );
197 | }
198 |
--------------------------------------------------------------------------------
/bar-charts/7_BarChartFlagsHorizontal.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 |
4 | const data = [
5 | { key: "Portugal", value: 55.8, flag: "pt" },
6 | { key: "France", value: 34.3, flag: "fr" },
7 | { key: "Sweden", value: 27.1, flag: "se" },
8 | { key: "Spain", value: 22.5, flag: "es" },
9 | { key: "Italy", value: 18.7, flag: "it" },
10 | { key: "Germany", value: 10.8, flag: "de" },
11 | ];
12 |
13 | export function Live7_BarChartFlagsHorizontal() {
14 | // Scales
15 | const yScale = scaleBand()
16 | .domain(data.map((d) => d.key) as string[])
17 | .range([0, 100]) // Represents the height of the chart
18 | .padding(0.2);
19 |
20 | const xScale = scaleLinear()
21 | .domain([0, max(data.map((d) => d.value)) ?? 0])
22 | .range([0, 100]); // Represents the width of the chart
23 |
24 | return (
25 |
36 | {/* Bars and Text */}
37 |
45 | {data.map((entry, i) => {
46 | const barWidth = xScale(entry.value);
47 | const barHeight = yScale.bandwidth();
48 |
49 | return (
50 |
61 |
72 | {entry.key}
73 |
74 |
75 | );
76 | })}
77 |
99 | {/* X Axis (Values) */}
100 | {xScale.ticks(4).map((value, i) => (
101 |
109 | {value}
110 |
111 | ))}
112 |
113 |
114 | {/* Y Axis (Images) */}
115 |
122 | {data.map((entry, i) => (
123 |
131 |

136 |
137 | ))}
138 |
139 |
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/bar-charts/9_BarChartVertical_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 | import { scaleBand, scaleLinear, max } from "d3";
3 |
4 | const data = [
5 | { key: "Technology", value: 18.1 },
6 | { key: "Utilities", value: 14.3 },
7 | { key: "Energy", value: 27.1 },
8 | { key: "Cyclicals", value: 40 },
9 | { key: "Defensive", value: 12.7 },
10 | { key: "Financials", value: 22.8 },
11 | { key: "Health", value: 17.8 },
12 | { key: "Real Estate", value: 5.8 },
13 | { key: "Communications", value: 42 },
14 | { key: "Materials", value: 12.7 },
15 | { key: "Whatever", value: 24.7 },
16 | { key: "Whenever", value: 19.7 },
17 | { key: "Whomever", value: 29.7 },
18 | ];
19 |
20 | export function Live9_BarChartVertical_DIV() {
21 | const minBars = 10;
22 | const filledData = [
23 | ...data,
24 | // Add empty bars to make the chart look better for small datasets
25 | ...Array.from({ length: Math.max(0, minBars - data.length) }, (_, i) => ({
26 | key: `Empty ${i + 1}`,
27 | value: 0,
28 | })),
29 | ];
30 | // Scales
31 | const xScale = scaleBand()
32 | .domain(filledData.map((d) => d.key))
33 | .range([0, 100])
34 | .padding(0.3);
35 |
36 | const yScale = scaleLinear()
37 | .domain([0, max(data.map((d) => d.value)) ?? 0])
38 | .range([100, 0]);
39 |
40 | return (
41 |
52 | {/* Y axis */}
53 |
61 | {yScale
62 | .ticks(8)
63 | .map(yScale.tickFormat(8, "d"))
64 | .map((value, i) => (
65 |
72 | {value}
73 |
74 | ))}
75 |
76 |
77 | {/* Chart Area */}
78 |
87 |
113 |
114 | {/* X Axis (Labels) */}
115 | {data.map((entry, i) => {
116 | const xPosition = xScale(entry.key)! + xScale.bandwidth() / 2;
117 |
118 | return (
119 |
128 |
129 | {entry.key.slice(0, 10) + (entry.key.length > 10 ? "..." : "")}
130 |
131 |
132 | );
133 | })}
134 |
135 | {/* Bars */}
136 | {filledData.map((d, index) => {
137 | const barWidth = xScale.bandwidth();
138 | const barHeight = yScale(0) - yScale(d.value);
139 |
140 | return (
141 |
151 | );
152 | })}
153 |
154 |
155 | );
156 | }
157 |
--------------------------------------------------------------------------------
/helpers/ClientTooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 | import { createPortal } from "react-dom";
4 |
5 | /* -------------------------------------------------------------------------------------------------
6 | * Context
7 | * -----------------------------------------------------------------------------------------------*/
8 |
9 | type TooltipContextValue = {
10 | tooltip: { x: number; y: number } | undefined;
11 | setTooltip: (tooltip: { x: number; y: number } | undefined) => void;
12 | };
13 |
14 | const TooltipContext = React.createContext(undefined);
15 |
16 | function useTooltipContext(componentName: string): TooltipContextValue {
17 | const context = React.useContext(TooltipContext);
18 | if (!context) {
19 | throw new Error("Please wrap your TooltipContent and TooltipTrigger in a ClientTooltip");
20 | }
21 | return context;
22 | }
23 |
24 | /* -------------------------------------------------------------------------------------------------
25 | * Tooltip
26 | * -----------------------------------------------------------------------------------------------*/
27 |
28 | const Tooltip: React.FC<{ children: React.ReactNode }> = ({ children }) => {
29 | const [tooltip, setTooltip] = React.useState<{ x: number; y: number }>();
30 |
31 | return (
32 | {children}
33 | );
34 | };
35 |
36 | /* -------------------------------------------------------------------------------------------------
37 | * TooltipTrigger
38 | * -----------------------------------------------------------------------------------------------*/
39 |
40 | const TRIGGER_NAME = "TooltipTrigger";
41 |
42 | const TooltipTrigger = React.forwardRef(
43 | (props, forwardedRef) => {
44 | const { children } = props;
45 | const context = useTooltipContext(TRIGGER_NAME);
46 | const triggerRef = React.useRef(null);
47 |
48 | React.useEffect(() => {
49 | const handleClickOutside = (event: MouseEvent | TouchEvent) => {
50 | if (triggerRef.current && !triggerRef.current.contains(event.target as Node)) {
51 | context.setTooltip(undefined);
52 | }
53 | };
54 |
55 | document.addEventListener("mousedown", handleClickOutside);
56 | document.addEventListener("touchstart", handleClickOutside);
57 |
58 | return () => {
59 | document.removeEventListener("mousedown", handleClickOutside);
60 | document.removeEventListener("touchstart", handleClickOutside);
61 | };
62 | }, [context]);
63 |
64 | return (
65 | {
67 | // Maintain both refs
68 | triggerRef.current = node;
69 | if (typeof forwardedRef === "function") {
70 | forwardedRef(node);
71 | } else if (forwardedRef) {
72 | forwardedRef.current = node;
73 | }
74 | }}
75 | onPointerMove={(event) => {
76 | // Only handle mouse events, not touch
77 | if (event.pointerType === "mouse") {
78 | context.setTooltip({ x: event.clientX, y: event.clientY });
79 | }
80 | }}
81 | onPointerLeave={(event) => {
82 | // Only handle mouse events, not touch
83 | if (event.pointerType === "mouse") {
84 | context.setTooltip(undefined);
85 | }
86 | }}
87 | onTouchStart={(event) => {
88 | // On mobile, trigger when clicked instead of hover. Change as needed.
89 | context.setTooltip({ x: event.touches[0].clientX, y: event.touches[0].clientY });
90 | setTimeout(() => {
91 | context.setTooltip(undefined);
92 | }, 2000);
93 | }}
94 | >
95 | {children}
96 |
97 | );
98 | }
99 | );
100 |
101 | TooltipTrigger.displayName = TRIGGER_NAME;
102 |
103 | /* -------------------------------------------------------------------------------------------------
104 | * TooltipContent
105 | * -----------------------------------------------------------------------------------------------*/
106 |
107 | const CONTENT_NAME = "TooltipContent";
108 |
109 | const TooltipContent = React.forwardRef(
110 | (props, _) => {
111 | const { children } = props;
112 | const context = useTooltipContext(CONTENT_NAME);
113 | const runningOnClient = typeof document !== "undefined";
114 | const tooltipRef = React.useRef(null);
115 |
116 | // Calculate position based on viewport
117 | const getTooltipPosition = () => {
118 | if (!tooltipRef.current || !context.tooltip) return {};
119 |
120 | const tooltipWidth = tooltipRef.current.offsetWidth;
121 | const viewportWidth = window.innerWidth;
122 | const willOverflowRight = context.tooltip.x + tooltipWidth + 10 > viewportWidth;
123 |
124 | return {
125 | top: context.tooltip.y - 20,
126 | left: willOverflowRight ? context.tooltip.x - tooltipWidth - 10 : context.tooltip.x + 10,
127 | };
128 | };
129 |
130 | if (!context.tooltip || !runningOnClient) {
131 | return null;
132 | }
133 |
134 | const isMobile = window.innerWidth < 768;
135 |
136 | return createPortal(
137 | isMobile ? (
138 |
145 | {children}
146 |
147 | ) : (
148 |
153 | {children}
154 |
155 | ),
156 | document.body
157 | );
158 | }
159 | );
160 |
161 | TooltipContent.displayName = CONTENT_NAME;
162 |
163 | /* -------------------------------------------------------------------------------------------------
164 | * Exports
165 | * -----------------------------------------------------------------------------------------------*/
166 |
167 | export { Tooltip as ClientTooltip, TooltipTrigger, TooltipContent };
168 |
--------------------------------------------------------------------------------
/line-charts/1_LineChart.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleTime, scaleLinear, max, line as d3_line } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | /* Original component: https://buildui.com/recipes/responsive-line-chart */
6 | let sales = [
7 | { date: "2023-04-30", value: 4 },
8 | { date: "2023-05-01", value: 6 },
9 | { date: "2023-05-02", value: 8 },
10 | { date: "2023-05-03", value: 7 },
11 | { date: "2023-05-04", value: 10 },
12 | { date: "2023-05-05", value: 12 },
13 | { date: "2023-05-06", value: 11 },
14 | { date: "2023-05-07", value: 8 },
15 | { date: "2023-05-08", value: 7 },
16 | { date: "2023-05-09", value: 9 },
17 | ];
18 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
19 |
20 | export function Live1_LineChart() {
21 | let xScale = scaleTime()
22 | .domain([data[0].date, data[data.length - 1].date])
23 | .range([0, 100]);
24 | let yScale = scaleLinear()
25 | .domain([0, max(data.map((d) => d.value)) ?? 0])
26 | .range([100, 0]);
27 |
28 | let line = d3_line<(typeof data)[number]>()
29 | .x((d) => xScale(d.date))
30 | .y((d) => yScale(d.value));
31 |
32 | let d = line(data);
33 |
34 | if (!d) {
35 | return null;
36 | }
37 |
38 | return (
39 |
50 | {/* Y axis */}
51 |
59 | {yScale
60 | .ticks(8)
61 | .map(yScale.tickFormat(8, "d"))
62 | .map((value, i) => (
63 |
71 | {value}
72 |
73 | ))}
74 |
75 |
76 | {/* Chart area */}
77 |
86 |
180 |
181 | {/* X Axis */}
182 |
183 | {data.map((day, i) => {
184 | const isFirst = i === 0;
185 | const isLast = i === data.length - 1;
186 | const isMax = day.value === Math.max(...data.map((d) => d.value));
187 | if (!isFirst && !isLast && !isMax) return null;
188 | return (
189 |
190 |
200 | {day.date.toLocaleDateString("en-US", {
201 | month: "numeric",
202 | day: "numeric",
203 | })}
204 |
205 |
206 | );
207 | })}
208 |
209 |
210 |
211 | );
212 | }
213 |
--------------------------------------------------------------------------------
/line-charts/2_LineChartCurved.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleTime, scaleLinear, max, line as d3_line, curveMonotoneX } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | let sales = [
6 | { date: "2023-04-30", value: 4 },
7 | { date: "2023-05-01", value: 2 },
8 | { date: "2023-05-02", value: 8 },
9 | { date: "2023-05-03", value: 7 },
10 | { date: "2023-05-04", value: 10 },
11 | { date: "2023-05-05", value: 12 },
12 | { date: "2023-05-06", value: 11 },
13 | { date: "2023-05-07", value: 8 },
14 | { date: "2023-05-08", value: 7 },
15 | { date: "2023-05-09", value: 9 },
16 | ];
17 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
18 |
19 | export function Live2_LineChartCurved() {
20 | let xScale = scaleTime()
21 | .domain([data[0].date, data[data.length - 1].date])
22 | .range([0, 100]);
23 | let yScale = scaleLinear()
24 | .domain([0, max(data.map((d) => d.value)) ?? 0])
25 | .range([100, 0]);
26 |
27 | let line = d3_line<(typeof data)[number]>()
28 | .x((d) => xScale(d.date))
29 | .y((d) => yScale(d.value))
30 | .curve(curveMonotoneX);
31 |
32 | let d = line(data);
33 |
34 | if (!d) {
35 | return null;
36 | }
37 |
38 | return (
39 |
50 | {/* Y axis */}
51 |
59 | {yScale
60 | .ticks(8)
61 | .map(yScale.tickFormat(8, "d"))
62 | .map((value, i) => (
63 |
71 | {value}
72 |
73 | ))}
74 |
75 |
76 | {/* Chart area */}
77 |
86 |
180 |
181 |
182 | {/* X Axis */}
183 | {data.map((day, i) => {
184 | const isFirst = i === 0;
185 | const isLast = i === data.length - 1;
186 | const isMax = day.value === Math.max(...data.map((d) => d.value));
187 | if (!isFirst && !isLast && !isMax) return null;
188 | return (
189 |
190 |
200 | {day.date.toLocaleDateString("en-US", {
201 | month: "numeric",
202 | day: "numeric",
203 | })}
204 |
205 |
206 | );
207 | })}
208 |
209 |
210 |
211 | );
212 | }
213 |
--------------------------------------------------------------------------------
/line-charts/4_LineChartLabelsCurved.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleTime, scaleLinear, max, line as d3_line, curveMonotoneX } from "d3";
3 |
4 | let sales = [
5 | { date: "2023-04-30", value: 3 },
6 | { date: "2023-05-01", value: 6 },
7 | { date: "2023-05-02", value: 8 },
8 | { date: "2023-05-03", value: 6 },
9 | { date: "2023-05-04", value: 10 },
10 | { date: "2023-05-05", value: 12 },
11 | { date: "2023-05-06", value: 11 },
12 | { date: "2023-05-07", value: 8 },
13 | { date: "2023-05-08", value: 4 },
14 | { date: "2023-05-09", value: 9 },
15 | { date: "2023-05-10", value: 9 },
16 | ];
17 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
18 |
19 | export function Live4_LineChartLabelsCurved() {
20 | let xScale = scaleTime()
21 | .domain([data[0].date, data[data.length - 1].date])
22 | .range([0, 100]);
23 | let yScale = scaleLinear()
24 | .domain([0, max(data.map((d) => d.value)) ?? 0])
25 | .range([100, 0]);
26 |
27 | let line = d3_line<(typeof data)[number]>()
28 | .x((d) => xScale(d.date))
29 | .y((d) => yScale(d.value))
30 | .curve(curveMonotoneX);
31 |
32 | let d = line(data);
33 |
34 | if (!d) {
35 | return null;
36 | }
37 |
38 | return (
39 |
50 | {/* Chart area */}
51 |
60 |
96 |
97 | {/* X Axis */}
98 | {data.map((day, i) => {
99 | const isFirst = i === 0;
100 | const isLast = i === data.length - 1;
101 | const isMax = day.value === Math.max(...data.map((d) => d.value));
102 | if (!isFirst && !isLast && !isMax) return null;
103 | return (
104 |
105 |
113 | {day.date.toLocaleDateString("en-US", {
114 | month: "numeric",
115 | day: "numeric",
116 | })}
117 |
118 |
119 | );
120 | })}
121 |
122 |
123 | {/* Labels */}
124 |
132 | {data.map((entry, i) => (
133 |
142 | {entry.value}
143 |
144 | ))}
145 |
146 |
147 | );
148 | }
149 |
--------------------------------------------------------------------------------
/line-charts/6_LineChartStep.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleTime, scaleLinear, max, line as d3_line, curveStep } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | let sales = [
6 | { date: "2023-04-30", value: 4 },
7 | { date: "2023-05-01", value: 6 },
8 | { date: "2023-05-02", value: 8 },
9 | { date: "2023-05-03", value: 7 },
10 | { date: "2023-05-04", value: 10 },
11 | { date: "2023-05-05", value: 12 },
12 | // { date: "2023-05-06", value: 11 },
13 | { date: "2023-05-07", value: 8 },
14 | { date: "2023-05-08", value: 7 },
15 | { date: "2023-05-09", value: 9 },
16 | ];
17 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
18 |
19 | export function Live6_LineChartStep() {
20 | let xScale = scaleTime()
21 | .domain([data[0].date, data[data.length - 1].date])
22 | .range([0, 100]);
23 | let yScale = scaleLinear()
24 | .domain([0, max(data.map((d) => d.value)) ?? 0])
25 | .range([100, 0]);
26 |
27 | let line = d3_line<(typeof data)[number]>()
28 | .x((d) => xScale(d.date))
29 | .y((d) => yScale(d.value))
30 | .curve(curveStep);
31 |
32 | let d = line(data);
33 |
34 | if (!d) {
35 | return null;
36 | }
37 |
38 | return (
39 |
50 | {/* Y axis */}
51 |
59 | {yScale
60 | .ticks(8)
61 | .map(yScale.tickFormat(8, "d"))
62 | .map((value, i) => (
63 |
71 | {value}
72 |
73 | ))}
74 |
75 |
76 | {/* Chart area */}
77 |
86 |
177 |
178 |
179 | {/* X Axis */}
180 | {data.map((day, i) => {
181 | const isFirst = i === 0;
182 | const isLast = i === data.length - 1;
183 | const isMax = day.value === Math.max(...data.map((d) => d.value));
184 | if (!isFirst && !isLast && !isMax) return null;
185 | return (
186 |
187 |
197 | {day.date.toLocaleDateString("en-US", {
198 | month: "numeric",
199 | day: "numeric",
200 | })}
201 |
202 |
203 | );
204 | })}
205 |
206 |
207 |
208 | );
209 | }
210 |
--------------------------------------------------------------------------------
/line-charts/7_LineChartPulse.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleTime, scaleLinear, max, line as d3_line, curveNatural as d3_curveNatural } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | let sales = [
6 | { date: "2023-04-30", value: 4 },
7 | { date: "2023-05-01", value: 6 },
8 | { date: "2023-05-02", value: 8 },
9 | { date: "2023-05-03", value: 7 },
10 | { date: "2023-05-04", value: 10 },
11 | { date: "2023-05-05", value: 12 },
12 | { date: "2023-05-06", value: 11 },
13 | { date: "2023-05-07", value: 8 },
14 | { date: "2023-05-08", value: 7 },
15 | { date: "2023-05-09", value: 9 },
16 | ];
17 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
18 |
19 | export function Live7_LineChartPulse() {
20 | let xScale = scaleTime()
21 | .domain([data[0].date, data[data.length - 1].date])
22 | .range([0, 100]);
23 | let yScale = scaleLinear()
24 | .domain([0, max(data.map((d) => d.value)) ?? 0])
25 | .range([100, 0]);
26 |
27 | let line = d3_line<(typeof data)[number]>()
28 | .x((d) => xScale(d.date))
29 | .y((d) => yScale(d.value))
30 | .curve(d3_curveNatural);
31 |
32 | let d = line(data);
33 |
34 | if (!d) {
35 | return null;
36 | }
37 |
38 | return (
39 |
50 | {/* Y axis */}
51 |
59 | {yScale
60 | .ticks(8)
61 | .map(yScale.tickFormat(8, "d"))
62 | .map((value, i) => (
63 |
71 | {value}
72 |
73 | ))}
74 |
75 |
76 | {/* Chart area */}
77 |
86 | {/* Pulsating dot */}
87 |
97 |
187 |
188 |
189 | {/* X Axis */}
190 | {data.map((day, i) => {
191 | const isFirst = i === 0;
192 | const isLast = i === data.length - 1;
193 | const isMax = day.value === Math.max(...data.map((d) => d.value));
194 | if (!isFirst && !isLast && !isMax) return null;
195 | return (
196 |
197 |
207 | {day.date.toLocaleDateString("en-US", {
208 | month: "numeric",
209 | day: "numeric",
210 | })}
211 |
212 |
213 | );
214 | })}
215 |
216 |
217 |
218 | );
219 | }
220 |
--------------------------------------------------------------------------------
/line-charts/8_LineChartFull.tsx:
--------------------------------------------------------------------------------
1 | import { scaleTime, scaleLinear, max, line as d3_line, curveNatural } from "d3";
2 |
3 | let sales = [
4 | { date: "2023-04-30", value: 5 },
5 | { date: "2023-05-01", value: 7 },
6 | { date: "2023-05-02", value: 7 },
7 | { date: "2023-05-03", value: 7.5 },
8 | { date: "2023-05-04", value: 9 },
9 | { date: "2023-05-05", value: 10.5 },
10 | { date: "2023-05-06", value: 11 },
11 | { date: "2023-05-07", value: 10.5 },
12 | { date: "2023-05-08", value: 8 },
13 | { date: "2023-05-09", value: 9 },
14 | { date: "2023-05-10", value: 9.5 },
15 | { date: "2023-05-11", value: 10 },
16 | { date: "2023-05-12", value: 9 },
17 | { date: "2023-05-13", value: 8.5 },
18 | { date: "2023-05-14", value: 6 },
19 | { date: "2023-05-15", value: 7 },
20 | { date: "2023-05-16", value: 9 },
21 | { date: "2023-05-17", value: 10 },
22 | { date: "2023-05-18", value: 10.5 },
23 | { date: "2023-05-19", value: 11 },
24 | { date: "2023-05-20", value: 11 },
25 | { date: "2023-05-21", value: 11.5 },
26 | { date: "2023-05-22", value: 12 },
27 | { date: "2023-05-23", value: 12 },
28 | ];
29 | let data = sales.map((d) => ({ ...d, date: new Date(d.date) }));
30 |
31 | export function Live8_LineChartFull() {
32 | let xScale = scaleTime()
33 | .domain([data[0].date, data[data.length - 1].date])
34 | .range([0, 100]);
35 | let yScale = scaleLinear()
36 | .domain([0, max(data.map((d) => d.value)) ?? 0])
37 | .range([100, 0]);
38 |
39 | let line = d3_line<(typeof data)[number]>()
40 | .x((d) => xScale(d.date))
41 | .y((d) => yScale(d.value))
42 | .curve(curveNatural);
43 |
44 | let d = line(data);
45 |
46 | if (!d) {
47 | return null;
48 | }
49 |
50 | return (
51 |
52 | {/* Chart area */}
53 |
60 |
97 |
98 | {/* Y axis */}
99 | {yScale
100 | .ticks(8)
101 | .map(yScale.tickFormat(8, "d"))
102 | .map((value, i) => {
103 | if (i < 1) return;
104 |
105 | return (
106 |
114 | {value}
115 |
116 | );
117 | })}
118 |
119 | {/* X axis */}
120 | {data.map((day, i) => {
121 | if (i % 3 !== 0 || i === 0 || i === data.length - 1) return;
122 | return (
123 |
124 |
132 | {day.date.toLocaleDateString("en-US", {
133 | month: "numeric",
134 | day: "numeric",
135 | })}
136 |
137 |
138 | );
139 | })}
140 |
141 |
142 | );
143 | }
144 |
--------------------------------------------------------------------------------
/other-charts/4_BubbleChart_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 | import { HierarchyNode } from "d3";
4 |
5 | const data = [
6 | { name: "MacOS", sector: "Tech", value: 4812 },
7 | { name: "Linux", sector: "Tech", value: 3212 },
8 | { name: "Windows", sector: "Tech", value: 512 },
9 | { name: "Diesel", sector: "Energy", value: 1252 },
10 | { name: "Petrol", sector: "Energy", value: 625 },
11 | { name: "Diesel", sector: "Utilities", value: 1252 },
12 | { name: "Petrol", sector: "Utilities", value: 825 },
13 | { name: "Bonds", sector: "Cyclicals", value: 1517 },
14 | { name: "Loans", sector: "Cyclicals", value: 1213 },
15 | { name: "Loans", sector: "Cyclicals", value: 213 },
16 | { name: "Petrol", sector: "Energy", value: 1825 },
17 | { name: "Bonds", sector: "Financials", value: 2517 },
18 | { name: "Loans", sector: "Financials", value: 1213 },
19 | { name: "Loans", sector: "Financials", value: 613 },
20 | { name: "Petrol", sector: "Energy", value: 825 },
21 | { name: "Bonds", sector: "Financials", value: 1817 },
22 | { name: "Loans", sector: "Financials", value: 1213 },
23 | { name: "Loans", sector: "Utilities", value: 213 },
24 | ];
25 |
26 | const colors = [
27 | "text-pink-400",
28 | "text-violet-500",
29 | "text-lime-500",
30 | "text-sky-400",
31 | "text-orange-400",
32 | ];
33 | export function Live4_BubbleChart_DIV() {
34 | // Define scales
35 | const color = d3.scaleOrdinal(colors);
36 | const strokeSize = 1;
37 | // Define pack layout
38 | const pack = d3.pack().size([1000, 1000]).padding(12);
39 |
40 | // Compute hierarchy and apply pack layout
41 | const root = pack(
42 | d3.hierarchy({ children: data }).sum((d: any) => d.value) as HierarchyNode
43 | );
44 |
45 | // Create nodes
46 | const nodes = root.leaves().map((d) => {
47 | const x = d.x;
48 | const y = d.y;
49 | const r = d.r;
50 | const fill = color((d.data as any).sector);
51 | const name = (d.data as any).name;
52 | const value = (d.data as any).value;
53 |
54 | return {
55 | x,
56 | y,
57 | r,
58 | fill,
59 | name,
60 | value,
61 | };
62 | });
63 |
64 | return (
65 |
66 | {nodes.map((node, i) => (
67 |
81 | {node.value > 1000 && (
82 | <>
83 |
90 | {node.name}
91 |
92 |
99 | {d3.format(",d")(node.value)}
100 |
101 | >
102 | )}
103 |
104 | ))}
105 |
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/other-charts/5_FunnelChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from "react";
2 |
3 | const data = [
4 | {
5 | key: "Gross Revenue",
6 | value: 47.1,
7 | color: "from-pink-300 to-pink-400 dark:from-pink-500 dark:to-pink-700",
8 | },
9 | {
10 | key: "Net Revenue",
11 | value: 32.3,
12 | color: "from-purple-400 to-purple-500 dark:from-purple-500 dark:to-purple-700",
13 | },
14 | {
15 | key: "EBITDA",
16 | value: 27.1,
17 | color: "from-indigo-400 to-indigo-500 dark:from-indigo-500 dark:to-indigo-700",
18 | },
19 | {
20 | key: "Gross Profit",
21 | value: 17.5,
22 | color: "from-sky-400 to-sky-500 dark:from-sky-500 dark:to-sky-700",
23 | },
24 | {
25 | key: "Net Profit",
26 | value: 12.7,
27 | color: "from-orange-300 to-orange-400 dark:from-amber-500 dark:to-amber-700",
28 | },
29 | ].sort((a, b) => b.value - a.value);
30 |
31 | export function Live5_FunnelChart() {
32 | const gap = 0.3; // gap between bars
33 | const maxFrequency = Math.max(...data.map((d) => d.value));
34 | const barHeight = 54;
35 | let cumulativeHeight = 0;
36 |
37 | return (
38 |
50 | {/* Bars with Gradient Fill and Lighter Stroke for 3D Effect */}
51 | {data.map((d, index) => {
52 | const barWidth = (d.value / maxFrequency) * 100;
53 | const yPosition = cumulativeHeight;
54 | cumulativeHeight += barHeight + gap + 2;
55 |
56 | return (
57 |
69 |
80 | {d.key}
81 |
82 |
94 | {d.value}
95 |
96 |
97 | );
98 | })}
99 |
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/pie-charts/1_PieChart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { pie, arc, PieArcDatum } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | type DataItem = {
6 | name: string;
7 | value: number;
8 | };
9 |
10 | const data: DataItem[] = [
11 | {
12 | name: "Rent",
13 | value: 731,
14 | },
15 | {
16 | name: "Food",
17 | value: 631,
18 | },
19 | {
20 | name: "Household",
21 | value: 331,
22 | },
23 | {
24 | name: "Transportation",
25 | value: 232,
26 | },
27 | {
28 | name: "Entertainment",
29 | value: 101,
30 | },
31 | {
32 | name: "Other",
33 | value: 42,
34 | },
35 | ];
36 |
37 | export function Live1_PieChart() {
38 | // Chart dimensions
39 | const radius = Math.PI * 100;
40 | const gap = 0.02; // Gap between slices
41 |
42 | // Pie layout and arc generator
43 | const pieLayout = pie()
44 | .value((d) => d.value)
45 | .padAngle(gap); // Creates a gap between slices
46 |
47 | const arcGenerator = arc>()
48 | .innerRadius(20)
49 | .outerRadius(radius)
50 | .cornerRadius(8);
51 |
52 | const labelRadius = radius * 0.8;
53 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
54 |
55 | const arcs = pieLayout(data);
56 |
57 | // Calculate the angle for each slice
58 | const computeAngle = (d: PieArcDatum) => {
59 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
60 | };
61 |
62 | // Minimum angle to display text
63 | const minAngle = 20; // Adjust this value as needed
64 |
65 | const gradients = [
66 | { id: "gradient-green", colors: ["#F5A5DB", "green"] },
67 | { id: "gradient-green", colors: ["#B89DFB", "green"] },
68 | { id: "gradient-green", colors: ["#758bcf", "green"] },
69 | { id: "gradient-orange", colors: ["#33C2EA", "#EC8E7B"] },
70 | { id: "gradient-blue", colors: ["#FFC182", "#7DCEDC"] },
71 | { id: "gradient-purple", colors: ["#73DC5A", "#CF91D8"] },
72 | { id: "gradient-green", colors: ["#B89DFB", "green"] },
73 | { id: "gradient-green", colors: ["#2999F5", "green"] },
74 | ];
75 |
76 | return (
77 |
78 |
79 |
92 | {/* Labels as absolutely positioned divs */}
93 | {arcs.map((d: PieArcDatum
, i) => {
94 | const angle = computeAngle(d);
95 | if (angle <= minAngle) return null;
96 |
97 | let centroid = arcLabel.centroid(d);
98 |
99 | // Converts SVG coordinates to container-relative 0-100% positioning. Tweak as needed.
100 | const leftLabel = `${50 + (centroid[0] / radius) * 70}%`;
101 | const topLabel = `${50 + (centroid[1] / radius) * 70}%`;
102 | const leftLogo = `${50 + (centroid[0] / radius) * 40}%`;
103 | const topLogo = `${50 + (centroid[1] / radius) * 40}%`;
104 |
105 | return (
106 |
107 |
111 | {d.data.value}
112 |
113 |
114 | );
115 | })}
116 |
117 |
118 | );
119 | }
120 |
--------------------------------------------------------------------------------
/pie-charts/2_PieChartStocks.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { pie, arc, PieArcDatum } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | type DataItem = {
6 | name: string;
7 | value: number;
8 | logo: string;
9 | color: string;
10 | };
11 |
12 | const data: DataItem[] = [
13 | {
14 | name: "Apple",
15 | value: 731,
16 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1001/1001_494D5A_F7F7F7.svg",
17 | color: "text-pink-400",
18 | },
19 | {
20 | name: "Mercedes",
21 | value: 631,
22 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1206/1206_2F3350_F7F7F7.svg",
23 | color: "text-purple-400",
24 | },
25 | {
26 | name: "Palantir",
27 | value: 331,
28 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/7991/7991_2C2C2C_F7F7F7.svg",
29 | color: "text-indigo-400",
30 | },
31 | {
32 | name: "Google",
33 | value: 232,
34 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1002/1002_3183FF_F7F7F7.svg",
35 | color: "text-sky-400",
36 | },
37 | {
38 | name: "Tesla",
39 | value: 101,
40 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1007/1007_F7F7F7_2C2C2C.svg",
41 | color: "text-lime-400",
42 | },
43 | {
44 | name: "Meta",
45 | value: 42,
46 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1008/1008_F7F7F7_2C2C2C.svg",
47 | color: "text-amber-400",
48 | },
49 | ];
50 |
51 | export function Live2_PieChartStocks() {
52 | // Chart dimensions
53 | const radius = Math.PI * 100;
54 | const gap = 0.02; // Gap between slices
55 |
56 | // Pie layout and arc generator
57 | const pieLayout = pie()
58 | .value((d) => d.value)
59 | .padAngle(gap); // Creates a gap between slices
60 |
61 | const arcGenerator = arc>()
62 | .innerRadius(20)
63 | .outerRadius(radius)
64 | .cornerRadius(8);
65 |
66 | const labelRadius = radius * 0.8;
67 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
68 |
69 | const arcs = pieLayout(data);
70 |
71 | // Calculate the angle for each slice
72 | const computeAngle = (d: PieArcDatum) => {
73 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
74 | };
75 |
76 | // Minimum angle to display text
77 | const MIN_ANGLE = 20;
78 |
79 | return (
80 |
81 |
82 |
125 |
126 | {/* Labels as absolutely positioned divs */}
127 |
128 | {arcs.map((d: PieArcDatum
, i) => {
129 | const angle = computeAngle(d);
130 |
131 | // Get pie center position
132 | const [x, y] = arcLabel.centroid(d);
133 | const CENTER_PCT = 50;
134 |
135 | // Convert to percentage positions. Adjust magic numbers to move the labels around
136 | const logoLeft = `${CENTER_PCT + (x / radius) * 40}%`;
137 | const logoTop = `${CENTER_PCT + (y / radius) * 40}%`;
138 |
139 | const valueLeft = `${CENTER_PCT + (x / radius) * 74}%`;
140 | const valueTop = `${CENTER_PCT + (y / radius) * 72}%`;
141 |
142 | return (
143 |
144 |
148 | {d.data.value}
149 |
150 | {angle >= MIN_ANGLE && (
151 |
155 |

156 |
157 | )}
158 |
159 | );
160 | })}
161 |
162 |
163 |
164 | );
165 | }
166 |
--------------------------------------------------------------------------------
/pie-charts/3_PieChartLabels.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { pie, arc, PieArcDatum } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | type DataItem = {
6 | name: string;
7 | value: number;
8 | colorFrom: string;
9 | colorTo: string;
10 | };
11 |
12 | const data: DataItem[] = [
13 | {
14 | name: "Technology",
15 | value: 731,
16 | colorFrom: "text-pink-400",
17 | colorTo: "text-pink-400",
18 | },
19 | { name: "Industrials", value: 631, colorFrom: "text-purple-400", colorTo: "text-purple-400" },
20 | { name: "Cyclical", value: 331, colorFrom: "text-indigo-400", colorTo: "text-indigo-400" },
21 | { name: "Energy", value: 232, colorFrom: "text-sky-400", colorTo: "text-sky-400" },
22 | { name: "Defensive", value: 101, colorFrom: "text-lime-400", colorTo: "text-lime-400" },
23 | { name: "Financials", value: 42, colorFrom: "text-amber-400", colorTo: "text-amber-400" },
24 | ];
25 |
26 | export function Live3_PieChartLabels() {
27 | // Chart dimensions
28 | const radius = Math.PI * 100;
29 | const gap = 0.02; // Gap between slices
30 |
31 | // Pie layout and arc generator
32 | const pieLayout = pie()
33 | .sort(null)
34 | .value((d) => d.value)
35 | .padAngle(gap); // Creates a gap between slices
36 |
37 | const arcGenerator = arc>()
38 | .innerRadius(20)
39 | .outerRadius(radius)
40 | .cornerRadius(8);
41 |
42 | const labelRadius = radius * 0.8;
43 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
44 |
45 | const arcs = pieLayout(data);
46 | // Calculate the angle for each slice
47 | const computeAngle = (d: PieArcDatum) => {
48 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
49 | };
50 |
51 | // Minimum angle to display text
52 | const MIN_ANGLE = 20;
53 |
54 | return (
55 |
56 |
57 |
93 |
94 | {/* Labels as absolutely positioned divs */}
95 |
96 | {arcs.map((d: PieArcDatum
, i) => {
97 | const angle = computeAngle(d);
98 | if (angle <= MIN_ANGLE) return null;
99 |
100 | // Get pie center position
101 | const [x, y] = arcLabel.centroid(d);
102 | const CENTER_PCT = 50;
103 |
104 | // Convert to percentage positions. Adjust magic numbers to move the labels around
105 | const nameLeft = `${CENTER_PCT + (x / radius) * 40}%`;
106 | const nameTop = `${CENTER_PCT + (y / radius) * 40}%`;
107 |
108 | const valueLeft = `${CENTER_PCT + (x / radius) * 72}%`;
109 | const valueTop = `${CENTER_PCT + (y / radius) * 70}%`;
110 |
111 | return (
112 |
113 |
117 | {d.data.value}
118 |
119 |
0 ? "2px" : "-2px",
126 | marginTop: y > 0 ? "2px" : "-2px",
127 | }}
128 | >
129 | {d.data.name}
130 |
131 |
132 | );
133 | })}
134 |
135 |
136 |
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/pie-charts/4_DonutChart.tsx:
--------------------------------------------------------------------------------
1 | import { pie, arc, PieArcDatum } from "d3";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | type Item = { name: string; value: number };
5 | const data: Item[] = [
6 | { name: "AAPL", value: 30 },
7 | { name: "BTC", value: 22 },
8 | { name: "GOLD", value: 11 },
9 | { name: "PLTR", value: 9 },
10 | { name: "ADA", value: 7 },
11 | { name: "MSFT", value: 3 },
12 | ];
13 |
14 | export function Live4_DonutChart() {
15 | const radius = 420; // Chart base dimensions
16 | const gap = 0.01; // Gap between slices
17 | const lightStrokeEffect = 10; // 3d light effect around the slice
18 |
19 | // Pie layout and arc generator
20 | const pieLayout = pie- ()
21 | .value((d) => d.value)
22 | .padAngle(gap); // Creates a gap between slices
23 |
24 | // Adjust innerRadius to create a donut shape
25 | const innerRadius = radius / 1.625;
26 | const arcGenerator = arc>()
27 | .innerRadius(innerRadius)
28 | .outerRadius(radius)
29 | .cornerRadius(lightStrokeEffect + 2);
30 |
31 | // Create an arc generator for the clip path that matches the outer path of the arc
32 | const arcClip =
33 | arc>()
34 | .innerRadius(innerRadius + lightStrokeEffect / 2)
35 | .outerRadius(radius)
36 | .cornerRadius(lightStrokeEffect + 2) || undefined;
37 |
38 | const labelRadius = radius * 0.825;
39 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
40 |
41 | const arcs = pieLayout(data);
42 |
43 | // Calculate the angle for each slice
44 | function computeAngle(d: PieArcDatum
- ) {
45 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
46 | }
47 |
48 | // Minimum angle to display text
49 | const minAngle = 20; // Adjust this value as needed
50 |
51 | const colors = ["#7e4cfe", "#895cfc", "#956bff", "#a37fff", "#b291fd", "#b597ff"];
52 |
53 | return (
54 |
55 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/pie-charts/5_DonutChartCenterText.tsx:
--------------------------------------------------------------------------------
1 | import { pie, arc, PieArcDatum } from "d3";
2 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
3 |
4 | type Item = { name: string; value: number };
5 | const data: Item[] = [
6 | { name: "AAPL", value: 30 },
7 | { name: "BTC", value: 22 },
8 | { name: "GOLD", value: 11 },
9 | { name: "PLTR", value: 9 },
10 | { name: "ADA", value: 7 },
11 | { name: "MSFT", value: 3 },
12 | ];
13 |
14 | export function Live5_DonutChartCenterText() {
15 | const radius = 420; // Chart base dimensions
16 | const gap = 0.01; // Gap between slices
17 | const lightStrokeEffect = 10; // 3d light effect around the slice
18 |
19 | // Pie layout and arc generator
20 | const pieLayout = pie- ()
21 | .value((d) => d.value)
22 | .padAngle(gap); // Creates a gap between slices
23 |
24 | // Adjust innerRadius to create a donut shape
25 | const innerRadius = radius / 1.625;
26 | const arcGenerator = arc>()
27 | .innerRadius(innerRadius)
28 | .outerRadius(radius)
29 | .cornerRadius(lightStrokeEffect + 2); // Apply rounded corners
30 |
31 | // Create an arc generator for the clip path that matches the outer path of the arc
32 | const arcClip =
33 | arc>()
34 | .innerRadius(innerRadius + lightStrokeEffect / 2)
35 | .outerRadius(radius)
36 | .cornerRadius(lightStrokeEffect + 2) || undefined;
37 |
38 | const labelRadius = radius * 0.825;
39 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
40 |
41 | const arcs = pieLayout(data);
42 |
43 | // Calculate the angle for each slice
44 | function computeAngle(d: PieArcDatum
- ) {
45 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
46 | }
47 |
48 | // Minimum angle to display text
49 | const minAngle = 20; // Adjust this value as needed
50 |
51 | const colors = ["#7e4cfe", "#895cfc", "#956bff", "#a37fff", "#b291fd", "#b597ff"]; // add more colors if needed
52 |
53 | return (
54 |
55 | {/* Add a new div for centered text */}
56 |
62 |
123 |
124 | );
125 | }
126 |
--------------------------------------------------------------------------------
/pie-charts/6_DonutChartHalf.tsx:
--------------------------------------------------------------------------------
1 | import { pie, arc, PieArcDatum } from "d3";
2 |
3 | type Item = { name: string; value: number };
4 | const data: Item[] = [
5 | { name: "AAPL", value: 30 },
6 | { name: "BTC", value: 22 },
7 | { name: "GOLD", value: 11 },
8 | { name: "PLTR", value: 9 },
9 | { name: "ADA", value: 7 },
10 | { name: "MSFT", value: 3 },
11 | ];
12 |
13 | export function Live6_DonutChartHalf() {
14 | const radius = 420; // Chart base dimensions
15 | const gap = 0.01; // Gap between slices
16 | const lightStrokeEffect = 10; // 3d light effect around the slice
17 |
18 | // Modify the pie layout to create a half donut
19 | const pieLayout = pie- ()
20 | .value((d) => d.value)
21 | .padAngle(gap)
22 | .startAngle(-Math.PI / 2) // Start at -90 degrees
23 | .endAngle(Math.PI / 2); // End at 90 degrees
24 |
25 | // Adjust innerRadius to create a donut shape
26 | const innerRadius = radius / 1.625;
27 | const arcGenerator = arc>()
28 | .innerRadius(innerRadius)
29 | .outerRadius(radius)
30 | .cornerRadius(lightStrokeEffect + 2);
31 |
32 | // Create an arc generator for the clip path that matches the outer path of the arc
33 | const arcClip =
34 | arc>()
35 | .innerRadius(innerRadius + lightStrokeEffect / 2)
36 | .outerRadius(radius)
37 | .cornerRadius(lightStrokeEffect + 2) || undefined;
38 |
39 | const labelRadius = (innerRadius + radius) / 2;
40 | const arcLabel = arc>().innerRadius(labelRadius).outerRadius(labelRadius);
41 |
42 | const arcs = pieLayout(data);
43 |
44 | // Calculate the angle for each slice
45 | function computeAngle(d: PieArcDatum
- ) {
46 | return ((d.endAngle - d.startAngle) * 180) / Math.PI;
47 | }
48 |
49 | // Minimum angle to display text
50 | const minAngle = 18; // Adjust this value as needed
51 |
52 | const colors = ["#7e4cfe", "#895cfc", "#956bff", "#a37fff", "#b291fd", "#b597ff"];
53 |
54 | return (
55 |
56 |
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/pie-charts/7_DonutChartFillableHalf.tsx:
--------------------------------------------------------------------------------
1 | import { pie, arc, PieArcDatum } from "d3";
2 |
3 | type Item = { name: string; value: number };
4 |
5 | export function Live7_DonutChartFillableHalf() {
6 | const radius = 420; // Chart base dimensions
7 | const lightStrokeEffect = 10; // 3d light effect around the slice
8 |
9 | // Update the data order to fill clockwise
10 | const data = [
11 | { name: "Filled", value: 31 },
12 | { name: "Empty", value: 70 },
13 | ];
14 |
15 | // Modify the pie layout to create a half donut filling clockwise from left to right
16 | const pieLayout = pie- ()
17 | .value((d) => d.value)
18 | .startAngle(-Math.PI / 2) // Start at -90 degrees (9 o'clock)
19 | .endAngle(Math.PI / 2)
20 | .sort((a, b) => a.value - b.value)
21 | .padAngle(0.0);
22 |
23 | // Adjust innerRadius to create a donut shape
24 | const innerRadius = radius / 1.625;
25 | const arcGenerator = arc>().innerRadius(innerRadius).outerRadius(radius);
26 |
27 | // Create an arc generator for the clip path that matches the outer path of the arc
28 | const arcClip =
29 | arc>()
30 | .innerRadius(innerRadius + lightStrokeEffect / 2)
31 | .outerRadius(radius)
32 | .cornerRadius(lightStrokeEffect + 2) || undefined;
33 |
34 | const arcs = pieLayout(data);
35 |
36 | const colors = {
37 | gray: "fill-[#e0e0e0] dark:fill-zinc-700",
38 | purple: "fill-violet-600 dark:fill-violet-500",
39 | };
40 |
41 | return (
42 |
43 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/pie-charts/8_DonutChartFillable.tsx:
--------------------------------------------------------------------------------
1 | import { pie, arc, PieArcDatum } from "d3";
2 |
3 | type Item = { name: string; value: number };
4 |
5 | export function Live8_DonutChartFillable() {
6 | const radius = 420; // Chart base dimensions
7 | const lightStrokeEffect = 10; // 3d light effect around the slice
8 |
9 | // Update the data order to fill clockwise
10 | const data = [
11 | { name: "Filled", value: 31 },
12 | { name: "Empty", value: 70 },
13 | ];
14 |
15 | // Modify the pie layout to create a full donut filling clockwise from 12 o'clock
16 | const pieLayout = pie- ()
17 | .value((d) => d.value)
18 | .startAngle(0) // Start at 0 degrees (12 o'clock)
19 | .endAngle(2 * Math.PI) // End at 360 degrees (12 o'clock again)
20 | .sort((a, b) => a.value - b.value)
21 | .padAngle(0.0);
22 |
23 | // Adjust innerRadius to create a donut shape
24 | const innerRadius = radius / 1.625;
25 | const arcGenerator = arc>().innerRadius(innerRadius).outerRadius(radius);
26 |
27 | // Create an arc generator for the clip path that matches the outer path of the arc
28 | const arcClip =
29 | arc>()
30 | .innerRadius(innerRadius + lightStrokeEffect / 2)
31 | .outerRadius(radius)
32 | .cornerRadius(lightStrokeEffect + 2) || undefined;
33 |
34 | const arcs = pieLayout(data);
35 |
36 | const colors = {
37 | purple: "fill-violet-600 dark:fill-violet-500",
38 | gray: "fill-[#e0e0e0] dark:fill-zinc-700",
39 | };
40 |
41 | return (
42 |
43 |
69 | {/* Centered value display */}
70 |
71 |
Filled
72 |
73 | {data[0].value}
74 | / 100
75 |
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/radar-charts/6_RadarChart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 |
4 | const rawData = [
5 | { topic: "Tech", value: 330 },
6 | { topic: "Financials", value: 160 },
7 | { topic: "Energy", value: 140 },
8 | { topic: "Healthcare", value: 200 },
9 | { topic: "Utilities", value: 180 },
10 | ];
11 | const maxValue = Math.max(...rawData.map((d) => d.value));
12 | const numAxes = rawData.length;
13 |
14 | export function Live6_RadarChart() {
15 | const radius = 150;
16 | const angleSlice = (Math.PI * 2) / numAxes;
17 |
18 | // Scale for the radius
19 | const rScale = d3.scaleLinear().range([0, radius]).domain([0, maxValue]);
20 |
21 | // Create the radar line
22 | const radarLine = d3
23 | .lineRadial<{ value: number }>()
24 | .radius((d) => rScale(d.value))
25 | .angle((d, i) => i * angleSlice)
26 | .curve(d3.curveLinearClosed); // Ensure the path is closed
27 |
28 | // Generate the radar chart path
29 | const radarPath = radarLine(rawData);
30 |
31 | return (
32 |
33 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/radar-charts/8_RadarChartRounded.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { curveCardinalClosed, lineRadial, scaleLinear } from "d3";
3 |
4 | const rawData = [
5 | { topic: "Tech", value: 320 },
6 | { topic: "Financials", value: 190 },
7 | { topic: "Energy", value: 170 },
8 | { topic: "Healthcare", value: 250 },
9 | { topic: "Utilities", value: 210 },
10 | ];
11 | const maxValue = Math.max(...rawData.map((d) => d.value));
12 | const numAxes = rawData.length;
13 |
14 | export function Live8_RadarChartRounded() {
15 | const radius = 150;
16 | const angleSlice = (Math.PI * 2) / numAxes;
17 |
18 | // Scale for the radius
19 | const rScale = scaleLinear().range([0, radius]).domain([0, maxValue]);
20 |
21 | // Create the radar line
22 | const radarLine = lineRadial<{ value: number }>()
23 | .radius((d) => rScale(d.value))
24 | .angle((d, i) => i * angleSlice)
25 | .curve(curveCardinalClosed); // Ensure the path is closed
26 |
27 | // Generate the radar chart path
28 | const radarPath = radarLine(rawData);
29 |
30 | return (
31 |
32 |
36 | {/* */}
37 |
38 | {/* Draw concentric circles */}
39 | {[...Array(5)].map((_, i) => {
40 | const circleRadius = (radius / 5) * (i + 1);
41 | return (
42 |
43 |
56 |
57 | );
58 | })}
59 |
60 | {/* Add labels to each circle */}
61 | {/* {rawData.map((d, i) => (
62 |
71 | {d.value}
72 |
73 | ))} */}
74 |
75 | {/* Draw axes */}
76 | {/* {rawData.map((d, i) => {
77 | if (i === 0) return null; // hide the first axis so it doesn't cover the labels
78 | return (
79 |
87 | );
88 | })} */}
89 |
90 | {/* Draw the radar chart path */}
91 |
92 |
93 | {/* Draw circles for each data point */}
94 | {rawData.map((d, i) => (
95 |
96 |
103 |
111 | {d.value}
112 |
113 |
114 | ))}
115 |
116 | {/* Add labels for each axis */}
117 | {rawData.map((d, i) => {
118 | const angle = (angleSlice * i * 180) / Math.PI;
119 | const x = (rScale(maxValue) + 10) * Math.cos(angleSlice * i - Math.PI / 2);
120 | const y = (rScale(maxValue) + 10) * Math.sin(angleSlice * i - Math.PI / 2);
121 | const adjustedAngle = angle > 90 && angle < 270 ? angle + 180 : angle;
122 |
123 | return (
124 | 90 && angle < 270 ? y + 20 : y - 15}
128 | textAnchor="middle"
129 | fontSize="14px"
130 | className="fill-lime-700 dark:fill-lime-300"
131 | transform={`rotate(${adjustedAngle}, ${x}, ${y})`}
132 | >
133 | {d.topic}
134 |
135 | );
136 | })}
137 |
138 |
139 |
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/scatter-charts/1_ScatterChart.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleLinear, max, min } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { revenue: 10, value: 102.8, company: "Company A" },
7 | { revenue: 20, value: 101.9, company: "Company B" },
8 | { revenue: 30, value: 101.5, company: "Company C" },
9 | { revenue: 40, value: 100.2, company: "Company D" },
10 | { revenue: 50, value: 100.8, company: "Company E" },
11 | { revenue: 60, value: 99.7, company: "Company F" },
12 | { revenue: 70, value: 99.9, company: "Company G" },
13 | { revenue: 80, value: 98.5, company: "Company H" },
14 | { revenue: 90, value: 98.9, company: "Company I" },
15 | { revenue: 100, value: 97.8, company: "Company J" },
16 | { revenue: 110, value: 98.2, company: "Company K" },
17 | { revenue: 120, value: 96.8, company: "Company L" },
18 | { revenue: 130, value: 96.9, company: "Company M" },
19 | { revenue: 140, value: 95.5, company: "Company N" },
20 | { revenue: 150, value: 95.9, company: "Company O" },
21 | { revenue: 160, value: 94.5, company: "Company P" },
22 | { revenue: 170, value: 94.8, company: "Company Q" },
23 | { revenue: 180, value: 93.9, company: "Company R" },
24 | { revenue: 190, value: 94.3, company: "Company S" },
25 | { revenue: 200, value: 93.5, company: "Company T" },
26 | ];
27 |
28 | export function Live1_ScatterChart() {
29 | let xScale = scaleLinear()
30 | .domain([data[0].revenue, data[data.length - 1].revenue])
31 | .range([0, 100]);
32 | let yScale = scaleLinear()
33 | .domain([(min(data.map((d) => d.value)) ?? 0) - 1, (max(data.map((d) => d.value)) ?? 0) + 1])
34 | .range([100, 0]);
35 |
36 | return (
37 |
48 | {/* Y axis */}
49 |
57 | {yScale
58 | .ticks(3)
59 | .map(yScale.tickFormat(3, "d"))
60 | .map((value, i) => (
61 |
69 | {value}
70 |
71 | ))}
72 |
73 |
74 | {/* Chart area */}
75 |
84 |
89 | {/* Horizontal grid lines */}
90 | {yScale
91 | .ticks(8)
92 | .map(yScale.tickFormat(8, "d"))
93 | .map((active, i) => (
94 |
99 |
107 |
108 | ))}
109 |
110 | {/* Vertical grid lines */}
111 | {xScale.ticks(8).map((active, i) => (
112 |
117 |
125 |
126 | ))}
127 |
128 | {/* Circles and Tooltips */}
129 | {data.map((d, index) => (
130 |
131 |
132 |
133 |
143 |
154 | {/* Invisible area closest to a specific point for the tooltip trigger */}
155 | {
157 | const prevX = index > 0 ? xScale(data[index - 1].revenue) : xScale(d.revenue);
158 | return (prevX + xScale(d.revenue)) / 2;
159 | })()}
160 | y={0}
161 | width={(() => {
162 | const prevX = index > 0 ? xScale(data[index - 1].revenue) : xScale(d.revenue);
163 | const nextX =
164 | index < data.length - 1
165 | ? xScale(data[index + 1].revenue)
166 | : xScale(d.revenue);
167 | const leftBound = (prevX + xScale(d.revenue)) / 2;
168 | const rightBound = (xScale(d.revenue) + nextX) / 2;
169 | return rightBound - leftBound;
170 | })()}
171 | height={100}
172 | fill="transparent"
173 | className="cursor-pointer"
174 | />
175 |
176 |
177 |
178 | {d.company}
179 |
180 | {d.value} / {d.revenue}
181 |
182 |
183 |
184 | ))}
185 |
186 | {/* X Axis */}
187 |
188 | {data.map((d, i) => {
189 | const isFirst = i === 0;
190 | const isLast = i === data.length - 1;
191 | if (!isFirst && !isLast && i % 5 !== 0) return null;
192 | return (
193 |
194 |
204 | {d.revenue}
205 |
206 |
207 | );
208 | })}
209 |
210 |
211 |
212 | );
213 | }
214 |
--------------------------------------------------------------------------------
/scatter-charts/2_ScatterChartInteractive.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleLinear, max, min } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { revenue: 10, value: 102.8, company: "Company A" },
7 | { revenue: 20, value: 101.9, company: "Company B" },
8 | { revenue: 30, value: 101.5, company: "Company C" },
9 | { revenue: 40, value: 100.2, company: "Company D" },
10 | { revenue: 50, value: 100.8, company: "Company E" },
11 | { revenue: 60, value: 101.7, company: "Company F" },
12 | { revenue: 70, value: 99.9, company: "Company G" },
13 | { revenue: 80, value: 98.5, company: "Company H" },
14 | { revenue: 90, value: 101.9, company: "Company I" },
15 | { revenue: 100, value: 100.8, company: "Company J" },
16 | { revenue: 110, value: 98.2, company: "Company K" },
17 | { revenue: 120, value: 96.8, company: "Company L" },
18 | { revenue: 130, value: 101.9, company: "Company M" },
19 | { revenue: 140, value: 100.5, company: "Company N" },
20 | { revenue: 150, value: 101.9, company: "Company O" },
21 | { revenue: 160, value: 99.5, company: "Company P" },
22 | { revenue: 170, value: 102.8, company: "Company Q" },
23 | { revenue: 180, value: 98.9, company: "Company R" },
24 | ];
25 |
26 | export function Live2_ScatterChartInteractive() {
27 | let xScale = scaleLinear()
28 | .domain([data[0].revenue, data[data.length - 1].revenue])
29 | .range([0, 100]);
30 | let yScale = scaleLinear()
31 | .domain([(min(data.map((d) => d.value)) ?? 0) - 1, (max(data.map((d) => d.value)) ?? 0) + 1])
32 | .range([100, 0]);
33 |
34 | return (
35 |
46 | {/* Y axis */}
47 |
55 | {yScale
56 | .ticks(3)
57 | .map(yScale.tickFormat(3, "d"))
58 | .map((value, i) => (
59 |
67 | {value}
68 |
69 | ))}
70 |
71 |
72 | {/* Chart area */}
73 |
82 |
87 | {/* Horizontal grid lines */}
88 | {yScale
89 | .ticks(8)
90 | .map(yScale.tickFormat(8, "d"))
91 | .map((active, i) => (
92 |
97 |
105 |
106 | ))}
107 |
108 | {/* Vertical grid lines */}
109 | {xScale.ticks(8).map((active, i) => (
110 |
115 |
123 |
124 | ))}
125 |
126 | {/* Circles and Tooltips */}
127 | {data.map((d, index) => (
128 |
129 |
130 |
131 |
141 |
150 |
151 |
152 |
153 | {d.company}
154 |
155 | {d.value} / {d.revenue}
156 |
157 |
158 |
159 | ))}
160 |
161 | {/* X Axis */}
162 |
163 | {data.map((d, i) => {
164 | const isFirst = i === 0;
165 | const isLast = i === data.length - 1;
166 | if (!isFirst && !isLast && i % 5 !== 0) return null;
167 | return (
168 |
169 |
179 | {d.revenue}
180 |
181 |
182 | );
183 | })}
184 |
185 |
186 |
187 | );
188 | }
189 |
--------------------------------------------------------------------------------
/scatter-charts/5_ScatterChartMulticlass.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { scaleLinear, max, min } from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const data = [
6 | { revenue: 10, value: 102.8, company: "Green A" },
7 | { revenue: 20, value: 101.9, company: "Green B" },
8 | { revenue: 30, value: 101.5, company: "Green C" },
9 | { revenue: 40, value: 100.8, company: "Green D" },
10 | { revenue: 50, value: 99.7, company: "Green E" },
11 | { revenue: 60, value: 98.5, company: "Green F" },
12 | ];
13 |
14 | const data2 = [
15 | { revenue: 10, value: 98.3, company: "Blue A" },
16 | { revenue: 20, value: 102.7, company: "Blue B" },
17 | { revenue: 30, value: 97.4, company: "Blue C" },
18 | { revenue: 40, value: 99.2, company: "Blue D" },
19 | { revenue: 50, value: 103.8, company: "Blue E" },
20 | { revenue: 60, value: 96.5, company: "Blue F" },
21 | ];
22 |
23 | export function Live5_ScatterChartMulticlass() {
24 | let xScale = scaleLinear()
25 | .domain([data[0].revenue, data[data.length - 1].revenue])
26 | .range([0, 100]);
27 | let yScale = scaleLinear()
28 | .domain([
29 | (min(data.concat(data2).map((d) => d.value)) ?? 0) - 1,
30 | (max(data.concat(data2).map((d) => d.value)) ?? 0) + 1,
31 | ])
32 | .range([100, 0]);
33 |
34 | return (
35 |
46 | {/* Y axis */}
47 |
55 | {yScale
56 | .ticks(3)
57 | .map(yScale.tickFormat(3, "d"))
58 | .map((value, i) => (
59 |
67 | {value}
68 |
69 | ))}
70 |
71 |
72 | {/* Chart area */}
73 |
82 |
87 | {/* Horizontal grid lines */}
88 | {yScale
89 | .ticks(8)
90 | .map(yScale.tickFormat(8, "d"))
91 | .map((active, i) => (
92 |
97 |
105 |
106 | ))}
107 |
108 | {/* Vertical grid lines */}
109 | {xScale.ticks(8).map((active, i) => (
110 |
115 |
123 |
124 | ))}
125 |
126 | {/* Circles and Tooltips */}
127 | {data.map((d, index) => (
128 |
129 |
130 |
131 |
141 |
150 |
151 |
152 |
153 | {d.company}
154 |
155 | {d.value} / {d.revenue}
156 |
157 |
158 |
159 | ))}
160 |
161 | {/* Circles 2 and Tooltips */}
162 | {data2.map((d, index) => (
163 |
164 |
165 |
166 |
176 |
185 |
186 |
187 |
188 | {d.company}
189 |
190 | {d.value} / {d.revenue}
191 |
192 |
193 |
194 | ))}
195 |
196 | {/* X Axis */}
197 |
198 | {data.map((d, i) => {
199 | const isFirst = i === 0;
200 | const isLast = i === data.length - 1;
201 | if (!isFirst && !isLast && i % 5 !== 0) return null;
202 | return (
203 |
204 |
214 | {d.revenue}
215 |
216 |
217 | );
218 | })}
219 |
220 |
221 |
222 | );
223 | }
224 |
--------------------------------------------------------------------------------
/treemap-charts/1_TreemapChart_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | const rawData = [
6 | { topic: "Tech", subtopics: [{ Windows: 100, MacOS: 120, Linux: 110 }] },
7 | { topic: "Financials", subtopics: [{ Loans: 60, Bonds: 80, PPRs: 20 }] },
8 | { topic: "Energy", subtopics: [{ Petrol: 70, Diesel: 50, Hydrogen: 20 }] },
9 | ];
10 | const colors = [
11 | "bg-violet-500 dark:bg-violet-500",
12 | "bg-pink-400 dark:bg-pink-400",
13 | "bg-orange-400 dark:bg-orange-400",
14 | ];
15 |
16 | export function Live1_TreemapChart_DIV() {
17 | // Transform the raw data into a hierarchical structure
18 | const data = {
19 | name: "root",
20 | children: rawData.map((topic) => ({
21 | name: topic.topic,
22 | children: Object.entries(topic.subtopics[0]).map(([name, value]) => ({
23 | name,
24 | value,
25 | })),
26 | })),
27 | };
28 |
29 | // Create root node
30 | const root = d3
31 | .hierarchy(data)
32 | .sum((d: any) => d.value)
33 | .sort((a: any, b: any) => (b.value ?? 0) - (a.value ?? 0));
34 |
35 | // Compute the treemap layout
36 | d3
37 | .treemap()
38 | .size([100, 100])
39 | .paddingInner(0.75) // Padding between subtopics
40 | .paddingOuter(1) // Padding between topics
41 | .round(false)(root as d3.HierarchyNode);
42 |
43 | // Color scale
44 | const color = d3
45 | .scaleOrdinal()
46 | .domain(rawData.map((d) => d.topic))
47 | .range(colors);
48 |
49 | return (
50 |
51 | {root.leaves().map((leaf: any, i) => {
52 | const leafWidth = leaf.x1 - leaf.x0;
53 | const leafHeight = leaf.y1 - leaf.y0;
54 | const VISIBLE_TEXT_WIDTH = 15;
55 | const VISIBLE_TEXT_HEIGHT = 15;
56 | return (
57 |
58 |
59 |
76 | {leafWidth > VISIBLE_TEXT_WIDTH && leafHeight > VISIBLE_TEXT_HEIGHT && (
77 |
{leaf.data.name}
78 | )}
79 | {leafWidth > VISIBLE_TEXT_WIDTH && leafHeight > VISIBLE_TEXT_HEIGHT && (
80 |
{leaf.value}
81 | )}
82 |
83 |
84 |
85 | {leaf.data.name}
86 | {leaf.value}
87 |
88 |
89 | );
90 | })}
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/treemap-charts/2_TreemapChartImages_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | interface ExtendedHierarchyNode extends d3.HierarchyNode {
6 | x0: number;
7 | x1: number;
8 | y0: number;
9 | y1: number;
10 | }
11 |
12 | type Subtopic = {
13 | name: string;
14 | value: number;
15 | logo: string;
16 | };
17 |
18 | type Topic = {
19 | topic: string;
20 | subtopics: Subtopic[];
21 | };
22 |
23 | type HierarchyData = {
24 | name: string;
25 | value: number;
26 | logo: string;
27 | children: {
28 | name: string;
29 | children: Subtopic[];
30 | }[];
31 | };
32 |
33 | const rawData: Topic[] = [
34 | {
35 | topic: "Tech",
36 | subtopics: [
37 | {
38 | name: "Apple",
39 | value: 100,
40 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1001/1001_494D5A_F7F7F7.svg",
41 | },
42 | {
43 | name: "Mercedes",
44 | value: 120,
45 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1206/1206_2F3350_F7F7F7.svg",
46 | },
47 | {
48 | name: "Palantir",
49 | value: 110,
50 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/7991/7991_2C2C2C_F7F7F7.svg",
51 | },
52 | ],
53 | },
54 | {
55 | topic: "Financials",
56 | subtopics: [
57 | {
58 | name: "Nvidia",
59 | value: 70,
60 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1137/1137_76B900_F7F7F7.svg",
61 | },
62 | {
63 | name: "AMD",
64 | value: 60,
65 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1832/1832_2C2C2C_F7F7F7.svg",
66 | },
67 | {
68 | name: "Google",
69 | value: 20,
70 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1002/1002_3183FF_F7F7F7.svg",
71 | },
72 | ],
73 | },
74 | {
75 | topic: "Energy",
76 | subtopics: [
77 | {
78 | name: "Apple",
79 | value: 70,
80 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1001/1001_494D5A_F7F7F7.svg",
81 | },
82 | {
83 | name: "Mercedes",
84 | value: 50,
85 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1206/1206_2F3350_F7F7F7.svg",
86 | },
87 | {
88 | name: "Palantir",
89 | value: 20,
90 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/7991/7991_2C2C2C_F7F7F7.svg",
91 | },
92 | {
93 | name: "Google",
94 | value: 100,
95 | logo: "https://etoro-cdn.etorostatic.com/market-avatars/1002/1002_3183FF_F7F7F7.svg",
96 | },
97 | ],
98 | },
99 | ];
100 | const colors = [
101 | "bg-violet-500 dark:bg-violet-500",
102 | "bg-pink-400 dark:bg-pink-400",
103 | "bg-orange-400 dark:bg-orange-400",
104 | ];
105 |
106 | const VISIBLE_TEXT_WIDTH = 10;
107 | const VISIBLE_TEXT_HEIGHT = 10;
108 |
109 | export function Live2_TreemapChartImages_DIV() {
110 | // Transform the raw data into a hierarchical structure
111 | const data: HierarchyData = {
112 | name: "root",
113 | value: 0,
114 | logo: "",
115 | children: rawData.map((topic) => ({
116 | name: topic.topic,
117 | children: topic.subtopics,
118 | })),
119 | };
120 |
121 | // Create root node
122 | const root = d3
123 | .hierarchy(data)
124 | .sum((d) => d.value)
125 | .sort((a, b) => b.value! - a.value!);
126 |
127 | // Compute the treemap layout
128 | d3
129 | .treemap()
130 | .size([100, 100])
131 | .paddingInner(0.75) // Padding between subtopics
132 | .paddingOuter(1) // Padding between topics
133 | .round(false)(root);
134 |
135 | const leaves = root.leaves() as ExtendedHierarchyNode[];
136 |
137 | // Color scale
138 | const color = d3
139 | .scaleOrdinal()
140 | .domain(rawData.map((d) => d.topic))
141 | .range(colors);
142 |
143 | return (
144 |
145 | {leaves.map((leaf, i) => {
146 | const leafWidth = leaf.x1 - leaf.x0;
147 | const leafHeight = leaf.y1 - leaf.y0;
148 |
149 | return (
150 |
151 |
152 |
170 | {leafWidth > VISIBLE_TEXT_WIDTH && leafHeight > VISIBLE_TEXT_HEIGHT && (
171 |

172 | )}
173 |
174 |
175 |
176 | {leaf.data.name}
177 | {leaf.value}
178 |
179 |
180 | );
181 | })}
182 |
183 | );
184 | }
185 |
--------------------------------------------------------------------------------
/treemap-charts/3_TreemapChartGradient_DIV.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 | import { ClientTooltip, TooltipContent, TooltipTrigger } from "../helpers/ClientTooltip";
4 |
5 | interface ExtendedHierarchyNode extends d3.HierarchyNode {
6 | x0: number;
7 | x1: number;
8 | y0: number;
9 | y1: number;
10 | }
11 |
12 | type Subtopic = {
13 | name: string;
14 | value: number;
15 | };
16 |
17 | type Topic = {
18 | topic: string;
19 | subtopics: Subtopic[];
20 | };
21 |
22 | type HierarchyData = {
23 | name: string;
24 | value: number;
25 | children: {
26 | name: string;
27 | children: Subtopic[];
28 | }[];
29 | };
30 |
31 | const rawData: Topic[] = [
32 | {
33 | topic: "Tech",
34 | subtopics: [
35 | {
36 | name: "Apple",
37 | value: 100,
38 | },
39 | {
40 | name: "Mercedes",
41 | value: 120,
42 | },
43 | {
44 | name: "Palantir",
45 | value: 110,
46 | },
47 | ],
48 | },
49 | {
50 | topic: "Financials",
51 | subtopics: [
52 | {
53 | name: "Tesla",
54 | value: 60,
55 | },
56 | {
57 | name: "Meta",
58 | value: 70,
59 | },
60 | {
61 | name: "Google",
62 | value: 20,
63 | },
64 | ],
65 | },
66 | {
67 | topic: "Energy",
68 | subtopics: [
69 | {
70 | name: "Apple",
71 | value: 70,
72 | },
73 | {
74 | name: "Mercedes",
75 | value: 50,
76 | },
77 | {
78 | name: "Palantir",
79 | value: 20,
80 | },
81 | {
82 | name: "Google",
83 | value: 100,
84 | },
85 | ],
86 | },
87 | ];
88 | const colors = [
89 | "bg-gradient-to-b from-purple-400 to-purple-500 text-white dark:from-purple-500 dark:to-purple-700 dark:text-purple-100",
90 | "bg-gradient-to-b from-pink-300 to-pink-400 text-white dark:from-pink-500 dark:to-pink-600 dark:text-pink-100",
91 | "bg-gradient-to-b from-orange-300 to-orange-400 text-white dark:from-amber-500 dark:to-amber-600 dark:text-amber-100",
92 | ];
93 |
94 | const VISIBLE_TEXT_WIDTH = 20;
95 | const VISIBLE_TEXT_HEIGHT = 23;
96 |
97 | export function Live3_TreemapChartGradient_DIV() {
98 | // Transform the raw data into a hierarchical structure
99 | const data: HierarchyData = {
100 | name: "root",
101 | value: 0,
102 | children: rawData.map((topic) => ({
103 | name: topic.topic,
104 | children: topic.subtopics,
105 | })),
106 | };
107 |
108 | // Create root node
109 | const root = d3
110 | .hierarchy(data)
111 | .sum((d) => d.value)
112 | .sort((a, b) => b.value! - a.value!);
113 |
114 | // Compute the treemap layout
115 | d3
116 | .treemap()
117 | .size([100, 100])
118 | .paddingInner(0.75) // Padding between subtopics
119 | .paddingOuter(1) // Padding between topics
120 | .round(false)(root);
121 |
122 | const leaves = root.leaves() as ExtendedHierarchyNode[];
123 |
124 | // Color scale
125 | const color = d3
126 | .scaleOrdinal()
127 | .domain(rawData.map((d) => d.topic))
128 | .range(colors);
129 |
130 | return (
131 |
132 | {leaves.map((leaf, i) => {
133 | const leafWidth = leaf.x1 - leaf.x0;
134 | const leafHeight = leaf.y1 - leaf.y0;
135 |
136 | return (
137 |
138 |
139 |
154 | {leafWidth > VISIBLE_TEXT_WIDTH && leafHeight > VISIBLE_TEXT_HEIGHT && (
155 |
{leaf.data.name}
156 | )}
157 | {leafWidth > VISIBLE_TEXT_WIDTH && leafHeight > VISIBLE_TEXT_HEIGHT && (
158 |
{leaf.value}
159 | )}
160 |
161 |
162 |
163 | {leaf.data.name}
164 | {leaf.value}
165 |
166 |
167 | );
168 | })}
169 |
170 | );
171 | }
172 |
--------------------------------------------------------------------------------