react-open-source-grid 1.6.6 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/index.js CHANGED
@@ -13515,10 +13515,511 @@ function renderMarkdownPreview(markdown) {
13515
13515
  }
13516
13516
  return html;
13517
13517
  }
13518
+
13519
+ // src/charts/rangeToChart.ts
13520
+ function normalizeRange(range) {
13521
+ const startRow = Math.min(range.start.rowIndex, range.end.rowIndex);
13522
+ const endRow = Math.max(range.start.rowIndex, range.end.rowIndex);
13523
+ const startCol = Math.min(range.start.colIndex, range.end.colIndex);
13524
+ const endCol = Math.max(range.start.colIndex, range.end.colIndex);
13525
+ return { startRow, endRow, startCol, endCol };
13526
+ }
13527
+ function isNumeric(value) {
13528
+ if (value === null || value === void 0 || value === "") {
13529
+ return false;
13530
+ }
13531
+ const num = Number(value);
13532
+ return !isNaN(num) && isFinite(num);
13533
+ }
13534
+ function toNumber2(value) {
13535
+ if (isNumeric(value)) {
13536
+ return Number(value);
13537
+ }
13538
+ return 0;
13539
+ }
13540
+ function generateChartId() {
13541
+ return `chart-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
13542
+ }
13543
+ var DEFAULT_COLORS = [
13544
+ "#8884d8",
13545
+ // blue
13546
+ "#82ca9d",
13547
+ // green
13548
+ "#ffc658",
13549
+ // yellow
13550
+ "#ff7c7c",
13551
+ // red
13552
+ "#a28fd0",
13553
+ // purple
13554
+ "#ff9f40",
13555
+ // orange
13556
+ "#4bc0c0",
13557
+ // teal
13558
+ "#ff6384"
13559
+ // pink
13560
+ ];
13561
+ function buildChartConfigFromRange(options) {
13562
+ const {
13563
+ range,
13564
+ rows,
13565
+ columns,
13566
+ chartType,
13567
+ useFirstColumnAsCategory = true,
13568
+ title,
13569
+ theme = "light"
13570
+ } = options;
13571
+ const normalized = normalizeRange(range);
13572
+ const { startRow, endRow, startCol, endCol } = normalized;
13573
+ if (startRow < 0 || endRow >= rows.length) {
13574
+ throw new Error("Invalid row range");
13575
+ }
13576
+ if (startCol < 0 || endCol >= columns.length) {
13577
+ throw new Error("Invalid column range");
13578
+ }
13579
+ const selectedRows = rows.slice(startRow, endRow + 1);
13580
+ const selectedColumns = columns.slice(startCol, endCol + 1);
13581
+ let categoryColumnIndex = 0;
13582
+ let dataColumnStartIndex = 0;
13583
+ if (useFirstColumnAsCategory && selectedColumns.length > 1) {
13584
+ categoryColumnIndex = 0;
13585
+ dataColumnStartIndex = 1;
13586
+ } else {
13587
+ categoryColumnIndex = -1;
13588
+ dataColumnStartIndex = 0;
13589
+ }
13590
+ const xLabels = [];
13591
+ if (categoryColumnIndex >= 0) {
13592
+ const categoryField = selectedColumns[categoryColumnIndex].field;
13593
+ selectedRows.forEach((row) => {
13594
+ const value = row[categoryField];
13595
+ xLabels.push(value !== null && value !== void 0 ? String(value) : "");
13596
+ });
13597
+ } else {
13598
+ for (let i = 0; i < selectedRows.length; i++) {
13599
+ xLabels.push(i + 1);
13600
+ }
13601
+ }
13602
+ const series = [];
13603
+ const dataColumns = selectedColumns.slice(dataColumnStartIndex);
13604
+ dataColumns.forEach((column, index) => {
13605
+ const seriesData = [];
13606
+ const field = column.field;
13607
+ let hasNumericData = false;
13608
+ selectedRows.forEach((row) => {
13609
+ const value = row[field];
13610
+ if (isNumeric(value)) {
13611
+ hasNumericData = true;
13612
+ seriesData.push(toNumber2(value));
13613
+ } else {
13614
+ seriesData.push(0);
13615
+ }
13616
+ });
13617
+ if (hasNumericData) {
13618
+ series.push({
13619
+ name: column.headerName || field,
13620
+ data: seriesData,
13621
+ color: DEFAULT_COLORS[index % DEFAULT_COLORS.length]
13622
+ });
13623
+ }
13624
+ });
13625
+ if (series.length === 0) {
13626
+ throw new Error("No numeric data found in the selected range");
13627
+ }
13628
+ if (chartType === "pie") {
13629
+ if (series.length === 1) {
13630
+ } else {
13631
+ const summedData = new Array(xLabels.length).fill(0);
13632
+ series.forEach((s) => {
13633
+ s.data.forEach((value, idx) => {
13634
+ summedData[idx] += value;
13635
+ });
13636
+ });
13637
+ series.length = 0;
13638
+ series.push({
13639
+ name: "Total",
13640
+ data: summedData,
13641
+ color: DEFAULT_COLORS[0]
13642
+ });
13643
+ }
13644
+ }
13645
+ return {
13646
+ id: generateChartId(),
13647
+ type: chartType,
13648
+ title: title || `${chartType.charAt(0).toUpperCase() + chartType.slice(1)} Chart`,
13649
+ xLabels,
13650
+ series,
13651
+ theme
13652
+ };
13653
+ }
13654
+ function updateChartType(config, newType) {
13655
+ var _a;
13656
+ return {
13657
+ ...config,
13658
+ type: newType,
13659
+ title: ((_a = config.title) == null ? void 0 : _a.replace(
13660
+ /^(Line|Bar|Area|Pie)/i,
13661
+ newType.charAt(0).toUpperCase() + newType.slice(1)
13662
+ )) || `${newType.charAt(0).toUpperCase() + newType.slice(1)} Chart`
13663
+ };
13664
+ }
13665
+ function updateChartTheme(config, newTheme) {
13666
+ return {
13667
+ ...config,
13668
+ theme: newTheme
13669
+ };
13670
+ }
13671
+
13672
+ // src/charts/QuickChart.tsx
13673
+ import React35, { useRef as useRef20 } from "react";
13674
+ import {
13675
+ LineChart,
13676
+ Line,
13677
+ BarChart,
13678
+ Bar,
13679
+ AreaChart,
13680
+ Area,
13681
+ PieChart,
13682
+ Pie,
13683
+ Cell,
13684
+ XAxis,
13685
+ YAxis,
13686
+ CartesianGrid,
13687
+ Tooltip as Tooltip2,
13688
+ Legend,
13689
+ ResponsiveContainer
13690
+ } from "recharts";
13691
+ import { toPng } from "html-to-image";
13692
+ var QuickChart = ({
13693
+ config,
13694
+ onClose,
13695
+ onChangeType,
13696
+ onToggleTheme,
13697
+ allowTypeSwitch = true,
13698
+ allowThemeSwitch = true,
13699
+ width = 600,
13700
+ height = 400
13701
+ }) => {
13702
+ const chartRef = useRef20(null);
13703
+ const theme = config.theme || "light";
13704
+ const transformedData = config.xLabels.map((label, index) => {
13705
+ const dataPoint = { name: label };
13706
+ config.series.forEach((series) => {
13707
+ dataPoint[series.name] = series.data[index] || 0;
13708
+ });
13709
+ return dataPoint;
13710
+ });
13711
+ const pieData = config.series.length > 0 ? config.xLabels.map((label, index) => ({
13712
+ name: label,
13713
+ value: config.series[0].data[index] || 0
13714
+ })) : [];
13715
+ const handleExportPNG = async () => {
13716
+ if (!chartRef.current) return;
13717
+ try {
13718
+ const dataUrl = await toPng(chartRef.current, {
13719
+ quality: 1,
13720
+ pixelRatio: 2,
13721
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff"
13722
+ });
13723
+ const link = document.createElement("a");
13724
+ link.download = `${config.title || "chart"}-${Date.now()}.png`;
13725
+ link.href = dataUrl;
13726
+ link.click();
13727
+ } catch (error) {
13728
+ console.error("Failed to export chart:", error);
13729
+ }
13730
+ };
13731
+ const chartTypeIcon = (type) => {
13732
+ switch (type) {
13733
+ case "line":
13734
+ return "\u{1F4C8}";
13735
+ case "bar":
13736
+ return "\u{1F4CA}";
13737
+ case "area":
13738
+ return "\u{1F4C9}";
13739
+ case "pie":
13740
+ return "\u{1F967}";
13741
+ default:
13742
+ return "\u{1F4CA}";
13743
+ }
13744
+ };
13745
+ const renderChart = () => {
13746
+ const commonProps = {
13747
+ data: config.type === "pie" ? pieData : transformedData,
13748
+ margin: { top: 5, right: 30, left: 20, bottom: 5 }
13749
+ };
13750
+ const axisProps = {
13751
+ stroke: theme === "dark" ? "#888" : "#666"
13752
+ };
13753
+ const gridProps = {
13754
+ strokeDasharray: "3 3",
13755
+ stroke: theme === "dark" ? "#333" : "#ddd"
13756
+ };
13757
+ switch (config.type) {
13758
+ case "line":
13759
+ return /* @__PURE__ */ React35.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React35.createElement(LineChart, { ...commonProps }, /* @__PURE__ */ React35.createElement(CartesianGrid, { ...gridProps }), /* @__PURE__ */ React35.createElement(XAxis, { dataKey: "name", ...axisProps }), /* @__PURE__ */ React35.createElement(YAxis, { ...axisProps }), /* @__PURE__ */ React35.createElement(
13760
+ Tooltip2,
13761
+ {
13762
+ contentStyle: {
13763
+ backgroundColor: theme === "dark" ? "#2a2a2a" : "#fff",
13764
+ border: `1px solid ${theme === "dark" ? "#444" : "#ccc"}`,
13765
+ color: theme === "dark" ? "#fff" : "#000"
13766
+ }
13767
+ }
13768
+ ), /* @__PURE__ */ React35.createElement(Legend, null), config.series.map((series) => /* @__PURE__ */ React35.createElement(
13769
+ Line,
13770
+ {
13771
+ key: series.name,
13772
+ type: "monotone",
13773
+ dataKey: series.name,
13774
+ stroke: series.color,
13775
+ strokeWidth: 2,
13776
+ dot: { r: 4 },
13777
+ activeDot: { r: 6 }
13778
+ }
13779
+ ))));
13780
+ case "bar":
13781
+ return /* @__PURE__ */ React35.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React35.createElement(BarChart, { ...commonProps }, /* @__PURE__ */ React35.createElement(CartesianGrid, { ...gridProps }), /* @__PURE__ */ React35.createElement(XAxis, { dataKey: "name", ...axisProps }), /* @__PURE__ */ React35.createElement(YAxis, { ...axisProps }), /* @__PURE__ */ React35.createElement(
13782
+ Tooltip2,
13783
+ {
13784
+ contentStyle: {
13785
+ backgroundColor: theme === "dark" ? "#2a2a2a" : "#fff",
13786
+ border: `1px solid ${theme === "dark" ? "#444" : "#ccc"}`,
13787
+ color: theme === "dark" ? "#fff" : "#000"
13788
+ }
13789
+ }
13790
+ ), /* @__PURE__ */ React35.createElement(Legend, null), config.series.map((series) => /* @__PURE__ */ React35.createElement(Bar, { key: series.name, dataKey: series.name, fill: series.color }))));
13791
+ case "area":
13792
+ return /* @__PURE__ */ React35.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React35.createElement(AreaChart, { ...commonProps }, /* @__PURE__ */ React35.createElement(CartesianGrid, { ...gridProps }), /* @__PURE__ */ React35.createElement(XAxis, { dataKey: "name", ...axisProps }), /* @__PURE__ */ React35.createElement(YAxis, { ...axisProps }), /* @__PURE__ */ React35.createElement(
13793
+ Tooltip2,
13794
+ {
13795
+ contentStyle: {
13796
+ backgroundColor: theme === "dark" ? "#2a2a2a" : "#fff",
13797
+ border: `1px solid ${theme === "dark" ? "#444" : "#ccc"}`,
13798
+ color: theme === "dark" ? "#fff" : "#000"
13799
+ }
13800
+ }
13801
+ ), /* @__PURE__ */ React35.createElement(Legend, null), config.series.map((series) => /* @__PURE__ */ React35.createElement(
13802
+ Area,
13803
+ {
13804
+ key: series.name,
13805
+ type: "monotone",
13806
+ dataKey: series.name,
13807
+ fill: series.color,
13808
+ stroke: series.color,
13809
+ fillOpacity: 0.6
13810
+ }
13811
+ ))));
13812
+ case "pie":
13813
+ return /* @__PURE__ */ React35.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React35.createElement(PieChart, null, /* @__PURE__ */ React35.createElement(
13814
+ Pie,
13815
+ {
13816
+ data: pieData,
13817
+ cx: "50%",
13818
+ cy: "50%",
13819
+ labelLine: false,
13820
+ label: ({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`,
13821
+ outerRadius: Math.min(height, width) / 4,
13822
+ fill: "#8884d8",
13823
+ dataKey: "value"
13824
+ },
13825
+ pieData.map((_, index) => {
13826
+ var _a;
13827
+ return /* @__PURE__ */ React35.createElement(
13828
+ Cell,
13829
+ {
13830
+ key: `cell-${index}`,
13831
+ fill: ((_a = config.series[0]) == null ? void 0 : _a.color) || ["#8884d8", "#82ca9d", "#ffc658", "#ff7c7c", "#a28fd0"][index % 5]
13832
+ }
13833
+ );
13834
+ })
13835
+ ), /* @__PURE__ */ React35.createElement(
13836
+ Tooltip2,
13837
+ {
13838
+ contentStyle: {
13839
+ backgroundColor: theme === "dark" ? "#2a2a2a" : "#fff",
13840
+ border: `1px solid ${theme === "dark" ? "#444" : "#ccc"}`,
13841
+ color: theme === "dark" ? "#fff" : "#000"
13842
+ }
13843
+ }
13844
+ ), /* @__PURE__ */ React35.createElement(Legend, null)));
13845
+ default:
13846
+ return /* @__PURE__ */ React35.createElement("div", null, "Unsupported chart type");
13847
+ }
13848
+ };
13849
+ return /* @__PURE__ */ React35.createElement(
13850
+ "div",
13851
+ {
13852
+ ref: chartRef,
13853
+ className: `quick-chart quick-chart--${theme}`,
13854
+ style: { width, height }
13855
+ },
13856
+ /* @__PURE__ */ React35.createElement("div", { className: "quick-chart__header" }, /* @__PURE__ */ React35.createElement("h3", { className: "quick-chart__title" }, config.title), /* @__PURE__ */ React35.createElement("div", { className: "quick-chart__controls" }, allowTypeSwitch && onChangeType && /* @__PURE__ */ React35.createElement("div", { className: "quick-chart__type-selector" }, ["line", "bar", "area", "pie"].map((type) => /* @__PURE__ */ React35.createElement(
13857
+ "button",
13858
+ {
13859
+ key: type,
13860
+ className: `quick-chart__type-btn ${config.type === type ? "quick-chart__type-btn--active" : ""}`,
13861
+ onClick: () => onChangeType(type),
13862
+ title: `${type.charAt(0).toUpperCase() + type.slice(1)} Chart`,
13863
+ "aria-label": `Switch to ${type} chart`
13864
+ },
13865
+ chartTypeIcon(type)
13866
+ ))), allowThemeSwitch && onToggleTheme && /* @__PURE__ */ React35.createElement(
13867
+ "button",
13868
+ {
13869
+ className: "quick-chart__btn",
13870
+ onClick: onToggleTheme,
13871
+ title: "Toggle theme",
13872
+ "aria-label": "Toggle theme"
13873
+ },
13874
+ theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"
13875
+ ), /* @__PURE__ */ React35.createElement(
13876
+ "button",
13877
+ {
13878
+ className: "quick-chart__btn",
13879
+ onClick: handleExportPNG,
13880
+ title: "Export as PNG",
13881
+ "aria-label": "Export chart as PNG"
13882
+ },
13883
+ "\u{1F4E5}"
13884
+ ), onClose && /* @__PURE__ */ React35.createElement(
13885
+ "button",
13886
+ {
13887
+ className: "quick-chart__btn quick-chart__close",
13888
+ onClick: onClose,
13889
+ title: "Close",
13890
+ "aria-label": "Close chart"
13891
+ },
13892
+ "\xD7"
13893
+ ))),
13894
+ /* @__PURE__ */ React35.createElement("div", { className: "quick-chart__body" }, renderChart())
13895
+ );
13896
+ };
13897
+
13898
+ // src/charts/ChartOverlay.tsx
13899
+ import React36, { useEffect as useEffect17, useRef as useRef21, useState as useState26 } from "react";
13900
+ var ChartOverlay = ({
13901
+ config,
13902
+ onClose,
13903
+ onChangeType,
13904
+ onToggleTheme,
13905
+ position = "bottom-right",
13906
+ draggable = true
13907
+ }) => {
13908
+ const overlayRef = useRef21(null);
13909
+ const [isDragging, setIsDragging] = useState26(false);
13910
+ const [dragOffset, setDragOffset] = useState26({ x: 0, y: 0 });
13911
+ const [chartPosition, setChartPosition] = useState26({ x: 0, y: 0 });
13912
+ const [dimensions] = useState26({ width: 600, height: 400 });
13913
+ useEffect17(() => {
13914
+ if (!overlayRef.current) return;
13915
+ const updatePosition = () => {
13916
+ const viewport = {
13917
+ width: window.innerWidth,
13918
+ height: window.innerHeight
13919
+ };
13920
+ let x = 0;
13921
+ let y = 0;
13922
+ switch (position) {
13923
+ case "top-right":
13924
+ x = viewport.width - dimensions.width - 40;
13925
+ y = 40;
13926
+ break;
13927
+ case "top-left":
13928
+ x = 40;
13929
+ y = 40;
13930
+ break;
13931
+ case "bottom-right":
13932
+ x = viewport.width - dimensions.width - 40;
13933
+ y = viewport.height - dimensions.height - 40;
13934
+ break;
13935
+ case "bottom-left":
13936
+ x = 40;
13937
+ y = viewport.height - dimensions.height - 40;
13938
+ break;
13939
+ case "center":
13940
+ x = (viewport.width - dimensions.width) / 2;
13941
+ y = (viewport.height - dimensions.height) / 2;
13942
+ break;
13943
+ }
13944
+ setChartPosition({ x, y });
13945
+ };
13946
+ updatePosition();
13947
+ window.addEventListener("resize", updatePosition);
13948
+ return () => window.removeEventListener("resize", updatePosition);
13949
+ }, [position, dimensions]);
13950
+ const handleMouseDown = (e) => {
13951
+ if (!draggable) return;
13952
+ if (e.target.closest(".quick-chart__body")) return;
13953
+ setIsDragging(true);
13954
+ setDragOffset({
13955
+ x: e.clientX - chartPosition.x,
13956
+ y: e.clientY - chartPosition.y
13957
+ });
13958
+ };
13959
+ useEffect17(() => {
13960
+ if (!isDragging) return;
13961
+ const handleMouseMove = (e) => {
13962
+ const newX = e.clientX - dragOffset.x;
13963
+ const newY = e.clientY - dragOffset.y;
13964
+ const maxX = window.innerWidth - dimensions.width;
13965
+ const maxY = window.innerHeight - dimensions.height;
13966
+ setChartPosition({
13967
+ x: Math.max(0, Math.min(newX, maxX)),
13968
+ y: Math.max(0, Math.min(newY, maxY))
13969
+ });
13970
+ };
13971
+ const handleMouseUp = () => {
13972
+ setIsDragging(false);
13973
+ };
13974
+ document.addEventListener("mousemove", handleMouseMove);
13975
+ document.addEventListener("mouseup", handleMouseUp);
13976
+ return () => {
13977
+ document.removeEventListener("mousemove", handleMouseMove);
13978
+ document.removeEventListener("mouseup", handleMouseUp);
13979
+ };
13980
+ }, [isDragging, dragOffset, dimensions]);
13981
+ useEffect17(() => {
13982
+ const handleKeyDown = (e) => {
13983
+ if (e.key === "Escape") {
13984
+ onClose();
13985
+ }
13986
+ };
13987
+ document.addEventListener("keydown", handleKeyDown);
13988
+ return () => document.removeEventListener("keydown", handleKeyDown);
13989
+ }, [onClose]);
13990
+ return /* @__PURE__ */ React36.createElement("div", { className: "chart-overlay" }, /* @__PURE__ */ React36.createElement("div", { className: "chart-overlay__backdrop", onClick: onClose }), /* @__PURE__ */ React36.createElement(
13991
+ "div",
13992
+ {
13993
+ ref: overlayRef,
13994
+ className: `chart-overlay__container ${isDragging ? "chart-overlay__container--dragging" : ""} ${draggable ? "chart-overlay__container--draggable" : ""}`,
13995
+ style: {
13996
+ left: chartPosition.x,
13997
+ top: chartPosition.y,
13998
+ width: dimensions.width,
13999
+ height: dimensions.height
14000
+ },
14001
+ onMouseDown: handleMouseDown
14002
+ },
14003
+ /* @__PURE__ */ React36.createElement(
14004
+ QuickChart,
14005
+ {
14006
+ config,
14007
+ onClose,
14008
+ onChangeType,
14009
+ onToggleTheme,
14010
+ allowTypeSwitch: true,
14011
+ allowThemeSwitch: true,
14012
+ width: dimensions.width,
14013
+ height: dimensions.height
14014
+ }
14015
+ )
14016
+ ));
14017
+ };
13518
14018
  export {
13519
14019
  AdvancedFilterBuilder,
13520
14020
  BadgeCell,
13521
14021
  ButtonCell,
14022
+ ChartOverlay,
13522
14023
  ColumnChooser,
13523
14024
  ColumnFilters,
13524
14025
  CurrencyCell,
@@ -13544,6 +14045,7 @@ export {
13544
14045
  PivotToolbar,
13545
14046
  PriorityIndicator,
13546
14047
  ProgressBar,
14048
+ QuickChart,
13547
14049
  Rating,
13548
14050
  RichSelectEditor,
13549
14051
  ServerAdapter,
@@ -13555,6 +14057,7 @@ export {
13555
14057
  VirtualScroller,
13556
14058
  WebSocketMockFeed,
13557
14059
  alpineTheme,
14060
+ buildChartConfigFromRange,
13558
14061
  buildPivot,
13559
14062
  buildTreeFromFlat,
13560
14063
  collapseAllNodes,
@@ -13593,11 +14096,14 @@ export {
13593
14096
  isTreeNode,
13594
14097
  loadDensityMode,
13595
14098
  materialTheme,
14099
+ normalizeRange,
13596
14100
  parseFormattedNumber,
13597
14101
  quartzTheme,
13598
14102
  saveDensityMode,
13599
14103
  themes,
13600
14104
  toggleNodeExpansion,
14105
+ updateChartTheme,
14106
+ updateChartType,
13601
14107
  useDensityMode,
13602
14108
  useEditorAutoFocus,
13603
14109
  useEditorClickOutside,
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "react-open-source-grid",
3
3
  "private": false,
4
- "version": "1.6.6",
4
+ "version": "1.7.0",
5
5
  "type": "module",
6
- "description": "A high-performance React DataGrid component with advanced features like virtual scrolling, infinite scrolling, tree data, market data mode, and more",
6
+ "description": "A high-performance React DataGrid component with advanced features like virtual scrolling, infinite scrolling, tree data, market data mode, integrated charts, and more",
7
7
  "main": "./dist/lib/index.cjs",
8
8
  "module": "./dist/lib/index.js",
9
9
  "types": "./dist/lib/index.d.ts",
@@ -71,10 +71,12 @@
71
71
  "dependencies": {
72
72
  "@types/react-syntax-highlighter": "^15.5.13",
73
73
  "exceljs": "^4.4.0",
74
+ "html-to-image": "^1.11.11",
74
75
  "react": "^19.2.0",
75
76
  "react-dom": "^19.2.0",
76
77
  "react-router-dom": "^7.9.6",
77
- "react-syntax-highlighter": "^16.1.0"
78
+ "react-syntax-highlighter": "^16.1.0",
79
+ "recharts": "^2.10.3"
78
80
  },
79
81
  "devDependencies": {
80
82
  "@eslint/js": "^9.39.1",