├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── etc ├── bar_graph_1.gif └── donut.gif ├── example ├── .eslintrc ├── .gitignore ├── README.md ├── demo.jsx ├── index.html └── package.json ├── gulpfile.babel.js ├── package.json └── src ├── BarChart.jsx ├── Chart.jsx ├── Colors.js ├── DataSeries.jsx ├── Group.jsx ├── Pie.jsx ├── Stack.jsx ├── XAxis.jsx ├── YAxis.jsx ├── constants.js ├── helpers ├── axes.js └── scales.js ├── index.js ├── styles.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-1" 6 | ], 7 | "plugins": [ 8 | 9 | ], 10 | "ignore": [ 11 | "build" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Transpiled javascript 2 | build/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "binaryLiterals": true, 13 | "blockBindings": true, 14 | "classes": true, 15 | "defaultParams": true, 16 | "destructuring": true, 17 | "forOf": true, 18 | "generators": true, 19 | "modules": true, 20 | "objectLiteralComputedProperties": true, 21 | "objectLiteralDuplicateProperties": true, 22 | "objectLiteralShorthandMethods": true, 23 | "objectLiteralShorthandProperties": true, 24 | "octalLiterals": true, 25 | "regexUFlag": false, 26 | "regexYFlag": false, 27 | "restParams": true, 28 | "spread": true, 29 | "superInFunctions": true, 30 | "templateStrings": true, 31 | "unicodeCodePointEscapes": true, 32 | "globalReturn": true, 33 | "jsx": true, 34 | "experimentalObjectRestSpread": true 35 | } 36 | }, 37 | "rules": { 38 | "no-mixed-requires": [ 39 | 0, 40 | false 41 | ], 42 | "strict": [ 43 | 1, 44 | "never" 45 | ], 46 | "no-undef": 2, 47 | "no-delete-var": 2, 48 | "no-shadow": 2, 49 | "no-shadow-restricted-names": 2, 50 | "no-unused-vars": [ 51 | 2, 52 | { 53 | "vars": "all", 54 | "args": "after-used" 55 | } 56 | ], 57 | "no-undefined": 2, 58 | "no-use-before-define": 2, 59 | "no-unneeded-ternary": 1, 60 | "no-obj-calls": 2, 61 | "no-negated-in-lhs": 2, 62 | "no-inner-declarations": 2, 63 | "curly": 1, 64 | "camelcase": 2, 65 | "max-len": [ 66 | 1, 67 | 100, 68 | 4, 69 | { 70 | "ignoreComments": true, 71 | "ignoreUrls": true, 72 | "ignorePattern": "^\\s*import\\s+" 73 | } 74 | ], 75 | "padded-blocks": [ 76 | 0, 77 | "always" 78 | ], 79 | "no-invalid-regexp": 2, 80 | "vars-on-top": 0, 81 | "guard-for-in": 1, 82 | "no-underscore-dangle": 0, 83 | "no-func-assign": 2, 84 | "no-extra-parens": 0, 85 | "no-loop-func": 1, 86 | "no-multi-spaces": 1, 87 | "comma-dangle": [ 88 | 1, 89 | "never" 90 | ], 91 | "indent": [ 92 | 2, 93 | 4, 94 | { 95 | "SwitchCase": 1, 96 | "VariableDeclarator": 1 97 | } 98 | ], 99 | "quotes": [ 100 | 2, 101 | "single" 102 | ], 103 | "semi": [ 104 | 2, 105 | "always" 106 | ], 107 | "semi-spacing": [ 108 | 2, 109 | { 110 | "before": false, 111 | "after": true 112 | } 113 | ], 114 | "brace-style": [ 115 | 2, 116 | "1tbs", 117 | { 118 | "allowSingleLine": true 119 | } 120 | ], 121 | "key-spacing": [ 122 | 2, 123 | { 124 | "beforeColon": false, 125 | "afterColon": true 126 | } 127 | ], 128 | "new-cap": [ 129 | 2, 130 | { 131 | "newIsCap": true, 132 | "capIsNew": true, 133 | "capIsNewExceptions": [ 134 | "Immutable", 135 | "Set" 136 | ] 137 | } 138 | ], 139 | "keyword-spacing": [ 140 | 2, 141 | { 142 | "before": true, 143 | "after": true 144 | } 145 | ], 146 | "space-before-blocks": [ 147 | 1, 148 | "always" 149 | ], 150 | "array-bracket-spacing": [ 151 | 2, 152 | "never" 153 | ], 154 | "object-curly-spacing": [ 155 | 2, 156 | "never", 157 | { 158 | "objectsInObjects": false, 159 | "arraysInObjects": false 160 | } 161 | ], 162 | "computed-property-spacing": [ 163 | 1, 164 | "never" 165 | ], 166 | "space-in-parens": [ 167 | 1, 168 | "never" 169 | ], 170 | "space-before-function-paren": [ 171 | 1, 172 | "never" 173 | ], 174 | "space-infix-ops": [ 175 | 2, 176 | { 177 | "int32Hint": false 178 | } 179 | ], 180 | "max-nested-callbacks": [ 181 | 2, 182 | 3 183 | ], 184 | "one-var": [ 185 | 0, 186 | "always" 187 | ], 188 | "eqeqeq": [ 189 | 2, 190 | "smart" 191 | ], 192 | "accessor-pairs": [ 193 | 1, 194 | { 195 | "getWithoutSet": true 196 | } 197 | ], 198 | "no-cond-assign": [ 199 | 2, 200 | "except-parens" 201 | ], 202 | "valid-typeof": 1, 203 | "no-div-regex": 2, 204 | "no-control-regex": 2, 205 | "no-caller": 2, 206 | "no-alert": 2, 207 | "no-with": 2, 208 | "no-duplicate-case": 2, 209 | "no-dupe-args": 2, 210 | "no-dupe-keys": 2, 211 | "no-unreachable": 2, 212 | "use-isnan": 2, 213 | "no-redeclare": [ 214 | 2, 215 | { 216 | "builtinGlobals": true 217 | } 218 | ], 219 | "no-useless-call": 2, 220 | "no-void": 2, 221 | "no-new-wrappers": 2, 222 | "no-new-require": 2, 223 | "no-path-concat": 2, 224 | "no-lonely-if": 1, 225 | "no-array-constructor": 1, 226 | "no-unexpected-multiline": 2, 227 | "linebreak-style": [ 228 | 2, 229 | "unix" 230 | ], 231 | "no-trailing-spaces": [ 232 | 1, 233 | { 234 | "skipBlankLines": false 235 | } 236 | ], 237 | "eol-last": 2, 238 | "prefer-spread": 1, 239 | "no-this-before-super": 2, 240 | "constructor-super": 2, 241 | "prefer-const": 1, 242 | "no-bitwise": 1, 243 | "babel/generator-star-spacing": 1, 244 | "babel/new-cap": 0, 245 | "babel/object-curly-spacing": 1, 246 | "babel/object-shorthand": 1, 247 | "babel/arrow-parens": 0, 248 | 249 | "react/display-name": 2, 250 | "react/forbid-prop-types": 0, 251 | "react/jsx-boolean-value": 1, 252 | "react/jsx-closing-bracket-location": [ 253 | 1, 254 | { 255 | "selfClosing": "after-props", 256 | "nonEmpty": "after-props" 257 | } 258 | ], 259 | "react/jsx-curly-spacing": 1, 260 | "react/jsx-indent-props": 1, 261 | "react/jsx-max-props-per-line": [ 262 | 1, 263 | { 264 | "maximum": 3 265 | } 266 | ], 267 | "react/jsx-no-duplicate-props": 2, 268 | "react/jsx-no-literals": 1, 269 | "react/jsx-no-undef": 2, 270 | "jsx-quotes": [ 271 | 1, 272 | "prefer-double" 273 | ], 274 | "react/sort-prop-types": 0, 275 | "react/jsx-sort-props": [ 276 | 0, 277 | { 278 | "callbacksLast": true 279 | } 280 | ], 281 | "react/jsx-uses-react": 1, 282 | "react/jsx-uses-vars": 1, 283 | "react/no-danger": 1, 284 | "react/jsx-pascal-case": 1, 285 | "react/no-did-mount-set-state": 2, 286 | "react/no-did-update-set-state": 2, 287 | "react/no-direct-mutation-state": 2, 288 | "react/no-multi-comp": 0, 289 | "react/no-set-state": 0, 290 | "react/no-unknown-property": 2, 291 | "react/prop-types": 0, 292 | "react/react-in-jsx-scope": 2, 293 | "react/require-extension": 0, 294 | "react/self-closing-comp": 1, 295 | "react/sort-comp": 1, 296 | "react/wrap-multilines": 1 297 | }, 298 | "plugins": [ 299 | "babel", 300 | "react" 301 | ] 302 | } 303 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | build 4 | diffract-*.tgz 5 | # jsx Transform Disk Cache 6 | .module-cache 7 | .tmp 8 | 9 | # Exclude compiled files 10 | lib 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | example 3 | src 4 | node_modules 5 | etc 6 | lib 7 | *.tgz 8 | .* 9 | .* 10 | # jsx Transform Disk Cache 11 | .module-cache 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Amey Sakhadeo 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diffract 2 | [![npm version](https://badge.fury.io/js/diffract.svg)](http://badge.fury.io/js/diffract) 3 | [![devDependency Status](https://david-dm.org/ameyms/diffract/dev-status.svg)](https://david-dm.org/ameyms/diffract#info=devDependencies) 4 | [![peerDependency Status](https://david-dm.org/ameyms/diffract/peer-status.svg)](https://david-dm.org/ameyms/diffract#info=peerDependencies) 5 | 6 | A set of d3 based visualization components with **cool animations** built for React 7 | 8 | Installation 9 | --- 10 | Diffract is available via npm and can be used along with [browserify](http://browserify.org/) 11 | 12 | ```shell 13 | npm install diffract 14 | 15 | ``` 16 | 17 | ## Usage 18 | 19 | After installing diffract via NPM, you may use the components in your code as follows: 20 | 21 | ```js 22 | /** DeathStar.jsx */ 23 | 24 | import React from 'react'; 25 | import {Chart, DataSeries, Pie} from 'diffract'; 26 | 27 | // ...And use it in your code 28 | class DeathStar extends Component { 29 | 30 | render: function() { 31 | return ( 32 | 33 | 34 | ({fill: this.getColors(i)})}> 36 | 38 | {'Hello'} 39 | 40 | 42 | {'diffract'} 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | 50 | } 51 | 52 | ``` 53 | 54 | ## More Examples 55 | See [examples](https://github.com/ameyms/diffract/blob/master/example/demo.jsx) for more 56 | examples including Donuts, BarChart, stacked bar chart, grouped bar chart etc. 57 | ## Demo 58 | ### Donut 59 | ![Donut](https://raw.github.com/ameyms/diffract/master/etc/donut.gif) 60 | 61 | ### Bar Graph 62 | ![Bar Graph](https://raw.github.com/ameyms/diffract/master/etc/bar_graph_1.gif) 63 | 64 | 65 | 66 | 67 | ---- 68 | 69 | 70 | License: MIT 71 | -------------------------------------------------------------------------------- /etc/bar_graph_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameyms/diffract/6b9805819abb7818f9dcb9c69446ec9c2e36e09c/etc/bar_graph_1.gif -------------------------------------------------------------------------------- /etc/donut.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameyms/diffract/6b9805819abb7818f9dcb9c69446ec9c2e36e09c/etc/donut.gif -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Demo JS files 2 | demo.bundle.js 3 | 4 | node_modules 5 | build 6 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # [diffract](http://github.com/ameyms/diffract) - Example Project 2 | 3 | This is an example project that uses [Diffract](http://github.com/ameyms/diffract). 4 | 5 | ## Installation 6 | After cloning the repository, install dependencies: 7 | 8 | ```shell 9 | npm install # Install diffract`s dependencies 10 | cd example # CD to example project 11 | npm install # Install example project`s dependencies 12 | ``` 13 | 14 | ## Run demos 15 | Now you can run your local server: 16 | 17 | ```shell 18 | grunt demo 19 | ``` 20 | 21 | You can now point your browser to http://localhost:9001 to view the demos 22 | -------------------------------------------------------------------------------- /example/demo.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { 4 | util, Chart, DataSeries, BarChart, Pie, XAxis, YAxis, Stack, Group 5 | } from 'diffract'; 6 | 7 | const colors = ['#E91E63', '#2196F3', '#FF9800', '#4CAF50', '#673AB7']; 8 | const width = 640; 9 | const height = 240; 10 | let cnt = 1; 11 | 12 | const getRandomValuesArray = () => ([ 13 | Math.random() * 10000, Math.random() * 10000, 14 | Math.random() * 10000, Math.random() * 10000, 15 | Math.random() * 10000 16 | ]); 17 | 18 | const margins = { 19 | left: 50, 20 | bottom: 20, 21 | top: 0, 22 | right: 0 23 | }; 24 | 25 | const xScale = util.scale.ordinal().rangeRoundBands([0, width - margins.left - margins.right], 0.2); 26 | const yScale = util.scale.linear().rangeRound([height - margins.top - margins.bottom, 0]); 27 | 28 | class App extends Component { 29 | 30 | constructor() { 31 | super(); 32 | this.state = { 33 | values: [Math.random() * 10000, Math.random() * 10000, 34 | Math.random() * 10000, Math.random() * 10000, 35 | Math.random() * 10000], 36 | 37 | multiValues: [ 38 | getRandomValuesArray(), getRandomValuesArray(), 39 | getRandomValuesArray(), getRandomValuesArray(), 40 | getRandomValuesArray() 41 | ], 42 | 43 | labels: ['Elves', 'Dwarves', 44 | 'Hobbitses', 'Men', 'Wizards'] 45 | }; 46 | } 47 | 48 | componentDidMount() { 49 | this._updater = setInterval(this.updateData.bind(this), 5000); 50 | } 51 | 52 | componentWillUnmount() { 53 | clearInterval(this._updater); 54 | } 55 | 56 | getColors(d, i) { 57 | if (arguments.length === 2) { 58 | return colors[i]; 59 | } else { 60 | return colors[d]; 61 | } 62 | 63 | } 64 | 65 | getPieChart() { 66 | return ( 67 | 68 | 69 | console.log(this.state.labels[i] + ' clicked')} 71 | style={(d, i) => ({fill: this.getColors(i)})}> 72 | 74 | {'Hello'} 75 | 76 | 78 | {'diffract'} 79 | 80 | 81 | 82 | 83 | ); 84 | } 85 | 86 | updateData() { 87 | 88 | if (cnt++ % 3) { 89 | this.setState({ 90 | 91 | values: getRandomValuesArray(), 92 | 93 | multiValues: [ 94 | getRandomValuesArray(), getRandomValuesArray(), 95 | getRandomValuesArray(), getRandomValuesArray(), 96 | getRandomValuesArray() 97 | ], 98 | labels: ['Elves', 'Dwarves', 99 | 'Hobbitses', 'Men', 'Some really long label']}); 100 | } else { 101 | this.setState({ 102 | values: [ 103 | Math.random() * 10000, Math.random() * 10000, 104 | Math.random() * 10000, Math.random() * 10000 105 | ], 106 | multiValues: [ 107 | getRandomValuesArray(), getRandomValuesArray(), 108 | getRandomValuesArray(), getRandomValuesArray() 109 | ], 110 | 111 | labels: ['Elves', 'Dwarves', 112 | 'Hobbitses', 'Men']}); 113 | } 114 | } 115 | 116 | getBarChart() { 117 | return ( 118 | 120 | 122 | console.log(this.state.labels[i] + ' clicked')} 123 | style={(d, i) => ({fill: this.getColors(i)})}/> 124 | this.state.labels[i]} debug/> 125 | { 127 | return d; 128 | }}/> 129 | 130 | 131 | ); 132 | } 133 | 134 | getStackedBarChart() { 135 | return ( 136 | 138 | 140 | 141 | ({fill: this.getColors(i)})}/> 142 | this.state.labels[i]}/> 143 | { 145 | return d; 146 | }}/> 147 | 148 | 149 | 150 | ); 151 | } 152 | 153 | getGroupedBarChart() { 154 | return ( 155 | 158 | 160 | 161 | ({fill: this.getColors(i)})}/> 162 | this.state.labels[i]}/> 163 | { 165 | return d; 166 | }}/> 167 | 168 | 169 | 170 | ); 171 | } 172 | 173 | render() { 174 | 175 | const donut = this.getPieChart(); 176 | const barGraph = this.getBarChart(); 177 | const stackedBarGraph = this.getStackedBarChart(); 178 | const groupedBarGraph = this.getGroupedBarChart(); 179 | const padding = {padding: '50px'}; 180 | 181 | return ( 182 |
183 |

