├── .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 | 94 | {/* Area */} 95 | 96 | 97 | {/* Line */} 98 | 106 | {/* Invisible Tooltip Area */} 107 | {data.map((d, index) => ( 108 | 109 | 110 | 111 | {/* Tooltip Line */} 112 | 123 | {/* Invisible area closest to a specific point for the tooltip trigger */} 124 | { 126 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 127 | return (prevX + xScale(d.date)) / 2; 128 | })()} 129 | y={0} 130 | width={(() => { 131 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 132 | const nextX = 133 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 134 | const leftBound = (prevX + xScale(d.date)) / 2; 135 | const rightBound = (xScale(d.date) + nextX) / 2; 136 | return rightBound - leftBound; 137 | })()} 138 | height={100} 139 | fill="transparent" 140 | className="cursor-pointer" 141 | /> 142 | 143 | 144 | 145 |
146 | {d.date.toLocaleDateString("en-US", { 147 | month: "short", 148 | day: "2-digit", 149 | })} 150 |
151 |
{d.value.toLocaleString("en-US")}
152 | 153 | 154 | ))} 155 | 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 | 94 | {/* Area */} 95 | 96 | 97 | {/* Gradient definition */} 98 | 99 | 104 | 109 | 110 | 111 | 112 | {/* Line */} 113 | 121 | {/* Invisible Tooltip Area */} 122 | {data.map((d, index) => ( 123 | 124 | 125 | 126 | {/* Tooltip Line */} 127 | 138 | {/* Invisible area closest to a specific point for the tooltip trigger */} 139 | { 141 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 142 | return (prevX + xScale(d.date)) / 2; 143 | })()} 144 | y={0} 145 | width={(() => { 146 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 147 | const nextX = 148 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 149 | const leftBound = (prevX + xScale(d.date)) / 2; 150 | const rightBound = (xScale(d.date) + nextX) / 2; 151 | return rightBound - leftBound; 152 | })()} 153 | height={100} 154 | fill="transparent" 155 | className="cursor-pointer" 156 | /> 157 | 158 | 159 | 160 |
161 | {d.date.toLocaleDateString("en-US", { 162 | month: "short", 163 | day: "2-digit", 164 | })} 165 |
166 |
{d.value.toLocaleString("en-US")}
167 | 168 | 169 | ))} 170 | 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 | 94 | {/* Area */} 95 | 96 | 97 | {/* Gradient definition */} 98 | 99 | 104 | 109 | 110 | 111 | 112 | {/* Line */} 113 | 121 | {/* Invisible Tooltip Area */} 122 | {data.map((d, index) => ( 123 | 124 | 125 | 126 | {/* Tooltip Line */} 127 | 138 | {/* Invisible area closest to a specific point for the tooltip trigger */} 139 | { 141 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 142 | return (prevX + xScale(d.date)) / 2; 143 | })()} 144 | y={0} 145 | width={(() => { 146 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 147 | const nextX = 148 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 149 | const leftBound = (prevX + xScale(d.date)) / 2; 150 | const rightBound = (xScale(d.date) + nextX) / 2; 151 | return rightBound - leftBound; 152 | })()} 153 | height={100} 154 | fill="transparent" 155 | className="cursor-pointer" 156 | /> 157 | 158 | 159 | 160 |
161 | {d.date.toLocaleDateString("en-US", { 162 | month: "short", 163 | day: "2-digit", 164 | })} 165 |
166 |
{d.value.toLocaleString("en-US")}
167 | 168 | 169 | ))} 170 | 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 | 89 | {/* Grid lines */} 90 | {yScale 91 | .ticks(8) 92 | .map(yScale.tickFormat(8, "d")) 93 | .map((active, i) => ( 94 | 99 | 107 | 108 | ))} 109 | 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 | 76 | {/* Grid lines */} 77 | {xScale 78 | .ticks(8) 79 | .map(xScale.tickFormat(8, "d")) 80 | .map((active, i) => ( 81 | 86 | 94 | 95 | ))} 96 | 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 | 208 | 216 | 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 | 82 | {/* Grid lines */} 83 | {xScale 84 | .ticks(8) 85 | .map(xScale.tickFormat(8, "d")) 86 | .map((active, i) => ( 87 | 92 | 100 | 101 | ))} 102 | 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 | 77 | {/* Grid lines */} 78 | {xScale 79 | .ticks(8) 80 | .map(xScale.tickFormat(8, "d")) 81 | .map((active, i) => ( 82 | 87 | 95 | 96 | ))} 97 | 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 | 137 | {/* Grid lines */} 138 | {xScale 139 | .ticks(8) 140 | .map(xScale.tickFormat(8, "d")) 141 | .map((active, i) => ( 142 | 147 | 155 | 156 | ))} 157 | {/* Bars with Rounded Right Corners */} 158 | 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 | 78 | {/* Grid lines */} 79 | {xScale 80 | .ticks(8) 81 | .map(xScale.tickFormat(8, "d")) 82 | .map((active, i) => ( 83 | 88 | 96 | 97 | ))} 98 | 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 | 92 | {/* Grid lines */} 93 | {yScale 94 | .ticks(8) 95 | .map(yScale.tickFormat(8, "d")) 96 | .map((active, i) => ( 97 | 102 | 110 | 111 | ))} 112 | 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 | 91 | {/* Grid lines */} 92 | {yScale 93 | .ticks(8) 94 | .map(yScale.tickFormat(8, "d")) 95 | .map((active, i) => ( 96 | 101 | 109 | 110 | ))} 111 | {/* Line */} 112 | 119 | 120 | {/* Circles and Tooltips */} 121 | {data.map((d, index) => ( 122 | 123 | 124 | 134 | 135 | {/* Tooltip Line */} 136 | 147 | {/* Invisible area closest to a specific point for the tooltip trigger */} 148 | { 150 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 151 | return (prevX + xScale(d.date)) / 2; 152 | })()} 153 | y={0} 154 | width={(() => { 155 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 156 | const nextX = 157 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 158 | const leftBound = (prevX + xScale(d.date)) / 2; 159 | const rightBound = (xScale(d.date) + nextX) / 2; 160 | return rightBound - leftBound; 161 | })()} 162 | height={100} 163 | fill="transparent" 164 | className="cursor-pointer" 165 | /> 166 | 167 | 168 | 169 |
170 | {d.date.toLocaleDateString("en-US", { 171 | month: "short", 172 | day: "2-digit", 173 | })} 174 |
175 |
{d.value.toLocaleString("en-US")}
176 | 177 | 178 | ))} 179 | 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 | 91 | {/* Grid lines */} 92 | {yScale 93 | .ticks(8) 94 | .map(yScale.tickFormat(8, "d")) 95 | .map((active, i) => ( 96 | 101 | 109 | 110 | ))} 111 | {/* Line */} 112 | 119 | 120 | {/* Circles and Tooltips */} 121 | {data.map((d, index) => ( 122 | 123 | 124 | 134 | 135 | {/* Tooltip Line */} 136 | 147 | {/* Invisible area closest to a specific point for the tooltip trigger */} 148 | { 150 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 151 | return (prevX + xScale(d.date)) / 2; 152 | })()} 153 | y={0} 154 | width={(() => { 155 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 156 | const nextX = 157 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 158 | const leftBound = (prevX + xScale(d.date)) / 2; 159 | const rightBound = (xScale(d.date) + nextX) / 2; 160 | return rightBound - leftBound; 161 | })()} 162 | height={100} 163 | fill="transparent" 164 | className="cursor-pointer" 165 | /> 166 | 167 | 168 | 169 |
170 | {d.date.toLocaleDateString("en-US", { 171 | month: "short", 172 | day: "2-digit", 173 | })} 174 |
175 |
{d.value.toLocaleString("en-US")}
176 | 177 | 178 | ))} 179 | 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 | 65 | {/* Grid lines */} 66 | {yScale 67 | .ticks(8) 68 | .map(yScale.tickFormat(8, "d")) 69 | .map((active, i) => ( 70 | 75 | 83 | 84 | ))} 85 | 86 | {/* Line */} 87 | 95 | 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 | 91 | {/* Grid lines */} 92 | {yScale 93 | .ticks(8) 94 | .map(yScale.tickFormat(8, "d")) 95 | .map((active, i) => ( 96 | 101 | 109 | 110 | ))} 111 | 112 | {/* Line */} 113 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {/* Tooltips */} 128 | {data.map((d, index) => ( 129 | 130 | 131 | 132 | {/* Tooltip Line */} 133 | 144 | {/* Invisible area closest to a specific point for the tooltip trigger */} 145 | { 147 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 148 | return (prevX + xScale(d.date)) / 2; 149 | })()} 150 | y={0} 151 | width={(() => { 152 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 153 | const nextX = 154 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 155 | const leftBound = (prevX + xScale(d.date)) / 2; 156 | const rightBound = (xScale(d.date) + nextX) / 2; 157 | return rightBound - leftBound; 158 | })()} 159 | height={100} 160 | fill="transparent" 161 | className="cursor-pointer" 162 | /> 163 | 164 | 165 | 166 |
167 | {d.date.toLocaleDateString("en-US", { 168 | month: "short", 169 | day: "2-digit", 170 | })} 171 |
172 |
{d.value.toLocaleString("en-US")}
173 | 174 | 175 | ))} 176 | 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 |
95 |
96 |
97 | 102 | {/* Grid lines */} 103 | {yScale 104 | .ticks(8) 105 | .map(yScale.tickFormat(8, "d")) 106 | .map((active, i) => ( 107 | 112 | 120 | 121 | ))} 122 | {/* Line */} 123 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | {/* Circles and Tooltips */} 138 | {data.map((d, index) => ( 139 | 140 | 141 | 142 | {/* Tooltip Line */} 143 | 154 | {/* Invisible area closest to a specific point for the tooltip trigger */} 155 | { 157 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 158 | return (prevX + xScale(d.date)) / 2; 159 | })()} 160 | y={0} 161 | width={(() => { 162 | const prevX = index > 0 ? xScale(data[index - 1].date) : xScale(d.date); 163 | const nextX = 164 | index < data.length - 1 ? xScale(data[index + 1].date) : xScale(d.date); 165 | const leftBound = (prevX + xScale(d.date)) / 2; 166 | const rightBound = (xScale(d.date) + nextX) / 2; 167 | return rightBound - leftBound; 168 | })()} 169 | height={100} 170 | fill="transparent" 171 | className="cursor-pointer" 172 | /> 173 | 174 | 175 | 176 |
177 | {d.date.toLocaleDateString("en-US", { 178 | month: "short", 179 | day: "2-digit", 180 | })} 181 |
182 |
{d.value.toLocaleString("en-US")}
183 | 184 | 185 | ))} 186 | 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 | 65 | {/* Grid lines */} 66 | {yScale 67 | .ticks(8) 68 | .map(yScale.tickFormat(8, "d")) 69 | .map((active, i) => ( 70 | 71 | 79 | 80 | ))} 81 | 82 | {/* Line */} 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 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 | 80 | {/* Sectors with Gradient Fill */} 81 | {arcs.map((d: PieArcDatum, i) => ( 82 | 83 | 84 | 85 | 86 | 87 |
{d.data.name}
88 | 89 | 90 | ))} 91 | 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 | 86 | {/* Connecting lines */} 87 | {arcs.map((d, i) => { 88 | const [labelX, labelY] = arcLabel.centroid(d); 89 | const [arcX, arcY] = arcGenerator.centroid(d); 90 | const LINE_LENGTH = 1.35; 91 | 92 | return ( 93 | 94 | 103 | 104 | ); 105 | })} 106 | 107 | {/* Slices */} 108 | {arcs.map((d: PieArcDatum, i) => ( 109 | 110 | 111 | 117 | 118 | 119 |
{d.data.name}
120 |
{d.data.value.toLocaleString("en-US")}
121 | 122 | 123 | ))} 124 | 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 | {d.data.name} 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 | 61 | {/* Slices */} 62 | {arcs.map((d, i) => { 63 | const midAngle = (d.startAngle + d.endAngle) / 2; 64 | 65 | return ( 66 | 67 | 68 | 69 | 70 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
{d.data.name}
85 |
86 | {d.data.value.toLocaleString("en-US")} 87 |
88 | 89 | 90 | ); 91 | })} 92 | 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 | 59 | {/* Define clip paths and colors for each slice */} 60 | 61 | {arcs.map((d, i) => ( 62 | 63 | 64 | 65 | 66 | 67 | 68 | ))} 69 | 70 | 71 | {/* Slices */} 72 | {arcs.map((d, i) => { 73 | const angle = computeAngle(d); 74 | let centroid = arcLabel.centroid(d); 75 | if (d.endAngle > Math.PI) { 76 | centroid[0] += 10; 77 | centroid[1] += 10; 78 | } else { 79 | centroid[0] -= 10; 80 | centroid[1] -= 0; 81 | } 82 | return ( 83 | 84 | 85 | 86 | {/* Use the clip path on this group or individual path */} 87 | 88 | 94 | 95 | {/* Labels with conditional rendering */} 96 | minAngle ? 1 : 0}> 97 | 98 | 99 | {d.data.name} 100 | 101 | {angle > minAngle && ( 102 | 103 | {d.data.value.toLocaleString("en-US")}% 104 | 105 | )} 106 | 107 | 108 | 109 | 110 | 111 |
{d.data.name}
112 |
{d.data.value.toLocaleString("en-US")}
113 | 114 | 115 | ); 116 | })} 117 | 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 |
57 |
58 |

