diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3b4e8dc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+lib/
+node_modules/
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..a169b3d
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,4 @@
+.gitignore
+.travis.yml
+examples/
+src/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..31ca458
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+sudo: false
+language: node_js
+cache:
+ directories:
+ - node_modules
+notifications:
+ email: false
+node_js:
+ - '5.7'
+before_install:
+ - npm i -g npm@^3.6.0
+before_script:
+ - npm prune
+after_success:
+ - npm run semantic-release
+branches:
+ except:
+ - "/^v\\d+\\.\\d+\\.\\d+$/"
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..98f6262
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,16 @@
+Internet Systems Consortium license
+===================================
+
+Copyright (c) `2016`, `Colin Meinke`
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d08113f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+# React SVG chart
+
+Animated SVG charts for React.
+
+## Installation
+
+```
+npm install react-svg-chart
+```
+
+## Usage
+
+```js
+import React from 'react';
+import { BarChart } from 'react-svg-chart';
+
+const App = () => (
+
+);
+```
diff --git a/examples/barChart/.gitignore b/examples/barChart/.gitignore
new file mode 100644
index 0000000..8c7d1b8
--- /dev/null
+++ b/examples/barChart/.gitignore
@@ -0,0 +1,2 @@
+client.dist.js
+node_modules/
diff --git a/examples/barChart/App.js b/examples/barChart/App.js
new file mode 100644
index 0000000..2a232c7
--- /dev/null
+++ b/examples/barChart/App.js
@@ -0,0 +1,67 @@
+import React, { createClass } from 'react';
+import { BarChart } from '../../src';
+
+const days = [
+ {
+ title: 'Thursday, 9th March',
+ bars: [
+ { value: 3.50 },
+ { value: 7.45 },
+ { value: 1.27 },
+ { value: 1.15 },
+ { value: 2.93 },
+ ],
+ },
+ {
+ title: 'Wednesday, 8th March',
+ bars: [
+ { value: 1.92 },
+ { value: 1.11 },
+ { value: 7.20 },
+ { value: 6.34 },
+ { value: 3.15 },
+ ],
+ },
+ {
+ title: 'Tuesday, 7th March',
+ bars: [
+ { 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 (
+
+
+
+
+ );
+ }
+});
+
+export default App;
diff --git a/examples/barChart/Page.js b/examples/barChart/Page.js
new file mode 100644
index 0000000..054ee89
--- /dev/null
+++ b/examples/barChart/Page.js
@@ -0,0 +1,41 @@
+import React, { PropTypes } from 'react';
+
+const propTypes = {
+ app: PropTypes.string.isRequired,
+};
+
+const Page = ({ app }) => (
+
+
+
+
+
+
+
+
+
+);
+
+Page.propTypes = propTypes;
+
+export default Page;
diff --git a/examples/barChart/client.js b/examples/barChart/client.js
new file mode 100644
index 0000000..12d276b
--- /dev/null
+++ b/examples/barChart/client.js
@@ -0,0 +1,8 @@
+import 'babel-polyfill';
+
+import React from 'react';
+import { render } from 'react-dom';
+
+import App from './App';
+
+render( , document.querySelector( '.app' ));
diff --git a/examples/barChart/package.json b/examples/barChart/package.json
new file mode 100644
index 0000000..79d1965
--- /dev/null
+++ b/examples/barChart/package.json
@@ -0,0 +1,43 @@
+{
+ "author": {
+ "name": "Colin Meinke",
+ "email": "hello@colinmeinke.com",
+ "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 bar 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"
+}
diff --git a/examples/barChart/server.js b/examples/barChart/server.js
new file mode 100644
index 0000000..c5ab2fa
--- /dev/null
+++ b/examples/barChart/server.js
@@ -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( '' +
+ renderToStaticMarkup(
+ )} />
+ )
+ );
+});
+
+app.listen( 3000, () => {
+ console.log( 'Listening for requests on http://localhost:3000' );
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..14e974d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,69 @@
+{
+ "author": {
+ "name": "Colin Meinke",
+ "email": "hello@colinmeinke.com",
+ "url": "www.colinmeinke.com"
+ },
+ "babel": {
+ "plugins": [
+ "transform-object-rest-spread"
+ ],
+ "presets": [
+ "es2015",
+ "react"
+ ]
+ },
+ "bugs": {
+ "url": "https://github.com/colinmeinke/react-svg-chart/issues"
+ },
+ "config": {
+ "commitizen": {
+ "path": "node_modules/cz-conventional-changelog"
+ }
+ },
+ "dependencies": {
+ "svg-tween": "^1.0.1"
+ },
+ "description": "Animated SVG charts for React",
+ "devDependencies": {
+ "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",
+ "commitizen": "^2.5.0",
+ "cz-conventional-changelog": "^1.1.5",
+ "react": "^0.14.7",
+ "semantic-release": "^4.3.5"
+ },
+ "keywords": [
+ "animate",
+ "bar",
+ "chart",
+ "data",
+ "ease",
+ "graph",
+ "line",
+ "path",
+ "points",
+ "react",
+ "svg",
+ "tween"
+ ],
+ "license": "ISC",
+ "main": "lib/index.js",
+ "name": "react-svg-chart",
+ "peerDependencies": {
+ "react": "^0.14.7"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/colinmeinke/react-svg-chart.git"
+ },
+ "scripts": {
+ "build": "babel src --out-dir lib",
+ "commit": "git-cz",
+ "prepublish": "npm run build",
+ "semantic-release": "semantic-release pre && npm publish && semantic-release post"
+ },
+ "version": "0.0.0-semantically-released"
+}
diff --git a/src/BarChart.js b/src/BarChart.js
new file mode 100644
index 0000000..7bc40d5
--- /dev/null
+++ b/src/BarChart.js
@@ -0,0 +1,83 @@
+import React, { createClass, PropTypes } from 'react';
+import tween from 'tweening';
+
+const BarChart = createClass({
+ propTypes: {
+ barClassName: PropTypes.string,
+ bars: PropTypes.array.isRequired,
+ chartClassName: PropTypes.string,
+ easing: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
+ height: PropTypes.number,
+ spacing: PropTypes.number,
+ width: PropTypes.number,
+ },
+
+ getDefaultProps () {
+ return {
+ duration: 750,
+ easing: 'easeOutBounce',
+ height: 500,
+ spacing: 10,
+ width: 800,
+ };
+ },
+
+ getInitialState () {
+ return {
+ bars: this.props.bars.map( b => ({ ...b, value: 0 })),
+ barHeight: ( this.props.height - this.props.spacing * ( this.props.bars.length - 1 )) / this.props.bars.length,
+ };
+ },
+
+ componentDidMount () {
+ this.animateBars( this.state.bars, this.relativeBars( this.props.bars ));
+ },
+
+ componentWillReceiveProps ({ bars }) {
+ const relativeBars = this.relativeBars( bars );
+ if ( JSON.stringify( relativeBars ) !== JSON.stringify( this.state.bars )) {
+ this.animateBars( this.state.bars, relativeBars );
+ }
+ },
+
+ render () {
+ return (
+
+ );
+ },
+
+ animateBars ( from, to ) {
+ tween({
+ duration: this.props.duration,
+ easing: this.props.easing,
+ from,
+ to,
+ next: bars => this.setState({ bars }),
+ });
+ },
+
+ relativeBars ( bars ) {
+ const absolutePercent = this.props.width / 100;
+ const relativePercent = Math.max( ...bars.map( b => b.value )) / 100;
+ return bars.map( b => ({ ...b, value: b.value / relativePercent * absolutePercent }));
+ }
+});
+
+export default BarChart;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..c5bd01b
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,3 @@
+import BarChart from './BarChart';
+
+export { BarChart };