Skip to content

Commit

Permalink
feat(chart): add line chart support
Browse files Browse the repository at this point in the history
  • Loading branch information
colinmeinke committed Mar 13, 2016
1 parent b82abba commit 875029f
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 1 deletion.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ npm install react-svg-chart

## Usage

### Bar chart

```js
import React from 'react';
import { BarChart } from 'react-svg-chart';
Expand All @@ -28,3 +30,26 @@ const App = () => (
/>
);
```

### Line chart

```js
import React from 'react';
import { LineChart } from 'react-svg-chart';

const App = () => (
<LineChart
lines={[
{ points: [
{ value: 11 },
{ value: 27 },
{ value: 4 },
{ value: 19 },
{ value: 10 },
]},
]}
height={ 400 }
width={ 600 }
/>
);
```
2 changes: 2 additions & 0 deletions examples/lineChart/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
client.dist.js
node_modules/
80 changes: 80 additions & 0 deletions examples/lineChart/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { createClass } from 'react';
import { LineChart } from '../../src';

const days = [
{
title: 'Thursday, 9th March',
lines: [
{
points: [
{ value: 3.50 },
{ value: 7.45 },
{ value: 1.27 },
{ value: 1.15 },
{ value: 2.93 },
],
},
],
},
{
title: 'Wednesday, 8th March',
lines: [
{
points: [
{ value: 1.92 },
{ value: 1.11 },
{ value: 7.20 },
{ value: 6.34 },
{ value: 3.15 },
],
},
],
},
{
title: 'Tuesday, 7th March',
lines: [
{
points: [
{ value: 5.37 },
{ value: 7.32 },
{ value: 0.90 },
{ value: 4.78 },
{ value: 2.75 },
],
},
],
},
];

const App = createClass({
onChange ( e ) {
this.setState({
day: days[ e.target.value ],
});
},

getInitialState () {
return {
day: days[ 0 ],
};
},

render () {
return (
<section className="content">
<select onChange={ this.onChange }>
{ days.map(( day, i ) => (
<option key={ i } value={ i }>{ day.title }</option>
))}
</select>
<LineChart
chartClassName="chart"
lines={ this.state.day.lines }
preserveAspectRatio="xMinYMid meet"
/>
</section>
);
}
});

export default App;
47 changes: 47 additions & 0 deletions examples/lineChart/Page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { PropTypes } from 'react';

const propTypes = {
app: PropTypes.string.isRequired,
};

const Page = ({ app }) => (
<html lang="en">
<head>
<style>{ `
html,
body,
.app,
.content {
height: 100%;
margin: 0;
}
select {
position: absolute;
right: 10px;
top: 10px;
}
select {
display: block;
}
.chart {
background-color: rgb(240,240,240);
box-sizing: border-box;
height: 100%;
padding: 40px 10px 10px;
width: 100%;
}
` }</style>
</head>
<body>
<section className="app" dangerouslySetInnerHTML={{ __html: app }} />
<script src="/client.dist.js" />
</body>
</html>
);

Page.propTypes = propTypes;

export default Page;
8 changes: 8 additions & 0 deletions examples/lineChart/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'babel-polyfill';

import React from 'react';
import { render } from 'react-dom';

import App from './App';

render( <App />, document.querySelector( '.app' ));
43 changes: 43 additions & 0 deletions examples/lineChart/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"author": {
"name": "Colin Meinke",
"email": "[email protected]",
"url": "www.colinmeinke.com"
},
"babel": {
"plugins": [
"transform-object-rest-spread"
],
"presets": [
"es2015",
"react"
]
},
"bugs": {
"url": "https://github.com/colinmeinke/react-svg-chart/issues"
},
"dependencies": {
"babel-cli": "^6.6.5",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"express": "^4.13.4",
"react": "^0.14.7",
"react-dom": "^0.14.7"
},
"description": "SVG charts line chart example",
"devDependencies": {
"babelify": "^7.2.0",
"browserify": "^13.0.0"
},
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/colinmeinke/react-svg-chart.git"
},
"scripts": {
"build": "browserify ./client.js -o ./client.dist.js -t babelify",
"start": "npm run build && babel-node ./server.js"
},
"version": "0.0.0-semantically-released"
}
24 changes: 24 additions & 0 deletions examples/lineChart/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import express from 'express';
import React from 'react';
import { renderToStaticMarkup, renderToString } from 'react-dom/server';

import App from './App';
import Page from './Page';

const app = express();

app.get( '/client.dist.js', ( req, res ) => {
res.sendFile( `${ __dirname }/client.dist.js` );
});

app.get( '/', ( req, res ) => {
res.send( '<!DOCTYPE html>' +
renderToStaticMarkup(
<Page app={ renderToString( <App /> )} />
)
);
});

app.listen( 3000, () => {
console.log( 'Listening for requests on http://localhost:3000' );
});
90 changes: 90 additions & 0 deletions src/LineChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { createClass, PropTypes } from 'react';
import tween from 'tweening';

const LineChart = createClass({
propTypes: {
chartClassName: PropTypes.string,
easing: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
height: PropTypes.number,
lineClassName: PropTypes.string,
lines: PropTypes.array.isRequired,
preserveAspectRatio: PropTypes.string,
width: PropTypes.number,
},

getDefaultProps () {
return {
duration: 400,
easing: 'easeInOutQuad',
height: 500,
preserveAspectRatio: 'xMidYMid meet',
width: 800,
};
},

getInitialState () {
return {
lines: this.relativeLines( this.props.lines ),
spacing: this.props.width / ( this.props.lines.reduce(( p, c ) => {
return Math.max( p, c.points.length );
}, 0 ) - 1 ),
};
},

componentWillReceiveProps ({ lines }) {
const relativeLines = this.relativeLines( lines );
if ( JSON.stringify( relativeLines ) !== JSON.stringify( this.state.lines )) {
this.animateLines( this.state.lines, relativeLines );
}
},

render () {
return (
<svg
className={ this.props.chartClassName }
height={ this.props.height }
preserveAspectRatio={ this.props.preserveAspectRatio }
width={ this.props.width }
viewBox={ `0 0 ${ this.props.width } ${ this.props.height }` }
>
{ this.state.lines.map(({ points }, i ) => (
<polyline
className={ this.props.lineClassName }
fill="none"
key={ i }
points={ points.map(( p, j ) => (
`${ this.state.spacing * j },${ p.value }`
)).join( ',' )}
stroke="rgb(241,76,84)"
strokeWidth="5"
/>
))}
</svg>
);
},

animateLines ( from, to ) {
tween({
duration: this.props.duration,
easing: this.props.easing,
from,
to,
next: lines => this.setState({ lines }),
});
},

relativeLines ( lines ) {
const absolutePercent = this.props.height / 100;

const relativePercent = Math.max(
...lines.map( l => Math.max( ...l.points.map( p => p.value )))
) / 100;

return lines.map( l => ({
...l,
points: l.points.map( p => ({ ...p, value: this.props.height - ( p.value / relativePercent * absolutePercent )})),
}));
}
});

export default LineChart;
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BarChart from './BarChart';
import LineChart from './LineChart';

export { BarChart };
export { BarChart, LineChart };

0 comments on commit 875029f

Please sign in to comment.