Total

59 |

184

60 |
61 |
62 | 66 | {/* Slices */} 67 | {arcs.map((d, i) => ( 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
{d.data.name}
79 |
{d.data.value.toLocaleString("en-US")}
80 | 81 | 82 | ))} 83 | 84 | {/* Labels with conditional rendering */} 85 | {arcs.map((d, i) => { 86 | const angle = computeAngle(d); 87 | let centroid = arcLabel.centroid(d); 88 | if (d.endAngle > Math.PI) { 89 | centroid[0] += 10; 90 | centroid[1] += 0; 91 | } else { 92 | centroid[0] -= 20; 93 | centroid[1] -= 0; 94 | } 95 | 96 | return ( 97 | 98 | 99 | 105 | 106 | 107 | minAngle ? 1 : 0}> 108 | 109 | 110 | {d.data.name} 111 | 112 | {angle > minAngle && ( 113 | 114 | {d.data.value.toLocaleString("en-US")}% 115 | 116 | )} 117 | 118 | 119 | 120 | ); 121 | })} 122 | 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 | 60 | {/* Define clip paths and colors for each slice */} 61 | 62 | {arcs.map((d, i) => ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | ))} 70 | 71 | 72 | {/* Slices */} 73 | {arcs.map((d, i) => { 74 | const angle = computeAngle(d); 75 | let centroid = arcLabel.centroid(d); 76 | if (d.endAngle > Math.PI) { 77 | centroid[0] += 10; 78 | centroid[1] += 20; 79 | } else { 80 | centroid[0] += 10; 81 | centroid[1] -= 0; 82 | } 83 | return ( 84 | 85 | {/* Use the clip path on this group or individual path */} 86 | 87 | 93 | 94 | {/* Labels with conditional rendering */} 95 | minAngle ? 1 : 0}> 96 | 97 | 98 | {d.data.name} 99 | 100 | {angle > minAngle && ( 101 | 102 | {d.data.value.toLocaleString("en-US")}% 103 | 104 | )} 105 | 106 | 107 | 108 | ); 109 | })} 110 | 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 | 47 | 48 | {arcs.map((d, i) => ( 49 | 50 | 51 | 52 | ))} 53 | 54 | 55 | {/* Slices */} 56 | {arcs.map((d, i) => ( 57 | 58 | 65 | 66 | ))} 67 | 68 | 76 | Goal 77 | {" "} 78 | 86 | {data[0].value}% 87 | 88 | 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 | 47 | 48 | {arcs.map((d, i) => ( 49 | 50 | 51 | 52 | ))} 53 | 54 | 55 | {/* Slices */} 56 | {arcs.map((d, i) => ( 57 | 58 | 65 | 66 | ))} 67 | 68 | 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 | 37 | {/* */} 38 | 39 | {/* Draw concentric circles */} 40 | {[...Array(5)].map((_, i) => ( 41 | 50 | ))} 51 | 52 | {/* Draw axes */} 53 | {rawData.map((d, i) => ( 54 | 62 | ))} 63 | 64 | {/* Draw the radar chart path */} 65 | 66 | 67 | {/* Add labels for each axis */} 68 | {rawData.map((d, i) => ( 69 | 77 | {d.topic} 78 | 79 | ))} 80 | 81 | 82 |
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 | {leaf.data.name} 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 | --------------------------------------------------------------------------------