Skip to content

Commit

Permalink
Line chart2 (#15)
Browse files Browse the repository at this point in the history
* Primer avance pruebas

* All piechart tests passed

* Fixes and linting

* LineChart Inicial

* Fixed labels and line positioning

* Ui changes on all controls

* Last fixes and linting

* Fixed double rendering and YAxis values

* Added DashedLineChart and Line types

* LineChart Wrapper to fix duplicated code

* Support for legend

* Support for Tooltip

* Linting and final fixes
  • Loading branch information
v3gaaa authored Oct 8, 2024
1 parent bdd6915 commit fe4d8e3
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 84 deletions.
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "jest"
},
"dependencies": {
"d3-shape": "^3.2.0",
"lucide-react": "^0.445.0",
"react": "^19.0.0-rc-100dfd7dab-20240701",
"react-dom": "^19.0.0-rc-100dfd7dab-20240701",
Expand Down
2 changes: 2 additions & 0 deletions samples/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import PieChartWithPaddingAngle from './pages/PieChartWithPaddingAngle';
import PieChartWithCustomizedLabel from './pages/PieChartWithCustomizedLabel';
import CustomActiveShapePieChart from './pages/CustomActiveShapePieChart';
import LineChartExample from './pages/LineChartExample';
import DashedLineChart from './pages/DashedLineChart';

const App = () => {
return (
Expand All @@ -29,6 +30,7 @@ const App = () => {
<Route path="/pie-chart-with-customized-label" element={<PieChartWithCustomizedLabel />} />
<Route path="/custom-active-shape-pie" element={<CustomActiveShapePieChart />} />
<Route path="/line-chart" element={<LineChartExample />} />
<Route path="/dashed-line-chart" element={<DashedLineChart />} />
</Routes>
</div>
</Router>
Expand Down
13 changes: 13 additions & 0 deletions samples/pages/DashedLineChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import LineChartWrapper from '../utils/LineChartWrapper';

const DashedLineChart = () => {
const initialLines = [
{ id: 1, stroke: '#8884d8', dataKey: 'pv', strokeDasharray: '5 5', type: 'linear' },
{ id: 2, stroke: '#82ca9d', dataKey: 'uv', strokeDasharray: '3 3', type: 'monotoneX' },
];

return <LineChartWrapper initialLines={initialLines} />;
};

export default DashedLineChart;
63 changes: 9 additions & 54 deletions samples/pages/LineChartExample.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,13 @@
import React, { useState } from 'react';
import LineChart from '../../src/LineChart';
import Line from '../../src/Line';
import CartesianGrid from '../../src/CartesianGrid';
import XAxis from '../../src/XAxis';
import YAxis from '../../src/YAxis';
import Tooltip from '../../src/Tooltip';
import Legend from '../../src/Legend';
import LineChartControls from '../utils/LineChartControls';
import React from 'react';
import LineChartWrapper from '../utils/LineChartWrapper';

const data = [
{ name: 'Page A', uv: 4000, pv: 2400, amt: 2400 },
{ name: 'Page B', uv: 3000, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 2000, pv: 9800, amt: 2290 },
{ name: 'Page D', uv: 2780, pv: 3908, amt: 2000 },
{ name: 'Page E', uv: 1890, pv: 4800, amt: 2181 },
{ name: 'Page F', uv: 2390, pv: 3800, amt: 2500 },
{ name: 'Page G', uv: 3490, pv: 4300, amt: 2100 },
];
const LineChartExample = () => {
const initialLines = [
{ id: 1, stroke: '#8884d8', dataKey: 'pv', type: 'monotone' },
{ id: 2, stroke: '#82ca9d', dataKey: 'uv', type: 'linear' },
];

const LineChartExample: React.FC = () => {
const [lines, setLines] = useState([
{ id: 1, stroke: '#8884d8', dataKey: 'pv' },
{ id: 2, stroke: '#82ca9d', dataKey: 'uv' },
]);
const [width, setWidth] = useState(730);
const [height, setHeight] = useState(250);
const [margin, setMargin] = useState({ top: 5, right: 30, left: 20, bottom: 5 });

return (
<div className="p-6">
<div className="flex">
<LineChartControls
lines={lines}
setLines={setLines}
width={width}
setWidth={setWidth}
height={height}
setHeight={setHeight}
margin={margin}
setMargin={setMargin}
/>
<LineChart width={width} height={height} data={data} margin={margin}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
{lines.map((line) => (
<Line key={line.id} type="monotone" dataKey={line.dataKey} stroke={line.stroke} />
))}
</LineChart>
</div>
</div>
);
return <LineChartWrapper initialLines={initialLines} />;
};

export default LineChartExample;
export default LineChartExample;
25 changes: 21 additions & 4 deletions samples/utils/LineChartControls.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';

interface LineChartControlsProps {
lines: Array<{ id: number; stroke: string }>;
setLines: React.Dispatch<React.SetStateAction<Array<{ id: number; stroke: string }>>>;
lines: Array<{ id: number; stroke: string; type: string }>;
setLines: React.Dispatch<React.SetStateAction<Array<{ id: number; stroke: string; type: string }>>>;
width: number;
setWidth: React.Dispatch<React.SetStateAction<number>>;
height: number;
Expand All @@ -11,6 +11,10 @@ interface LineChartControlsProps {
setMargin: React.Dispatch<React.SetStateAction<{ top: number; right: number; bottom: number; left: number }>>;
}

const interpolationOptions = [
'linear', 'basis', 'basisClosed', 'basisOpen', 'bumpX', 'bumpY', 'natural', 'monotoneX', 'monotoneY', 'step', 'stepBefore', 'stepAfter',
];

export default function LineChartControls({
lines,
setLines,
Expand Down Expand Up @@ -78,7 +82,7 @@ export default function LineChartControls({
))}
</div>
</div>

{lines.map((line, index) => (
<div key={line.id} className="bg-gray-50 p-4 rounded-lg mb-4 shadow-sm">
<h3 className="text-lg font-medium text-indigo-700 mb-2">Line {index + 1} Settings</h3>
Expand All @@ -93,10 +97,23 @@ export default function LineChartControls({
/>
<span className="text-sm font-medium text-gray-600">{line.stroke}</span>
</div>

<label className="block text-sm font-medium text-gray-700 mt-2">Interpolation Type</label>
<select
value={line.type}
onChange={(e) => handleLineChange(index, 'type', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-300 ease-in-out"
>
{interpolationOptions.map((option) => (
<option key={option} value={option}>
{option.charAt(0).toUpperCase() + option.slice(1)}
</option>
))}
</select>
</div>
</div>
))}
</form>
</div>
);
}
}
61 changes: 61 additions & 0 deletions samples/utils/LineChartWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useState } from 'react';
import LineChart from '../../src/LineChart';
import Line from '../../src/Line';
import CartesianGrid from '../../src/CartesianGrid';
import XAxis from '../../src/XAxis';
import YAxis from '../../src/YAxis';
import Tooltip from '../../src/Tooltip';
import Legend from '../../src/Legend';
import LineChartControls from './LineChartControls';

const data = [
{ name: 'Page A', uv: 4000, pv: 2400, amt: 2400 },
{ name: 'Page B', uv: 3000, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 2000, pv: 9800, amt: 2290 },
{ name: 'Page D', uv: 2780, pv: 3908, amt: 2000 },
{ name: 'Page E', uv: 1890, pv: 4800, amt: 2181 },
{ name: 'Page F', uv: 2390, pv: 3800, amt: 2500 },
{ name: 'Page G', uv: 3490, pv: 4300, amt: 2100 },
];

const LineChartWrapper = ({ initialLines }) => {
const [lines, setLines] = useState(initialLines);
const [width, setWidth] = useState(730);
const [height, setHeight] = useState(250);
const [margin, setMargin] = useState({ top: 5, right: 30, left: 20, bottom: 5 });

return (
<div className="p-6">
<div className="flex">
<LineChartControls
lines={lines}
setLines={setLines}
width={width}
setWidth={setWidth}
height={height}
setHeight={setHeight}
margin={margin}
setMargin={setMargin}
/>
<LineChart width={width} height={height} data={data} margin={margin}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
{lines.map((line) => (
<Line
key={line.id}
type={line.type}
dataKey={line.dataKey}
stroke={line.stroke}
strokeDasharray={line.strokeDasharray}
/>
))}
</LineChart>
</div>
</div>
);
};

export default LineChartWrapper;
1 change: 1 addition & 0 deletions samples/utils/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function NavBar() {
category: 'Line Charts',
items: [
{ name: 'Line Chart', path: '/line-chart' },
{ name: 'Dashed Line Chart', path: '/dashed-line-chart' },
],
},
];
Expand Down
46 changes: 34 additions & 12 deletions src/Line/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import React from 'react';
import * as d3Shape from 'd3-shape';

interface LineProps {
data: Array<{ [key: string]: any }>;
dataKey: string;
stroke: string;
type?: 'monotone' | 'linear';
strokeDasharray?: string;
type?:
| 'basis'
| 'basisClosed'
| 'basisOpen'
| 'bumpX'
| 'bumpY'
| 'bump'
| 'linear'
| 'linearClosed'
| 'natural'
| 'monotoneX'
| 'monotoneY'
| 'monotone'
| 'step'
| 'stepBefore'
| 'stepAfter';
chartWidth: number;
chartHeight: number;
onMouseOver?: (event: React.MouseEvent, entry: { name: string; [key: string]: any }) => void;
Expand All @@ -15,6 +32,7 @@ const Line: React.FC<LineProps> = ({
data = [],
dataKey,
stroke,
strokeDasharray = '0',
type = 'linear',
chartWidth,
chartHeight,
Expand All @@ -25,28 +43,32 @@ const Line: React.FC<LineProps> = ({

const maxValue = Math.max(...data.map((d) => d[dataKey]));

const points = data
.map((entry, index) => {
const x = (index + 0.5) * (chartWidth / data.length); // Centrado
const y = chartHeight - (entry[dataKey] / maxValue) * chartHeight;
return `${x},${y}`;
})
.join(' ');
const xScale = (index: number) => (index + 0.5) * (chartWidth / data.length);
const yScale = (value: number) => chartHeight - (value / maxValue) * chartHeight;

const lineGenerator = d3Shape
.line()
.x((d, index) => xScale(index))
.y((d) => yScale((d as any)[dataKey]))
.curve(d3Shape[`curve${type.charAt(0).toUpperCase() + type.slice(1)}`] || d3Shape.curveLinear);

const path = lineGenerator(data as [number, number][]);

return (
<>
<polyline
points={points}
<path
d={path || ''}
fill='none'
stroke={stroke}
strokeWidth={2}
strokeDasharray={strokeDasharray}
onMouseOver={(event) => onMouseOver(event, { name: dataKey })}
onMouseOut={onMouseOut}
style={{ transition: 'all 0.3s' }}
/>
{data.map((entry, index) => {
const x = (index + 0.5) * (chartWidth / data.length);
const y = chartHeight - (entry[dataKey] / maxValue) * chartHeight;
const x = xScale(index);
const y = yScale(entry[dataKey]);
return (
<circle
key={`point-${index}`}
Expand Down
Loading

0 comments on commit fe4d8e3

Please sign in to comment.