{'Diffract demos'}

184 |
185 |

{'Donut'}

186 | {donut} 187 |
188 |
189 |

{'Bar Graph'}

190 | {barGraph} 191 |
192 |
193 |

{'Stacked Chart'}

194 | {stackedBarGraph} 195 |
196 |
197 |

{'Grouped Chart'}

198 | {groupedBarGraph} 199 |
200 | 201 |
202 | ); 203 | } 204 | } 205 | 206 | App.displayName = 'App'; 207 | 208 | ReactDom.render( 209 | , 210 | document.getElementById('appRoot') 211 | ); 212 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | diffract Demo 6 | 8 | 9 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diffract-example", 3 | "version": "0.0.2", 4 | "description": "Showcase of diffract d3 widgets", 5 | "dependencies": { 6 | "d3": "^3.5.6", 7 | "react": "^0.14.7", 8 | "react-addons-transition-group": "^0.14.7", 9 | "react-dom": "^0.14.1" 10 | }, 11 | "author": "Amey Sakhadeo", 12 | "license": "ISC", 13 | "devDependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | 3 | import gulp from 'gulp'; 4 | import {exec} from 'child_process'; 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | 8 | import gulpLoadPlugins from 'gulp-load-plugins'; 9 | import browserify from 'browserify'; 10 | import babelify from 'babelify'; 11 | import del from 'del'; 12 | import opn from 'opn'; 13 | import Promise from 'bluebird'; 14 | import srcStream from 'vinyl-source-stream'; 15 | const $ = gulpLoadPlugins(); 16 | const pkg = JSON.parse(fs.readFileSync('./package.json')); 17 | 18 | function lint(files, options) { 19 | return () => { 20 | return gulp.src(files) 21 | .pipe($.eslint(options)) 22 | .pipe($.eslint.format()); 23 | }; 24 | } 25 | 26 | gulp.task('clean', del.bind(null, ['build', `${pkg.name}-*.tgz`])); 27 | 28 | gulp.task('lint', lint(['src/{,*/}*.js{,x}'])); 29 | 30 | gulp.task('npmpack', ['build'], () => { 31 | 32 | return new Promise((resolve, reject) => { 33 | exec('npm pack', (err, stdout) => { 34 | 35 | if (err) { 36 | reject(err); 37 | } else { 38 | resolve(stdout.toString()); 39 | } 40 | }); 41 | }); 42 | }); 43 | 44 | gulp.task('demoInstall', ['npmpack'], () => { 45 | 46 | return new Promise((resolve, reject) => { 47 | exec(`npm install ../${pkg.name}-${pkg.version}.tgz`, { 48 | cwd: path.join(__dirname, 'example') 49 | }, (err, stdout) => { 50 | if (err) { 51 | reject(err); 52 | } else { 53 | resolve(stdout.toString()); 54 | } 55 | }); 56 | }); 57 | }); 58 | 59 | gulp.task('demoBuild', ['demoInstall'], () => { 60 | return browserify({ 61 | entries: 'example/demo.jsx', 62 | debug: true, 63 | transform: [babelify] 64 | }).bundle() 65 | .pipe(srcStream('demo.bundle.js')) 66 | .pipe(gulp.dest('example')); 67 | 68 | }); 69 | 70 | gulp.task('server', ['demoBuild'], () => { 71 | return $.connect.server({ 72 | port: 9000, 73 | root: 'example', 74 | livereload: true 75 | }); 76 | }); 77 | gulp.task('demo', ['server', 'watch'], () => { 78 | opn('http://localhost:9000'); 79 | }); 80 | 81 | gulp.task('watch', () => { 82 | gulp.watch(['src/{,*/}*.js{,x}', 'example/demo.jsx'], ['demoBuild']); 83 | gulp.watch(['example/*.html', 'example/demo.bundle.js']). 84 | on('change', (file) => { 85 | gulp.src(file.path) 86 | .pipe($.connect.reload()); 87 | }); 88 | }); 89 | 90 | gulp.task('build', ['clean', 'lint'], () => { 91 | 92 | return gulp.src('src/{,*/}*.js{,x}') 93 | .pipe($.plumber()) 94 | .pipe($.babel()) 95 | .pipe(gulp.dest('build/')); 96 | }); 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diffract", 3 | "author": "Amey Sakhadeo", 4 | "version": "0.3.2", 5 | "description": "A set of d3 based visualization components built for React", 6 | "main": "build/index.js", 7 | "files": [ 8 | "build", 9 | "package.json" 10 | ], 11 | "scripts": {}, 12 | "keywords": [ 13 | "react", 14 | "react-component", 15 | "d3", 16 | "visualization", 17 | "graphs" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/ameyms/diffract.git" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/ameyms/diffract/issues" 26 | }, 27 | "dependencies": { 28 | "d3": "^3.5.16", 29 | "react-motion": "^0.4.2", 30 | "react-addons-transition-group": "^0.14.0 || ^15.0.2" 31 | }, 32 | "peerDependencies": { 33 | "react": "^0.14.0 || ^15.0.2", 34 | "react-dom": "^0.14.0 || ^15.0.2" 35 | }, 36 | "devDependencies": { 37 | "babel": "6.5.2", 38 | "babel-core": "^6.8.0", 39 | "babel-eslint": "^6.0.4", 40 | "babel-plugin-constant-folding": "^1.0.1", 41 | "babel-plugin-dead-code-elimination": "^1.0.2", 42 | "babel-plugin-react-transform": "^2.0.0", 43 | "babel-plugin-uglify": "^1.0.2", 44 | "babel-preset-es2015": "^6.0.11", 45 | "babel-preset-react": "^6.0.2", 46 | "babel-preset-stage-0": "^6.5.0", 47 | "babel-preset-stage-1": "^6.5.0", 48 | "babel-preset-stage-2": "^6.5.0", 49 | "babel-register": "^6.8.0", 50 | "babel-runtime": "6.6.1", 51 | "babelify": "^7.2.0", 52 | "bluebird": "^3.3.4", 53 | "browserify": "^13.0.0", 54 | "del": "^2.2.0", 55 | "eslint": "^2.9.0", 56 | "eslint-plugin-babel": "^3.1.0", 57 | "eslint-plugin-react": "^5.0.1", 58 | "gulp": "^3.9.1", 59 | "gulp-babel": "^6.1.1", 60 | "gulp-cache": "^0.4.2", 61 | "gulp-connect": "^4.0.0", 62 | "gulp-eslint": "^2.0.0", 63 | "gulp-if": "^2.0.0", 64 | "gulp-load-plugins": "^1.2.0", 65 | "gulp-nodemon": "^2.0.6", 66 | "gulp-plumber": "^1.0.1", 67 | "gulp-size": "^2.0.0", 68 | "gulp-util": "^3.0.7", 69 | "opn": "^4.0.1", 70 | "vinyl-source-stream": "^1.1.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/BarChart.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import d3 from 'd3'; 3 | import {TransitionMotion, Motion, spring} from 'react-motion'; 4 | 5 | export default class BarChart extends Component { 6 | 7 | static displayName = 'BarChart' 8 | 9 | static propTypes = { 10 | style: PropTypes.func, 11 | onClick: PropTypes.func 12 | } 13 | 14 | static contextTypes = { 15 | data: PropTypes.oneOfType([ 16 | PropTypes.arrayOf(PropTypes.number), 17 | PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)) 18 | ]), 19 | height: PropTypes.number.isRequired, 20 | width: PropTypes.number.isRequired, 21 | margin: PropTypes.shape({ 22 | top: PropTypes.number, 23 | bottom: PropTypes.number, 24 | left: PropTypes.number, 25 | right: PropTypes.number 26 | }), 27 | xScale: PropTypes.func.isRequired, 28 | yScale: PropTypes.func.isRequired, 29 | dataTransform: PropTypes.func, 30 | groupWidth: PropTypes.number 31 | } 32 | 33 | static defaultProps = { 34 | style: () => ({}), 35 | onClick: () => {} 36 | } 37 | 38 | willLeave({style}) { 39 | 40 | return { 41 | ...style, 42 | width: 0, 43 | x: style.x + style.width 44 | }; 45 | } 46 | 47 | willEnter({style}) { 48 | return { 49 | ...style, 50 | width: 0, 51 | x: style.x + style.width 52 | }; 53 | } 54 | 55 | render() { 56 | 57 | const { 58 | xScale, yScale, data, 59 | dataTransform = arr => arr.map((d, i) => ({ 60 | x: i, 61 | dx: 0, 62 | y0: 0, 63 | y: d, 64 | z: 0, 65 | index: i 66 | })), 67 | groupWidth = 1 68 | 69 | } = this.context; 70 | 71 | const {style, onClick} = this.props; 72 | 73 | const txData = dataTransform(data); 74 | 75 | const xScaleFn = xScale. 76 | domain(d3.range(data.length)); 77 | 78 | 79 | const yScaleFn = yScale. 80 | domain([0, d3.max(txData, d => d.y0 + d.y)]); 81 | 82 | const motionStyles = txData.map(d => ({ 83 | key: d.index + '-' + d.z, 84 | data: {...d, index: d.index}, 85 | style: { 86 | width: xScaleFn.rangeBand() / groupWidth, 87 | height: yScaleFn(d.y0) - yScaleFn(d.y0 + d.y), 88 | y0: yScaleFn(d.y0), 89 | y: yScaleFn(d.y0 + d.y), 90 | x: xScaleFn(d.x) + xScaleFn.rangeBand() / groupWidth * d.dx 91 | 92 | } 93 | })); 94 | 95 | const defaultStyles = txData.map(d => ({ 96 | key: d.index + '-' + d.z, 97 | data: {...d, index: d.index}, 98 | style: { 99 | width: xScaleFn.rangeBand() / groupWidth, 100 | height: 0, 101 | y0: yScaleFn(d.y0), 102 | y: yScaleFn(d.y0), 103 | x: xScaleFn(d.x) + xScaleFn.rangeBand() / groupWidth * d.dx 104 | } 105 | })); 106 | 107 | return ( 108 | 109 | 111 | {interStyles => ( 112 | 113 | {interStyles.map(config => ( 114 | 127 | {interStyle => { 128 | 129 | return ( 130 | { 133 | onClick( 134 | e, 135 | config.data.y, 136 | config.data.index 137 | ); 138 | }} 139 | x={interStyle.x} y={interStyle.y} 140 | style={style( 141 | config.data.y, 142 | config.data.index 143 | )}/> 144 | ); 145 | } 146 | } 147 | 148 | ))} 149 | 150 | )} 151 | 152 | {this.props.children} 153 | 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Chart.jsx: -------------------------------------------------------------------------------- 1 | // "transform-remove-console" 2 | import React, {Component, PropTypes} from 'react'; 3 | 4 | export default class Chart extends Component { 5 | 6 | static displayName = 'Chart' 7 | 8 | static childContextTypes = { 9 | height: PropTypes.number, 10 | width: PropTypes.number, 11 | margin: PropTypes.shape({ 12 | top: PropTypes.number, 13 | bottom: PropTypes.number, 14 | left: PropTypes.number, 15 | right: PropTypes.number 16 | }) 17 | }; 18 | 19 | static propTypes = { 20 | height: PropTypes.number, 21 | width: PropTypes.number, 22 | margin: PropTypes.shape({ 23 | top: PropTypes.number, 24 | bottom: PropTypes.number, 25 | left: PropTypes.number, 26 | right: PropTypes.number 27 | }) 28 | }; 29 | 30 | static defaultProps = { 31 | height: 0, 32 | width: 0, 33 | margin: { 34 | top: 0, 35 | bottom: 0, 36 | left: 0, 37 | right: 0 38 | } 39 | }; 40 | 41 | 42 | getChildContext() { 43 | const {width, height} = this.props; 44 | let {margin} = this.props; 45 | 46 | margin = { 47 | top: 0, 48 | bottom: 0, 49 | left: 0, 50 | right: 0, 51 | ...margin 52 | }; 53 | 54 | return {width, height, margin}; 55 | } 56 | render(): () => any { 57 | 58 | const {width, height} = this.props; 59 | let {margin} = this.props; 60 | 61 | margin = { 62 | top: 0, 63 | bottom: 0, 64 | left: 0, 65 | right: 0, 66 | ...margin 67 | }; 68 | 69 | const insetString = `translate(${margin.left}, ${margin.top})`; 70 | return ( 71 |
77 | 78 | 79 | {this.props.children} 80 | 81 | 82 | 83 |
86 |
87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Colors.js: -------------------------------------------------------------------------------- 1 | export const AXIS_LINES = '#999'; 2 | -------------------------------------------------------------------------------- /src/DataSeries.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | 3 | export default class DataSeries extends Component { 4 | 5 | static displayName = 'DataSeries' 6 | 7 | static propTypes = { 8 | xScale: PropTypes.func, 9 | yScale: PropTypes.func, 10 | data: PropTypes.oneOfType([ 11 | PropTypes.arrayOf(PropTypes.number), 12 | PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)) 13 | ]).isRequired 14 | } 15 | 16 | static childContextTypes = { 17 | xScale: PropTypes.func, 18 | yScale: PropTypes.func, 19 | data: PropTypes.oneOfType([ 20 | PropTypes.arrayOf(PropTypes.number), 21 | PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)) 22 | ]) 23 | } 24 | 25 | getChildContext() { 26 | const {xScale, yScale, data} = this.props; 27 | return {xScale, yScale, data}; 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | {this.props.children} 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Group.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | 3 | export default class Group extends Component { 4 | 5 | static displayName = 'Group' 6 | 7 | static contextTypes = { 8 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, 9 | height: PropTypes.number.isRequired, 10 | width: PropTypes.number.isRequired, 11 | margin: PropTypes.shape({ 12 | top: PropTypes.number, 13 | bottom: PropTypes.number, 14 | left: PropTypes.number, 15 | right: PropTypes.number 16 | }), 17 | xScale: PropTypes.func.isRequired, 18 | yScale: PropTypes.func.isRequired 19 | } 20 | 21 | static childContextTypes = { 22 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), 23 | height: PropTypes.number, 24 | width: PropTypes.number, 25 | margin: PropTypes.shape({ 26 | top: PropTypes.number, 27 | bottom: PropTypes.number, 28 | left: PropTypes.number, 29 | right: PropTypes.number 30 | }), 31 | xScale: PropTypes.func.isRequired, 32 | yScale: PropTypes.func.isRequired, 33 | dataTransform: PropTypes.func, 34 | groupWidth: PropTypes.number 35 | } 36 | 37 | getChildContext() { 38 | 39 | const {data} = this.context; 40 | 41 | return { 42 | ...this.context, 43 | groupWidth: data[0].length, 44 | dataTransform: this.dataTransform 45 | }; 46 | } 47 | 48 | dataTransform(arr2d) { 49 | 50 | const txData = []; 51 | 52 | arr2d.forEach((samples, i) => { 53 | txData.push(samples.map((sample, j) => ({ 54 | x: i, 55 | dx: j, 56 | y: sample, 57 | z: i, 58 | y0: 0, 59 | index: j 60 | }))); 61 | }); 62 | 63 | return Array.prototype.concat.apply([], txData); 64 | } 65 | 66 | render() { 67 | return ( 68 | 69 | {this.props.children} 70 | 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Pie.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import d3 from 'd3'; 3 | import {TransitionMotion, Motion, spring} from 'react-motion'; 4 | 5 | export default class Pie extends Component { 6 | 7 | static displayName = 'Pie' 8 | 9 | static propTypes = { 10 | innerRadius: PropTypes.number, 11 | outerRadius: PropTypes.number, 12 | style: PropTypes.func, 13 | onClick: PropTypes.func 14 | } 15 | 16 | static contextTypes = { 17 | data: PropTypes.arrayOf(PropTypes.number), 18 | height: PropTypes.number, 19 | width: PropTypes.number, 20 | margin: PropTypes.shape({ 21 | top: PropTypes.number, 22 | bottom: PropTypes.number, 23 | left: PropTypes.number, 24 | right: PropTypes.number 25 | }) 26 | } 27 | 28 | static defaultProps = { 29 | style: () => ({}), 30 | onClick: () => {} 31 | } 32 | 33 | willLeave({style}) { 34 | return { 35 | ...style, 36 | startAngle: style.endAngle 37 | }; 38 | } 39 | 40 | willEnter({style}) { 41 | return { 42 | ...style, 43 | endAngle: style.startAngle 44 | }; 45 | } 46 | 47 | render() { 48 | const {width, height, data} = this.context; 49 | const pieFn = d3.layout.pie().sort(null); 50 | const {style, onClick} = this.props; 51 | 52 | const arcFn = d3.svg.arc(). 53 | outerRadius(this.props.outerRadius). 54 | innerRadius(this.props.innerRadius); 55 | 56 | const pieData = pieFn(data); 57 | const motionStyles = pieData.map((d, i) => ({ 58 | key: i + '', 59 | data: {...d, index: i}, 60 | style: d 61 | })); 62 | 63 | const defaultStyles = pieData.map((d, i) => ({ 64 | key: i + '', 65 | data: {...d, index: i}, 66 | style: {...d, endAngle: d.startAngle} 67 | })); 68 | 69 | const centerTransform = `translate(${width / 2}, ${height / 2})`; 70 | 71 | 72 | return ( 73 | 74 | 76 | {interStyles => ( 77 | 78 | {interStyles.map(config => ( 79 | 89 | {interStyle => 90 | { 96 | onClick( 97 | e, config.data.value, 98 | config.data.index 99 | ); 100 | } 101 | }/> 102 | } 103 | 104 | ))} 105 | 106 | )} 107 | 108 | {this.props.children} 109 | 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Stack.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import d3 from 'd3'; 3 | 4 | export default class Stack extends Component { 5 | static contextTypes = { 6 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, 7 | height: PropTypes.number.isRequired, 8 | width: PropTypes.number.isRequired, 9 | margin: PropTypes.shape({ 10 | top: PropTypes.number, 11 | bottom: PropTypes.number, 12 | left: PropTypes.number, 13 | right: PropTypes.number 14 | }), 15 | xScale: PropTypes.func.isRequired, 16 | yScale: PropTypes.func.isRequired 17 | } 18 | 19 | static childContextTypes = { 20 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), 21 | height: PropTypes.number, 22 | width: PropTypes.number, 23 | margin: PropTypes.shape({ 24 | top: PropTypes.number, 25 | bottom: PropTypes.number, 26 | left: PropTypes.number, 27 | right: PropTypes.number 28 | }), 29 | xScale: PropTypes.func.isRequired, 30 | yScale: PropTypes.func.isRequired, 31 | dataTransform: PropTypes.func 32 | } 33 | 34 | getChildContext() { 35 | 36 | return { 37 | ...this.context, 38 | dataTransform: this.dataTransform 39 | }; 40 | } 41 | 42 | dataTransform(arr2d) { 43 | 44 | const txData = []; 45 | 46 | arr2d[0].forEach(() => { 47 | txData.push([]); 48 | }); 49 | 50 | arr2d.forEach((samples, i) => { 51 | samples.forEach((s, j) => { 52 | txData[j].push({ 53 | x: i, 54 | y: s, 55 | z: i, 56 | y0: 0, 57 | dx: 0, 58 | index: j 59 | }); 60 | }); 61 | }); 62 | 63 | return Array.prototype.concat.apply([], d3.layout.stack()(txData)); 64 | } 65 | 66 | render() { 67 | return ( 68 | 69 | {this.props.children} 70 | 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/XAxis.jsx: -------------------------------------------------------------------------------- 1 | import React, {PropTypes, Component} from 'react'; 2 | import {Axis as Styles} from './styles'; 3 | import d3 from 'd3'; 4 | import {scaleRange} from './helpers/scales'; 5 | import {getTicks} from './helpers/axes'; 6 | import {TransitionMotion, Motion, spring} from 'react-motion'; 7 | 8 | export default class XAxis extends Component { 9 | 10 | static displayName = 'XAxis' 11 | 12 | static contextTypes = { 13 | xScale: PropTypes.func.isRequired, 14 | dataTransform: PropTypes.func, 15 | data: PropTypes.oneOfType([ 16 | PropTypes.arrayOf(PropTypes.number), 17 | PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)) 18 | ]), 19 | height: PropTypes.number, 20 | width: PropTypes.number, 21 | margin: PropTypes.shape({ 22 | top: PropTypes.number, 23 | bottom: PropTypes.number, 24 | left: PropTypes.number, 25 | right: PropTypes.number 26 | }) 27 | } 28 | 29 | static propTypes = { 30 | 31 | labels: PropTypes.func, 32 | orientation: PropTypes.oneOf(['top', 'bottom']), 33 | tickFormat: PropTypes.func, 34 | outerTickSize: PropTypes.number, 35 | innerTickSize: PropTypes.number, 36 | tickPadding: PropTypes.number, 37 | tickValues: PropTypes.arrayOf(PropTypes.any), 38 | ticks: PropTypes.arrayOf(PropTypes.any), 39 | tickTextStyle: PropTypes.object, 40 | tickLineStyle: PropTypes.object, 41 | textRotation: PropTypes.number, 42 | debug: PropTypes.bool 43 | } 44 | 45 | static defaultProps = { 46 | tickFormat: v => v, 47 | labels: v => (v), 48 | orientation: 'bottom', 49 | outerTickSize: 6, 50 | innerTickSize: 6, 51 | tickPadding: 3, 52 | tickValues: null, 53 | ticks: [10], 54 | tickTextStyle: {}, 55 | tickLineStyle: {}, 56 | textRotation: 0 57 | } 58 | 59 | constructor(props) { 60 | super(props); 61 | this.state = {}; 62 | } 63 | 64 | willEnter({style}) { 65 | return { 66 | ...style, 67 | opacity: 0 68 | }; 69 | } 70 | 71 | willLeave({style}) { 72 | return { 73 | ...style, 74 | opacity: 0 75 | }; 76 | } 77 | 78 | render() { 79 | 80 | const { 81 | orientation, 82 | outerTickSize, 83 | innerTickSize, 84 | tickValues, 85 | tickPadding, 86 | ticks, 87 | tickFormat, 88 | tickTextStyle, 89 | tickLineStyle, 90 | textRotation, 91 | /*eslint-disable no-unused-vars*/ 92 | debug 93 | /*eslint-enable unused*/ 94 | } = this.props; 95 | 96 | const {xScale, data, width, height, margin} = this.context; 97 | 98 | const tickSpacing = Math.max(innerTickSize, 0) + tickPadding; 99 | const wMax = width - margin.left - margin.right; 100 | const hMax = height - margin.top - margin.bottom; 101 | 102 | const scale = xScale.domain(d3.range(data.length)).copy(); 103 | 104 | const ticksArr = getTicks(scale, tickValues, ticks); 105 | 106 | 107 | const displacement = orientation === 'bottom' ? 108 | `translate(0, ${hMax})` : 'translate(0, 0)'; 109 | 110 | const sign = orientation === 'top' ? -1 : 1; 111 | 112 | const range = scaleRange(scale); 113 | const pathD = `M${range[0]},${sign * outerTickSize}V0H${range[1]}V${sign * outerTickSize}`; 114 | 115 | let dx = 0; 116 | if (scale.rangeBand) { 117 | dx = scale.rangeBand() / 2; 118 | } 119 | 120 | 121 | const defaultStyles = ticksArr.map((t, i) => ({ 122 | key: i + '', 123 | data: { 124 | text: tickFormat(t, i) 125 | }, 126 | style: { 127 | tx: scale(t) + dx, 128 | ty: 0, 129 | textRotation, 130 | opacity: 1 131 | } 132 | })); 133 | 134 | const motionStyles = ticksArr.map((t, i) => ({ 135 | key: i + '', 136 | data: { 137 | text: tickFormat(t, i) 138 | }, 139 | style: { 140 | tx: scale(t) + dx, 141 | ty: 0, 142 | textRotation, 143 | opacity: 1 144 | } 145 | })); 146 | 147 | 148 | return ( 149 | 150 | 152 | {interStyles => ( 153 | 154 | {interStyles.map(config => ( 155 | 167 | {interStyle => { 168 | return ( 169 | 176 | 181 | 194 | {config.data.text} 195 | 196 | 197 | ); 198 | } 199 | } 200 | 201 | ))} 202 | 203 | )} 204 | 205 | 206 | 207 | ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/YAxis.jsx: -------------------------------------------------------------------------------- 1 | import React, {PropTypes, Component} from 'react'; 2 | import {Axis as Styles} from './styles'; 3 | import d3 from 'd3'; 4 | import {scaleRange} from './helpers/scales'; 5 | import {getTicks} from './helpers/axes'; 6 | import {TransitionMotion, Motion, spring} from 'react-motion'; 7 | 8 | export default class YAxis extends Component { 9 | 10 | static displayName = 'YAxis' 11 | 12 | static contextTypes = { 13 | yScale: PropTypes.func.isRequired, 14 | dataTransform: PropTypes.func, 15 | data: PropTypes.oneOfType([ 16 | PropTypes.arrayOf(PropTypes.number), 17 | PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)) 18 | ]), 19 | height: PropTypes.number, 20 | width: PropTypes.number, 21 | margin: PropTypes.shape({ 22 | top: PropTypes.number, 23 | bottom: PropTypes.number, 24 | left: PropTypes.number, 25 | right: PropTypes.number 26 | }) 27 | } 28 | 29 | static propTypes = { 30 | 31 | labels: PropTypes.func, 32 | orientation: PropTypes.oneOf(['left', 'right']), 33 | tickFormat: PropTypes.func, 34 | outerTickSize: PropTypes.number, 35 | innerTickSize: PropTypes.number, 36 | tickPadding: PropTypes.number, 37 | tickValues: PropTypes.arrayOf(PropTypes.any), 38 | ticks: PropTypes.arrayOf(PropTypes.any), 39 | tickTextStyle: PropTypes.object, 40 | tickLineStyle: PropTypes.object, 41 | textRotation: PropTypes.number, 42 | debug: PropTypes.bool 43 | } 44 | 45 | static defaultProps = { 46 | tickFormat: v => v, 47 | labels: v => (v), 48 | orientation: 'left', 49 | outerTickSize: 6, 50 | innerTickSize: 6, 51 | tickPadding: 3, 52 | tickValues: null, 53 | ticks: [10], 54 | tickTextStyle: {}, 55 | tickLineStyle: {}, 56 | textRotation: 0 57 | } 58 | 59 | constructor(props) { 60 | super(props); 61 | this.state = {}; 62 | } 63 | 64 | willEnter({style}) { 65 | return { 66 | ...style, 67 | opacity: 0 68 | }; 69 | } 70 | 71 | willLeave({style}) { 72 | return { 73 | ...style, 74 | opacity: 0 75 | }; 76 | } 77 | 78 | render() { 79 | 80 | const { 81 | orientation, 82 | outerTickSize, 83 | innerTickSize, 84 | tickValues, 85 | tickPadding, 86 | ticks, 87 | tickFormat, 88 | tickTextStyle, 89 | tickLineStyle, 90 | textRotation, 91 | /*eslint-disable no-unused-vars*/ 92 | debug 93 | /*eslint-enable unused*/ 94 | } = this.props; 95 | 96 | const { 97 | yScale, data, width, height, margin, 98 | dataTransform = arr => arr.map((d, i) => ({ 99 | x: i, 100 | y0: 0, 101 | y: d, 102 | z: 0 103 | })) 104 | } = this.context; 105 | 106 | const txData = dataTransform(data); 107 | const tickSpacing = Math.max(innerTickSize, 0) + tickPadding; 108 | const wMax = width - margin.left - margin.right; 109 | const hMax = height - margin.top - margin.bottom; 110 | 111 | const scale = yScale.domain([0, d3.max(txData, d => d.y0 + d.y)]).copy(); 112 | 113 | const ticksArr = getTicks(scale, tickValues, ticks); 114 | 115 | 116 | const displacement = orientation === 'right' ? 117 | `translate(${wMax},0)` : 'translate(0, 0)'; 118 | 119 | const sign = orientation === 'left' ? -1 : 1; 120 | 121 | const range = scaleRange(scale); 122 | const pathD = `M${sign * outerTickSize},${range[0]}H0V${range[1]}H${sign * outerTickSize}`; 123 | 124 | let dx = 0; 125 | if (scale.rangeBand) { 126 | dx = scale.rangeBand() / 2; 127 | } 128 | 129 | 130 | const defaultStyles = ticksArr.map((t, i) => ({ 131 | key: i + '', 132 | data: { 133 | text: tickFormat(t, i) 134 | }, 135 | style: { 136 | ty: scale(t) + dx, 137 | tx: 0, 138 | textRotation, 139 | opacity: 1 140 | } 141 | })); 142 | 143 | const motionStyles = ticksArr.map((t, i) => ({ 144 | key: i + '', 145 | data: { 146 | text: tickFormat(t, i) 147 | }, 148 | style: { 149 | ty: scale(t) + dx, 150 | tx: 0, 151 | textRotation, 152 | opacity: 1 153 | } 154 | })); 155 | 156 | 157 | return ( 158 | 159 | 161 | {interStyles => ( 162 | 163 | {interStyles.map(config => ( 164 | 176 | {interStyle => { 177 | return ( 178 | 185 | 190 | 202 | {config.data.text} 203 | 204 | 205 | ); 206 | } 207 | } 208 | 209 | ))} 210 | 211 | )} 212 | 213 | 214 | 215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const TRANSITION_DURATION = 800; 2 | -------------------------------------------------------------------------------- /src/helpers/axes.js: -------------------------------------------------------------------------------- 1 | export function getTicks(scale, tickValues, ticks) { 2 | if (tickValues === null) { 3 | return (scale.ticks ? scale.ticks(...ticks) : scale.domain()); 4 | } else { 5 | return tickValues; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/helpers/scales.js: -------------------------------------------------------------------------------- 1 | 2 | export function scaleExtent(domain) { 3 | var start = domain[0], stop = domain[domain.length - 1]; 4 | return start < stop ? [start, stop] : [stop, start]; 5 | } 6 | 7 | export function scaleRange(scale) { 8 | return scale.rangeExtent ? scale.rangeExtent() : scaleExtent(scale.range()); 9 | } 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Chart from './Chart'; 2 | import DataSeries from './DataSeries'; 3 | import Pie from './Pie'; 4 | import BarChart from './BarChart'; 5 | import Stack from './Stack'; 6 | import Group from './Group'; 7 | import XAxis from './XAxis'; 8 | import YAxis from './YAxis'; 9 | import util from './util'; 10 | 11 | module.exports = { 12 | Chart, 13 | DataSeries, 14 | Pie, 15 | BarChart, 16 | Stack, 17 | Group, 18 | XAxis, 19 | YAxis, 20 | util 21 | }; 22 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | import * as Colors from './Colors'; 2 | 3 | export const Axis = { 4 | text: { 5 | fontSize: '0.8em' 6 | }, 7 | paths: { 8 | fill: 'none', 9 | stroke: Colors.AXIS_LINES, 10 | shapeRendering: 'crispEdges' 11 | }, 12 | lines: { 13 | fill: 'none', 14 | stroke: Colors.AXIS_LINES, 15 | shapeRendering: 'crispEdges' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | import d3 from 'd3'; 2 | 3 | const {scale} = d3; 4 | 5 | export default {scale}; 6 | --------------------------------------------------------------------------------