├── .babelrc ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── apps ├── CoreComponent │ ├── d3component.js │ ├── d3container.js │ └── wrapper.js ├── actions │ └── ChartActions.js ├── charts │ ├── barChart.js │ ├── genericChart.js │ ├── lineChart.js │ └── pieChart.js ├── constants │ └── ActionTypes.js ├── main.js └── reducers │ ├── chartReducer.js │ └── index.js ├── dist └── d3-react-squared.js ├── example ├── PlainComponent.js ├── WrappedComponent.js ├── example.js ├── index.html └── main.js ├── img ├── dr2overview.png ├── explPieBar.png ├── explPieBar2.png ├── explPieBarLine.png └── explPlayground2.png ├── package.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "indent": [0], 5 | "id-length": [0], 6 | "no-underscore-dangle": [0], 7 | "no-mixed-operators": [0], 8 | "import/prefer-default-export": [0], 9 | "react/no-unused-prop-types": [0], 10 | "react/jsx-filename-extension": [0], 11 | "react/forbid-prop-types": [0], 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # -------------------- 2 | # OSX Files 3 | # -------------------- 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | Icon 8 | ._* 9 | .Spotlight-V100 10 | .Trashes 11 | 12 | # -------------------- 13 | # IntelliJ Files 14 | # -------------------- 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # -------------------- 21 | # Netbeans Files 22 | # -------------------- 23 | nbproject/private/ 24 | #build/ 25 | nbbuild/ 26 | #dist/ 27 | nbdist/ 28 | nbactions.xml 29 | nb-configuration.xml 30 | 31 | # -------------------- 32 | # Node Files 33 | # -------------------- 34 | .nodemonignore 35 | npm-debug.log 36 | node_modules/ 37 | 38 | # -------------------- 39 | # SASS Files 40 | # -------------------- 41 | .sass-cache/ 42 | 43 | # -------------------- 44 | # Bower Files 45 | # -------------------- 46 | .bower-*/ 47 | bower_components 48 | 49 | # -------------------- 50 | # Build Files 51 | # -------------------- 52 | build/* 53 | !build/index.html 54 | !build/img 55 | 56 | # -------------------- 57 | # Public Files 58 | # -------------------- 59 | 60 | # -------------------- 61 | # Other 62 | # -------------------- 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version Updates: 2 | 0.5.0 3 | Update other dependencies to current versions (EXCEPT for d3 (still using v3)) 4 | 5 | 0.4.0 6 | - Update dependencies to React 15 (based on PR) 7 | 8 | 0.3.6: 9 | - Remove c3 from this library and create [d3-react-squared-c3-loader](https://github.com/bgrsquared/d3-react-squared-c3-loader) 10 | 11 | 0.3.4 & 0.3.5: 12 | - Bug fixing. 13 | 14 | 0.3.3: 15 | - Fix event system (events blocked first update after event) 16 | 17 | 0.3.2: 18 | - While we're at it, externalize c3 too. 19 | 20 | 0.3.1: 21 | - Fix webpack externals (react-dom) to avoid "...it probably means that 22 | you've loaded two copies of React..." 23 | 24 | 0.3.0: 25 | - Update react to 0.14 and redux to v3.0 (and update other dependencies) 26 | - Linting (airbnb style). 27 | 28 | 0.2.7: 29 | - Add c3-wrapper, so that you can add [c3](http://c3js.org) charts to the mix. 30 | 31 | 0.2.6: 32 | - Update documentation. 33 | - Fix a paddingBottom issue. 34 | 35 | 0.2.5: 36 | - Add wrapper functionality (so far undocumented on dr2 page). Core idea: 37 | Use the Chart Component to wrap a component that is passed to it to enable access to chart-related redux functionality. 38 | (listen to events or even init events). 39 | 40 | 0.2.4: 41 | - We replaced Reflux and are using redux now. We implemented it so that you shouldn't notice a thing (breaking changes might occur later, as we have added possible functionality) 42 | 43 | 0.2.3: 44 | - Precompiled (babel) single library as direct entry point, to make stuff easier for direct users. 45 | 46 | 0.2.0 - 0.2.2 (including some betas): 47 | - Experiments with npm packages. 48 | 49 | 0.1.9: 50 | - Update dependencies 51 | - Linting. 52 | 53 | 0.1.8: 54 | - Adjust margins in bar- and line-charts. 55 | 56 | 0.1.7: 57 | - Add 'aspectRatio' and 'labelSize' to barChart 58 | - Change basic size of pie- and barCharts to 1000 (can be overridden in params) 59 | 60 | 0.1.6: 61 | - Add 'line chart' with some new features (these will be added to other charts later, where appilcable), such as: 62 | - Parametrized **aspect ratio** (make sure you also adjust paddingBottom!) This will be improved in future versions 63 | (We are thinking of automated paddingBottom ratios, unless overridden) 64 | - Parametrized **label size** 65 | - Parametrized **size** (currently not really useful (due to automated viewbox), unless you want to be pixel perfect) 66 | - Parametrized **line thickness** 67 | - 'Automatic' adjustment of margins (currently based on labelSize, not on actual tick label) 68 | - For fun: parametrized (and sort-of-animated) **interpolation modes** for paths (based on d3) 69 | - Some other, see documentation (when completed, until then: source) 70 | - And: the line chart uses same highlight-sharing-system and tooltips as in the other examplary charts. 71 | - Also added two exemplary line charts in ./example/example.js 72 | 73 | 0.1.5: 74 | - Add 'bubble up' of chart events. I.e. when a calling component passes `onChartEvent` property, 75 | it will be passed the the respective data object plus the event's label 76 | (will add documentation to this later). 77 | 78 | 0.1.4: 79 | - Add highlightEmit and highlightListen props. This allows you to specifiy which 80 | events (which groups) a chart should listen to (and emit to). 81 | 82 | 0.1.3: 83 | - Add dynamic paddingBottom (useful in custom charts that have a dynamic aspect ratio depending on data) 84 | (let me know if you require an example) 85 | 86 | 0.1.2: 87 | - Replace `mouseout` by `mouseleave` (especially important in pie chart) 88 | - Fix minor `attrTween` bug in pie chart 89 | 90 | 0.1.1: 91 | - add eslint and clean up code (lint and other stuff) 92 | - add propTypes. 93 | 94 | 0.1.0: 95 | - added standalone example 96 | 97 | 0.0.8: 98 | - Add destroyFunction to charts (essentially to get rid of tooltips on unload) 99 | - Added playground to docu! [--> See here](http://bgrsquared.com/DR2/) 100 | 101 | 0.0.7: 102 | - Add tooltips to bar and pie charts 103 | - Improve highlighting styles 104 | 105 | 0.0.6: 106 | - add cross-highlight capabilities (based on id of element) 107 | (use of Reflux motivated by [@pbeshai 's linked-highlighting-react-d3-reflux](https://github.com/pbeshai/linked-highlighting-react-d3-reflux) - 108 | big thanks!) 109 | 110 | 0.0.5: 111 | - add more parameters to pieChart and barChart (colors etc.) 112 | 113 | 0.0.4: 114 | - update charts to use ES6 modules and use Object.create() syntax to load (instead of `new`) 115 | - rename `chartGenerator` prop of custom charts to `chartModule` 116 | 117 | 0.0.3: 118 | - add custom chart loader function 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christian Roth / bgrsquared consulting AG 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 | [--> Demos, Examples, Playground, Docu](http://bgrsquared.com/DR2/) 2 | 3 | [--> d3-react-squared-c3-loader](https://github.com/bgrsquared/d3-react-squared-c3-loader) 4 | 5 | [--> New live example](http://bgrsquared.com/dogs/) 6 | 7 | [--> New blog post, based on live example](https://medium.com/@ilikepiecharts/about-using-d3-react-squared-an-example-8cc5e5a6b58e#.jso6use4q) 8 | 9 | ### Notes 10 | --> v0.6.0 and later require d3 v4! 11 | 12 | --> v0.3.0 and later require React 0.14! 13 | 14 | ### c3 15 | 16 | Documentation is still missing, sorry! 17 | 18 | #### v0.3.6 and newer 19 | Starting in 0.3.6, c3 charts are loaded using [d3-react-squared-c3-loader](https://github.com/bgrsquared/d3-react-squared-c3-loader) 20 | 21 | #### v0.2.7 through v0.3.5: 22 | Please note that this is still 'beta'. So far, there is no docu on the docu page. 23 | --> please check out the c3example.js in the source (./examples). 24 | 25 | # d3-react-squared 26 | [![npm version](https://badge.fury.io/js/d3-react-squared.png)](http://badge.fury.io/js/d3-react-squared) 27 | 28 | Feedback, ideas, PRs, etc. very welcome! 29 | 30 | ## Why yet another d3-react component? 31 | There are already some great solutions out there, combining React and D3, e.g.: 32 | 33 | [A gist with some links here](https://gist.github.com/chroth7/a56fafed1efc43737d11) 34 | 35 | Most of these articles/code aims to combine/add d3 into the lifecycle methods to 36 | generate charts that way. Have a look at them, great ideas there. 37 | 38 | See docu page for some details about my approach. I don't want to bore you with details here - 39 | just contact us (contacts on docu page). I am very happy to discuss ideas/concepts! 40 | 41 | Some keywords: 42 | - Use D3 charts 'directly', maybe very limited adjustments needed (just think [examples](https://github.com/mbostock/d3/wiki/Gallery)!) 43 | - Provide viewboxes etc. to get responsive graphs 44 | - Make chart modular (a.k.a. reusable) 45 | - Provide a clean API to create and update charts (from ANY component!). 46 | - Parametrize charts 47 | - Be lightweight 48 | - Provide a way to share events between charts (and using a wrapper: any component!) 49 | - Provide access to a charts library (we currently offer [c3js](http://c3js.org), as of v0.2.7) 50 | - Provide a limited set of examples in this repo and make it easy to the users to add their own custom charts 51 | 52 | We believe that especially the last bullet is helpful to teams separate concerns and have maintainable solutions. 53 | Why? The chart generating code is in its own module and the interaction designer doesn't really have to care about React (maybe he should, but that's another story...). 54 | 55 | Details? 56 | 57 | [See also here](http://bgrsquared.com/DR2/) 58 | (click on DR2 in top right navigation!) 59 | 60 | 61 | ## Documentation 62 | [--> See here](http://bgrsquared.com/DR2/) 63 | (click on DR2 in top right navigation!) 64 | 65 | The documentation is still somewhat basic. Definitely check out the examples in the repo! 66 | 67 | But hey, writing docu is sooooo time consuming... 68 | 69 | ## Stand-alone example 70 | This repo now includes a stand-alone example. Simply: 71 | 72 | ``` 73 | npm install 74 | ``` 75 | 76 | and then 77 | 78 | ``` 79 | npm run dev 80 | ``` 81 | 82 | and it should be running on `localhost:8080`. 83 | 84 | ### Requirements 85 | As far as I know, you shouldn't need anything fancy. 86 | 87 | We run it in a babel/webpack/react setup, plain vanilla, so to speak (plenty of setup guides out there), 88 | and it works. 89 | 90 | Also: we have bootstrap, no other css/sass/... (actually: we love [react-bootstrap](https://react-bootstrap.github.io)) 91 | (Note: you could, if you wanted, to use SASS to style your graphs, must require the files where and when needed; you know how.). 92 | 93 | # Thanks 94 | Huge thanks to all the people involved in providing awesome tools such as: 95 | * [ReactJS](https://facebook.github.io/react/) 96 | * [D3](http://d3js.org) 97 | * [webpack](http://webpack.github.io) 98 | * [BabelJS](https://babeljs.io) 99 | * [Reflux (no longer using it, thanks anyway!)](https://github.com/spoike/refluxjs) 100 | * [redux (replaces Reflux)](https://github.com/rackt/redux) 101 | * [c3.js](http://c3js.org) 102 | 103 | and many others... 104 | 105 | ### Some screenshots 106 | 107 | #### New Docu-Page 108 | ![DocuPage](https://github.com/bgrsquared/d3-react-squared/blob/master/img/dr2overview.png) 109 | 110 | #### Playground ([--> See here](http://bgrsquared.com/DR2/)) to learn about parameters: 111 | ![Playground](https://github.com/bgrsquared/d3-react-squared/blob/master/img/explPlayground2.png) 112 | -------------------------------------------------------------------------------- /apps/CoreComponent/d3component.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import * as d3 from 'd3'; 3 | 4 | // some examples 5 | import { barChart } from '../charts/barChart'; 6 | import { pieChart } from '../charts/pieChart'; 7 | import { lineChart } from '../charts/lineChart'; 8 | 9 | export default class D3Component extends Component { 10 | constructor() { 11 | super(); 12 | this.state = { 13 | chartObject: {}, 14 | lastEvent: 0, 15 | chartStyle: { // svg-container 16 | display: 'block', 17 | position: 'relative', 18 | width: '100%', 19 | paddingBottom: '50%', // adjust below for other aspect ratios! 20 | verticalAlign: 'middle', 21 | overflow: 'hidden', 22 | }, 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | // create new chart 28 | this.createNewChart.call(this, this.props.chartType, this.props); 29 | } 30 | 31 | componentWillReceiveProps(newProps) { 32 | const { chartObject, lastEvent } = this.state; 33 | const { chartType } = this.props; 34 | const { eventData } = newProps; 35 | 36 | // we check if we need to create a new chart or update the existing one 37 | if (!chartObject.mainFunction || 38 | newProps.chartType !== chartType) { 39 | this.createNewChart.call(this, newProps.chartType, newProps); 40 | } else if (lastEvent === 0 || eventData.timeStamp <= lastEvent) { 41 | chartObject.updateFunction(newProps.data, newProps.params); 42 | } 43 | 44 | // Redux Events 45 | if (eventData.timeStamp > lastEvent) { 46 | this.incomingEvent(eventData, ['default']); 47 | } 48 | 49 | // update timestamp 50 | this.setState({ lastEvent: eventData.timeStamp }); 51 | } 52 | 53 | shouldComponentUpdate(newProps) { 54 | return (newProps.eventData.timeStamp <= this.state.lastEvent); 55 | } 56 | 57 | componentWillUnmount() { 58 | const { chartObject } = this.state; 59 | if (chartObject.destroyFunction) { 60 | chartObject.destroyFunction(); 61 | } 62 | } 63 | 64 | incomingEvent(obj) { 65 | const { data, event, eventGroup } = obj; 66 | const { highlightListen, highlight } = this.props; 67 | const { chartObject } = this.state; 68 | // check if you have an overlap between highlightEmit and highlightListen 69 | const listenGroups = highlightListen; 70 | const intersection = eventGroup.filter(n => listenGroups.indexOf(n) !== -1); 71 | 72 | if (intersection.length && highlight && chartObject.onEvent) { 73 | chartObject.onEvent({ 74 | d: data, 75 | e: event, 76 | }); 77 | } 78 | } 79 | 80 | createNewChart(chartPrototype, props) { 81 | const { paddingBottom, setEvent } = this.props; 82 | 83 | // clean up existing stuff 84 | d3.select(this.node).select('#d3graphSVG').remove(); 85 | // Create afresh 86 | let chartObject; 87 | switch (chartPrototype) { 88 | case 'bar': 89 | chartObject = Object.create(barChart); 90 | break; 91 | case 'line': 92 | chartObject = Object.create(lineChart); 93 | break; 94 | case 'pie': 95 | chartObject = Object.create(pieChart); 96 | break; 97 | case 'custom': 98 | chartObject = Object.create(props.chartModule); 99 | break; 100 | default: 101 | chartObject = Object.create(barChart); 102 | } 103 | 104 | chartObject.setEvent = setEvent; 105 | this.setState({ 106 | chartObject, 107 | chartStyle: Object.assign({}, this.state.chartStyle, { paddingBottom }), 108 | }); 109 | 110 | // and create it: 111 | chartObject.mainFunction(d3.select(this.node), 112 | props.data, props.params, this); 113 | } 114 | 115 | handleChartEvent(d, event) { 116 | const { onChartEvent, highlightEmit } = this.props; 117 | // call action 118 | if (onChartEvent) { 119 | onChartEvent(d, event); 120 | } 121 | 122 | // redux 123 | const eventObj = { 124 | data: d, 125 | event, 126 | eventGroup: highlightEmit, 127 | }; 128 | this.props.setEvent(eventObj); 129 | } 130 | 131 | render() { 132 | const { paddingBottom } = this.props; 133 | let { chartStyle } = this.state; 134 | if (paddingBottom) { 135 | chartStyle = Object.assign({}, chartStyle, { paddingBottom }); 136 | } 137 | return (
(this.node = node)} 139 | style={chartStyle} 140 | />); 141 | } 142 | } 143 | 144 | D3Component.defaultProps = { 145 | params: {}, 146 | chartType: 'bar', 147 | paddingBottom: '100%', 148 | chartModule: barChart, 149 | data: [], 150 | highlight: true, 151 | highlightEmit: ['default'], 152 | highlightListen: ['default'], 153 | }; 154 | 155 | D3Component.propTypes = { 156 | chartModule: PropTypes.object, 157 | chartType: PropTypes.string, 158 | data: PropTypes.array, 159 | eventData: PropTypes.object.isRequired, 160 | highlight: PropTypes.bool, 161 | highlightEmit: PropTypes.array, 162 | highlightListen: PropTypes.array, 163 | onChartEvent: PropTypes.func, 164 | paddingBottom: PropTypes.string, 165 | params: PropTypes.object, 166 | setEvent: PropTypes.func.isRequired, 167 | }; 168 | -------------------------------------------------------------------------------- /apps/CoreComponent/d3container.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as ChartActions from '../actions/ChartActions'; 4 | 5 | import D3Component from './d3component'; 6 | 7 | function mapStateToProps(state) { 8 | return { 9 | eventData: state.d3ReactSquared, 10 | }; 11 | } 12 | 13 | function mapDispatchToProps(dispatch) { 14 | return bindActionCreators(ChartActions, dispatch); 15 | } 16 | 17 | export default connect(mapStateToProps, mapDispatchToProps)(D3Component); 18 | -------------------------------------------------------------------------------- /apps/CoreComponent/wrapper.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as ChartActions from '../actions/ChartActions'; 4 | 5 | function mapStateToProps(state) { 6 | return { 7 | eventData: state.d3ReactSquared, 8 | }; 9 | } 10 | 11 | function mapDispatchToProps(dispatch) { 12 | return bindActionCreators(ChartActions, dispatch); 13 | } 14 | 15 | export default function (component) { 16 | return connect(mapStateToProps, mapDispatchToProps)(component); 17 | } 18 | -------------------------------------------------------------------------------- /apps/actions/ChartActions.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | 3 | export function setEvent(event) { 4 | return { 5 | type: types.SET_EVENT, 6 | event, 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /apps/charts/barChart.js: -------------------------------------------------------------------------------- 1 | const d3 = require('d3'); 2 | 3 | export const barChart = { 4 | defaultParams: { 5 | size: 1000, // debug switch, for exact values 6 | aspectRatio: 1, 7 | labelSize: 1, 8 | col1: 'green', 9 | col2: 'red', 10 | defaultDuration: 500, 11 | rx: 5, 12 | ry: 5, 13 | yLabel: 'Value', 14 | colorType: 'gradient', 15 | colorArray: d3.schemeCategory20, 16 | tooltip: d => `
ID: ${d.id}
Value: ${d.value}
`, 17 | }, 18 | 19 | mainFunction(loc, data, params, reactComp) { 20 | const self = this; 21 | this.reactComp = reactComp; 22 | 23 | self.par = Object.assign({}, this.defaultParams, params); 24 | 25 | this.size = this.par.size; 26 | const labelSize = this.par.labelSize; 27 | this.fontSize = labelSize * this.size / 100; 28 | 29 | this.margin = { 30 | top: this.size / 100, 31 | right: this.size / 50, 32 | bottom: this.fontSize + this.size / 100, 33 | left: (1 + labelSize / 10) * 40, 34 | }; 35 | this.width = this.size - this.margin.left - this.margin.right; 36 | this.height = this.size * this.par.aspectRatio - 37 | this.margin.top - this.margin.bottom; 38 | this.fullWidth = this.size; 39 | this.fullHeight = this.size * this.par.aspectRatio; 40 | 41 | this.x = d3.scaleBand() 42 | .rangeRound([0, this.width], 0.1); 43 | 44 | this.y = d3.scaleLinear() 45 | .range([this.height, 0]); 46 | 47 | this.xAxis = d3.axisBottom(this.x) 48 | .tickSizeInner([this.size / 250]) 49 | .tickSizeOuter([this.size / 250]) 50 | .tickPadding([this.size / 250]); 51 | 52 | this.yAxis = d3.axisLeft(this.y) 53 | .tickSizeInner([this.size / 250]) 54 | .tickSizeOuter([this.size / 250]) 55 | .tickPadding([this.size / 250]) 56 | .tickFormat(d3.format('.2s')); 57 | 58 | this.svg = loc.append('svg') 59 | .attr('id', 'd3graphSVG') 60 | .style('display', 'inline-block') 61 | .style('position', 'absolute') 62 | .attr('preserveAspectRatio', 'xMinYMin slice') 63 | .attr('viewBox', `0 0 ${this.fullWidth} ${this.fullHeight}`) 64 | .append('g') 65 | .attr('transform', `translate(${this.margin.left},${this.margin.top})`); 66 | 67 | 68 | this.x.domain(data.map(d => d.id)); 69 | this.yMax = d3.max(data, d => d.value) || 100; 70 | this.y.domain([0, this.yMax]); 71 | 72 | 73 | this.xAx = this.svg.append('g') 74 | .attr('class', 'x axis') 75 | .style('stroke-width', `${(this.par.size / 1000)}px`) 76 | .style('font-size', `${this.fontSize}px`) 77 | .style('font-family', 'sans-serif') 78 | .attr('transform', `translate(0,${this.height})`) 79 | .call(this.xAxis); 80 | 81 | this.yAx = this.svg.append('g') 82 | .attr('class', 'y axis') 83 | .style('stroke-width', `${(this.par.size / 1000)}px`) 84 | .style('font-size', `${this.fontSize}px`) 85 | .style('font-family', 'sans-serif') 86 | .call(this.yAxis); 87 | 88 | this.yAx 89 | .append('text') 90 | .attr('transform', 'rotate(-90)') 91 | .attr('y', this.fontSize / 2) 92 | .attr('dy', '.71em') 93 | .style('text-anchor', 'end') 94 | .text(self.par.yLabel); 95 | 96 | this.tooltip = d3.select('body') 97 | .append('div') 98 | .style('background', 'rgba(238, 238, 238, 0.85)') 99 | .style('padding', '5px') 100 | .style('border-radius', '5px') 101 | .style('border-color', '#999') 102 | .style('border-width', '2px') 103 | .style('border-style', 'solid') 104 | .style('pointer-events', 'none') 105 | .style('position', 'absolute') 106 | .style('z-index', '10') 107 | .style('opacity', 0); 108 | 109 | this.updateFunction(data, params); 110 | }, 111 | 112 | destroyFunction() { 113 | this.tooltip.remove(); 114 | }, 115 | 116 | updateFunction(data, params) { 117 | const self = this; 118 | self.par = Object.assign({}, this.defaultParams, params); 119 | 120 | self.colFunc = this.colorFunction(self.par); 121 | 122 | this.x.domain(data.map(d => d.id)); 123 | this.yMax = d3.max(data, d => d.value) || 100; 124 | this.y.domain([0, this.yMax]); 125 | 126 | this.yAx 127 | .transition() 128 | .duration(self.par.defaultDuration) 129 | .call(this.yAxis); 130 | 131 | this.xAx 132 | .transition() 133 | .duration(self.par.defaultDuration) 134 | .call(this.xAxis); 135 | 136 | this.svg.selectAll('.axis line') 137 | .style('fill', 'none') 138 | .style('stroke', '#000') 139 | .style('shape-rendering', 'crispEdges'); 140 | 141 | this.svg.selectAll('.axis path') 142 | .style('fill', 'none') 143 | .style('stroke', '#000') 144 | .style('shape-rendering', 'crispEdges'); 145 | 146 | this.join = this.svg.selectAll('.bar') 147 | .data(data, d => d.id); 148 | 149 | this.join 150 | .transition() 151 | .duration(self.par.defaultDuration) 152 | .attr('y', d => self.y(d.value)) 153 | .attr('height', d => self.height - self.y(d.value)) 154 | .attr('width', self.x.bandwidth()) 155 | .attr('x', d => self.x(d.id)) 156 | .style('fill', (d, i) => self.colFunc(d, i)); 157 | 158 | // ENTER 159 | this.join.enter().append('rect') 160 | .attr('width', 0) 161 | .attr('height', 0) 162 | .attr('y', 0) 163 | .attr('rx', self.par.rx) 164 | .attr('ry', self.par.ry) 165 | .style('stroke', 'transparent') 166 | .style('stroke-width', '2px') 167 | .on('mouseover', (d) => { 168 | self.mouseoverBar.call(self, d, this); 169 | }) 170 | .on('mouseout', (d) => { 171 | self.mouseoutBar.call(self, d, this); 172 | }) 173 | .on('mousemove', (d) => { 174 | self.mousemoveBar.call(self, d, this); 175 | }) 176 | .transition() 177 | .duration(self.par.defaultDuration) 178 | .attr('class', 'bar bar') 179 | .attr('id', d => `bar-${d.id}`) 180 | .attr('x', d => self.x(d.id)) 181 | .attr('width', self.x.bandwidth()) 182 | .attr('y', d => self.y(d.value)) 183 | .attr('height', d => self.height - self.y(d.value)) 184 | .style('fill', (d, i) => self.colFunc(d, i)); 185 | 186 | // EXIT 187 | this.join.exit() 188 | .transition() 189 | .duration(self.par.defaultDuration) 190 | .attr('width', 0) 191 | .attr('height', 0) 192 | .remove(); 193 | }, 194 | 195 | onEvent(obj) { 196 | const self = this; 197 | const { d, e } = obj; 198 | switch (e) { 199 | case 'mouseover': 200 | this.svg.selectAll('.bar') 201 | .style('fill-opacity', 0.15) 202 | .style('stroke-opacity', 0) 203 | .style('stroke-width', '5px') 204 | .style('stroke', (dd, i) => self.colFunc(dd, i)); 205 | this.svg.selectAll(`#bar-${d.id}`) 206 | .style('fill-opacity', 0.5) 207 | .style('stroke-opacity', 1); 208 | break; 209 | case 'mouseout': 210 | this.svg.selectAll('.bar') 211 | .style('fill-opacity', 1) 212 | .style('stroke', 'transparent') 213 | .style('stroke-width', '2px') 214 | .style('stroke-opacity', 1); 215 | break; 216 | default: 217 | } 218 | }, 219 | 220 | colorFunction(par) { 221 | const self = this; 222 | if (par.colorType === 'gradient') { 223 | return d => d3.interpolateHsl(par.col1, par.col2)(d.value / self.yMax); 224 | } else if (par.colorType === 'category') { 225 | const cols = par.colorArray; 226 | return (d, i) => cols[i]; 227 | } 228 | return () => 'gray'; 229 | }, 230 | 231 | mouseoverBar(d) { 232 | // pass the event to the partent component 233 | this.reactComp.handleChartEvent(d, 'mouseover'); 234 | 235 | // show tooltip 236 | this.tooltip.html(this.par.tooltip(d)) 237 | .style('opacity', 1) 238 | .style('top', `${(d3.event.pageY - 10)}px`) 239 | .style('left', `${(d3.event.pageX + 10)}px`); 240 | }, 241 | 242 | mouseoutBar(d) { 243 | // pass the event to the partent component 244 | this.reactComp.handleChartEvent(d, 'mouseout'); 245 | 246 | // hide tooltip 247 | this.tooltip.style('opacity', 0); 248 | }, 249 | 250 | mousemoveBar() { 251 | // note: we do not pass that event to parent component 252 | 253 | // move tooltip 254 | this.tooltip 255 | .style('top', `${(d3.event.pageY)}px`) 256 | .style('left', `${(d3.event.pageX + 10)}px`); 257 | }, 258 | }; 259 | -------------------------------------------------------------------------------- /apps/charts/genericChart.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let d3 = require('d3'); 4 | 5 | export let genericChart = { 6 | //default parameters (mandatory) 7 | defaultParams: { 8 | param1: 'value1', //will be used as default as not explicitly set 9 | param2: 'value2' 10 | }, 11 | 12 | //mainFunction (mandatory) will be called on chart creation 13 | mainFunction(loc, data, params, reactComp) { 14 | let self = this; 15 | this.reactComp = reactComp; 16 | 17 | //set parameters (explicit overrides default) 18 | self.par = Object.assign({}, this.defaultParams, params); 19 | 20 | //size (absolute!) of the chart (will be scaled in viewbox! 21 | let size = 250; 22 | 23 | //some examplary size/width/etc. setup 24 | let width = size - 20, 25 | height = size - 20, 26 | radius = Math.min(width, height) / 2; 27 | let fullWidth = size; 28 | let fullHeight = size; 29 | 30 | //svg setup 31 | this.svg = loc.append('svg') 32 | .attr('id', 'd3graphSVG') 33 | .style('display', 'inline-block') 34 | .style('position', 'absolute') 35 | .attr('preserveAspectRatio', 'xMinYMin slice') 36 | .attr('viewBox', '0 0 ' + fullWidth + ' ' + fullHeight) 37 | .append('g') 38 | .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); 39 | 40 | //call updateFunction 41 | this.updateFunction(data, params); 42 | }, 43 | 44 | destroyFunction() { 45 | //do whatever necessary, such as 46 | //this.tooltip.remove(); 47 | //(because it is appended to body) 48 | }, 49 | 50 | // updateFunction routine (mandatory), will be called on new data 51 | // (and usually after mainFunction) 52 | updateFunction(data, params) { 53 | 54 | let self = this; 55 | self.par = Object.assign({}, this.defaultParams, params); 56 | 57 | // here is where you do some D3 magic... 58 | // (see the examples) (for code, not magic) 59 | // make sure to use D3's enter/update/exit pattern with proper joins, to 60 | // get the full power of data updates. 61 | d3.select('something'); 62 | 63 | // if you want to pass events to the parent reactComponent, you could do something like: 64 | // .on('mouseover', (d) => { 65 | // self.mouseoverBar.call(self, d, otherArguments) 66 | // }) 67 | }, 68 | 69 | onEvent(obj) { 70 | //this function is called when this or another component fires 71 | //an event (an action) to the redux store 72 | //d is the data object of the item that triggered the event 73 | //e is the event name 74 | let {d, e} = obj; 75 | let self = this; 76 | switch (e) { 77 | case 'mouseover': 78 | //do something... if you want 79 | break; 80 | case 'mouseleave': 81 | //do something else... if you want 82 | break; 83 | } 84 | }, 85 | 86 | genericEvent(d, otherArguments) { 87 | // do stuff that is for this chart only... 88 | // like tooltips... 89 | 90 | // send an event to the parent reactComp (to trigger actions and send to other charts) 91 | this.reactComp.handleChartEvent(d.data, 'eventName'); 92 | } 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /apps/charts/lineChart.js: -------------------------------------------------------------------------------- 1 | const d3 = require('d3'); 2 | 3 | export const lineChart = { 4 | defaultParams: { 5 | defaultDuration: 500, 6 | size: 1000, // debug switch, for exact values 7 | aspectRatio: 1 / 2, 8 | labelSize: 1, 9 | yLabel: 'Value', 10 | xLabel: 'Value', 11 | colorArray: d3.schemeCategory20, 12 | strokeWidth: 3, 13 | yAxisPlacement: 'left', 14 | xMax: -Infinity, 15 | yMax: -Infinity, 16 | xMin: Infinity, 17 | yMin: Infinity, 18 | interpolate: 'linear', 19 | tooltip: d => `
ID: ${d.id}
`, 20 | }, 21 | 22 | mainFunction(loc, data, params, reactComp) { 23 | const self = this; 24 | this.reactComp = reactComp; 25 | 26 | self.par = Object.assign({}, this.defaultParams, params); 27 | 28 | this.size = this.par.size; 29 | const labelSize = this.par.labelSize; 30 | this.fontSize = labelSize * this.size / 100; 31 | let lM = 1; 32 | let rM = 1; 33 | 34 | if (this.par.yAxisPlacement === 'left') { 35 | lM = (1 + labelSize / 2) * 5; 36 | } else { 37 | rM = (1 + labelSize / 2) * 5; 38 | } 39 | 40 | this.margin = { 41 | top: this.size / 20, 42 | right: rM * this.size / 100, 43 | bottom: this.fontSize + this.size / 20, 44 | left: lM * this.size / 100, 45 | }; 46 | this.width = this.size - this.margin.left - this.margin.right; 47 | this.height = this.size * this.par.aspectRatio - 48 | this.margin.top - this.margin.bottom; 49 | this.fullWidth = this.size; 50 | this.fullHeight = this.size * this.par.aspectRatio; 51 | 52 | this.x = d3.scaleLinear() 53 | .range([0, this.width]); 54 | 55 | this.y = d3.scaleLinear() 56 | .range([this.height, 0]); 57 | 58 | this.xAxis = d3.axisBottom(this.x) 59 | .tickSizeInner([this.size / 250]) 60 | .tickSizeOuter([this.size / 250]) 61 | .tickPadding([this.size / 250]); 62 | 63 | this.yAxis = d3.axisRight(this.y) 64 | .tickSizeInner([this.size / 250]) 65 | .tickSizeOuter([this.size / 250]) 66 | .tickPadding([this.size / 250]); 67 | // .orient(self.par.yAxisPlacement); 68 | 69 | this.vb = loc.append('svg') 70 | .attr('id', 'd3graphSVG') 71 | .style('display', 'inline-block') 72 | .style('position', 'absolute') 73 | .attr('preserveAspectRatio', 'xMinYMin slice') 74 | .attr('viewBox', `0 0 ${this.fullWidth} ${this.fullHeight}`); 75 | 76 | this.svg = this.vb.append('g') 77 | .attr('transform', 78 | `translate(${this.margin.left},${this.margin.top})`); 79 | 80 | this.tooltip = d3.select('body') 81 | .append('div') 82 | .style('background', 'rgba(238, 238, 238, 0.85)') 83 | .style('padding', '5px') 84 | .style('border-radius', '5px') 85 | .style('border-color', '#999') 86 | .style('border-width', '2px') 87 | .style('border-style', 'solid') 88 | .style('pointer-events', 'none') 89 | .style('position', 'absolute') 90 | .style('z-index', '10') 91 | .style('opacity', 0); 92 | 93 | this.xAx = this.svg.append('g') 94 | .attr('class', 'x axis') 95 | .style('stroke-width', `${(this.par.size / 1000)}px`) 96 | .style('font-size', `${this.fontSize}px`) 97 | .style('font-family', 'sans-serif') 98 | .attr('transform', `translate(0,${this.height})`) 99 | .call(this.xAxis); 100 | 101 | this.xAx.append('text') 102 | .attr('x', (self.par.yAxisPlacement === 'left' ? this.width : 0)) 103 | .attr('y', -this.fontSize / 2) 104 | .style('text-anchor', (self.par.yAxisPlacement === 'left' ? 105 | 'end' : 'start')) 106 | .text(self.par.xLabel); 107 | 108 | this.yAx = this.svg.append('g') 109 | .attr('class', 'y axis') 110 | .style('stroke-width', `${(this.par.size / 1000)}px`) 111 | .style('font-size', `${this.fontSize}px`) 112 | .style('font-family', 'sans-serif') 113 | .attr('transform', 114 | `translate(${((self.par.yAxisPlacement === 'left' ? 0 : 1) * this.width)}, 0)`) 115 | .call(this.yAxis); 116 | 117 | this.yAx 118 | .append('text') 119 | .attr('transform', 'rotate(-90)') 120 | .attr('y', (self.par.yAxisPlacement === 'left' ? 121 | this.fontSize / 2 : -this.fontSize)) 122 | .attr('dy', '.71em') 123 | .style('text-anchor', 'end') 124 | .text(self.par.yLabel); 125 | 126 | this.updateFunction(data, params); 127 | }, 128 | 129 | destroyFunction() { 130 | this.tooltip.remove(); 131 | }, 132 | 133 | updateFunction(data, params) { 134 | const self = this; 135 | self.par = Object.assign({}, this.defaultParams, params); 136 | 137 | let curveGen = d3.curveLinear(); 138 | switch (self.par.interpolate) { 139 | case 'linear-closed': { 140 | curveGen = d3.curveLinearClosed; 141 | break; 142 | } 143 | case 'step': { 144 | curveGen = d3.curveStep; 145 | break; 146 | } 147 | case 'step-after': { 148 | curveGen = d3.curveStepAfter; 149 | break; 150 | } 151 | case 'step-before': { 152 | curveGen = d3.curveStepBefore; 153 | break; 154 | } 155 | case 'basis': { 156 | curveGen = d3.curveBasis; 157 | break; 158 | } 159 | case 'basisClosed': { 160 | curveGen = d3.curveBasisClosed; 161 | break; 162 | } 163 | case 'bundle': { 164 | curveGen = d3.curveBundle; 165 | break; 166 | } 167 | case 'cardinal': { 168 | curveGen = d3.curveCardinal; 169 | break; 170 | } 171 | case 'cardinal-open': { 172 | curveGen = d3.curveCardinalOpen; 173 | break; 174 | } 175 | case 'cardinal-closed': { 176 | curveGen = d3.curveCardinalClosed; 177 | break; 178 | } 179 | case 'monotone': { 180 | curveGen = d3.curveMonotoneX; 181 | break; 182 | } 183 | default: 184 | curveGen = d3.curveLinear; 185 | } 186 | 187 | this.line = d3.line() 188 | .curve(curveGen) 189 | .x(d => this.x(d.x)) 190 | .y(d => this.y(d.y)); 191 | 192 | let { xMax, yMax, xMin, yMin } = self.par; 193 | data.forEach((line) => { 194 | line.values.forEach((val) => { 195 | xMax = Math.max(val.x, xMax); 196 | yMax = Math.max(val.y, yMax); 197 | xMin = Math.min(val.x, xMin); 198 | yMin = Math.min(val.y, yMin); 199 | }); 200 | }); 201 | 202 | if (xMax === -Infinity && xMin === Infinity) { 203 | xMax = xMin = 0; 204 | } 205 | if (yMax === -Infinity && yMin === Infinity) { 206 | yMax = yMin = 0; 207 | } 208 | this.x.domain([xMin, xMax]); 209 | this.y.domain([yMin, yMax]); 210 | 211 | this.xAx 212 | .transition() 213 | .duration(self.par.defaultDuration) 214 | .call(this.xAxis); 215 | 216 | this.yAx 217 | .transition() 218 | .duration(self.par.defaultDuration) 219 | .call(this.yAxis); 220 | 221 | this.svg.select('.y.axis') 222 | .transition() 223 | .duration(self.par.defaultDuration) 224 | .call(this.yAxis); 225 | 226 | this.svg.select('.x.axis') 227 | .transition() 228 | .duration(self.par.defaultDuration) 229 | .call(this.xAxis); 230 | 231 | this.svg.selectAll('.axis line') 232 | .style('fill', 'none') 233 | .style('stroke', '#000') 234 | .style('shape-rendering', 'crispEdges'); 235 | 236 | this.svg.selectAll('.axis path') 237 | .style('fill', 'none') 238 | .style('stroke', '#000') 239 | .style('shape-rendering', 'crispEdges'); 240 | 241 | this.joinLine = this.svg.selectAll('.lineGroup') 242 | .data(data, d => d.id); 243 | 244 | // ENTER 245 | this.lineGroup = this.joinLine.enter().append('g') 246 | .attr('class', 'lineGroup'); 247 | 248 | this.joinLine.select('path') 249 | .transition() 250 | .duration(self.par.defaultDuration) 251 | .attr('d', d => this.line(d.values)); 252 | 253 | this.lineGroup.append('path') 254 | .attr('class', 'line') 255 | .attr('id', d => `line${d.id}`) 256 | .style('fill', 'none') 257 | .style('stroke', (d, i) => self.par.colorArray[i]) 258 | .style('stroke-width', self.par.strokeWidth) 259 | .style('stroke-linecap', 'round') 260 | .on('mouseover', d => this.mouseoverLine.call(self, d, this)) 261 | .on('mouseout', d => this.mouseoutLine.call(self, d, this)) 262 | .on('mousemove', d => this.mousemoveLine.call(self, d, this)) 263 | .attr('d', d => this.line(d.values)) 264 | .style('opacity', 0) 265 | .transition() 266 | .duration(self.par.defaultDuration) 267 | .style('opacity', 1); 268 | 269 | 270 | // EXIT 271 | this.joinLine.exit() 272 | .transition() 273 | .duration(self.par.defaultDuration) 274 | .style('opacity', 0) 275 | .remove(); 276 | }, 277 | 278 | onEvent(obj) { 279 | const self = this; 280 | const { d, e } = obj; 281 | switch (e) { 282 | case 'mouseover': 283 | this.svg.selectAll('.line') 284 | .style('stroke-width', self.par.strokeWidth); 285 | this.svg.select(`#line${d.id}`) 286 | .style('stroke-width', self.par.strokeWidth * 3); 287 | break; 288 | case 'mouseout': 289 | this.svg.selectAll('.line') 290 | .style('stroke-width', self.par.strokeWidth); 291 | break; 292 | default: 293 | } 294 | }, 295 | 296 | mouseoverLine(d) { 297 | // pass the event to the partent component 298 | this.reactComp.handleChartEvent(d, 'mouseover'); 299 | // this.reactComp 300 | // .handleChartEvent({id: this.par.fundMap.get(d.id).comp}, 'mouseover'); 301 | 302 | this.svg.select(`#line${d.id}`) 303 | .style('stroke-width', this.par.strokeWidth * 3); 304 | 305 | // show tooltip 306 | this.tooltip.html(this.par.tooltip(d)) 307 | .style('opacity', 1) 308 | .style('top', `${(d3.event.pageY - 10)}px`) 309 | .style('left', `${(d3.event.pageX + 10)}px`); 310 | }, 311 | 312 | mouseoutLine(d) { 313 | // pass the event to the partent component 314 | this.reactComp.handleChartEvent(d, 'mouseout'); 315 | 316 | this.svg.select(`#line${d.id}`) 317 | .transition() 318 | .duration(this.par.defaultDuration) 319 | .style('stroke-width', this.par.strokeWidth); 320 | 321 | // hide tooltip 322 | this.tooltip.style('opacity', 0); 323 | }, 324 | 325 | mousemoveLine() { 326 | // note: we do not pass that event to parent component 327 | 328 | // move tooltip 329 | this.tooltip 330 | .style('top', `${(d3.event.pageY)}px`) 331 | .style('left', `${(+!this.par.debugMode * d3.event.pageX + 10)}px`); 332 | }, 333 | }; 334 | -------------------------------------------------------------------------------- /apps/charts/pieChart.js: -------------------------------------------------------------------------------- 1 | const d3 = require('d3'); 2 | 3 | export const pieChart = { 4 | defaultParams: { 5 | size: 1000, // debug switch, for exact values 6 | col1: 'green', 7 | col2: 'red', 8 | defaultDuration: 500, 9 | innerRadius: 0, 10 | cornerRadius: 5, 11 | colorType: 'gradient', 12 | colorArray: d3.schemeCategory20, 13 | tooltip: d => `
ID: ${d.id}
Value: ${d.value}
`, 14 | }, 15 | 16 | mainFunction(loc, data, params, reactComp) { 17 | this.reactComp = reactComp; 18 | 19 | this.par = Object.assign({}, this.defaultParams, params); 20 | 21 | const size = this.par.size; 22 | 23 | const width = size - 20; 24 | const height = size - 20; 25 | const radius = Math.min(width, height) / 2; 26 | const fullWidth = size; 27 | const fullHeight = size; 28 | 29 | this.arc = d3.arc() 30 | .outerRadius(radius - 10); 31 | 32 | this.pie = d3.pie() 33 | .sort(null) 34 | .value(d => d.value); 35 | 36 | this.svg = loc.append('svg') 37 | .attr('id', 'd3graphSVG') 38 | .style('display', 'inline-block') 39 | .style('position', 'absolute') 40 | .attr('preserveAspectRatio', 'xMinYMin slice') 41 | .attr('viewBox', `0 0 ${fullWidth} ${fullHeight}`) 42 | .append('g') 43 | .attr('transform', `translate(${(width / 2)},${(height / 2)})`); 44 | 45 | this.tooltip = d3.select('body') 46 | .append('div') 47 | .style('background', 'rgba(238, 238, 238, 0.85)') 48 | .style('padding', '5px') 49 | .style('border-radius', '5px') 50 | .style('border-color', '#999') 51 | .style('border-width', '2px') 52 | .style('border-style', 'solid') 53 | .style('pointer-events', 'none') 54 | .style('position', 'absolute') 55 | .style('z-index', '10') 56 | .style('opacity', 0); 57 | 58 | this.updateFunction(data, params); 59 | }, 60 | 61 | destroyFunction() { 62 | this.tooltip.remove(); 63 | }, 64 | 65 | tweenFunc(a, context) { 66 | const i = d3.interpolate(this._current || a, a); 67 | this._current = i(0); 68 | return t => context.arc(i(t)); 69 | }, 70 | 71 | updateFunction(data, params) { 72 | const self = this; 73 | this.par = Object.assign({}, this.defaultParams, params); 74 | 75 | this.colFunc = this.colorFunction(this.par); 76 | 77 | this.arc 78 | .innerRadius(this.par.innerRadius) 79 | .cornerRadius(this.par.cornerRadius); 80 | 81 | this.join = this.svg.selectAll('.pie') 82 | .data(this.pie(data), d => d.data.id); 83 | this.angMax = d3.max(this.pie(data).map(d => d.endAngle - d.startAngle)); 84 | 85 | this.join 86 | .transition() 87 | .duration(500) 88 | .attrTween('d', d => self.tweenFunc.apply(this, [d, self])) 89 | .style('fill', (d, i) => self.colFunc(d, i)); 90 | 91 | // ENTER 92 | this.join.enter().append('path') 93 | .attr('class', 'pie pie-sector') 94 | .attr('id', d => `pie-sector-${d.data.id}`) 95 | .style('stroke', 'white') 96 | .style('stroke-width', '2px') 97 | .attr('d', this.arc) 98 | .each((d) => { 99 | this._current = d; 100 | }) 101 | .style('fill', (d, i) => self.colFunc(d, i)) 102 | .on('mouseover', (d) => { 103 | self.mouseoverSector.call(self, d, this); 104 | }) 105 | .on('mouseout', (d) => { 106 | self.mouseoutSector.call(self, d, this); 107 | }) 108 | .on('mousemove', (d) => { 109 | self.mousemoveSector.call(self, d, this); 110 | }); 111 | 112 | // EXIT 113 | this.join.exit().remove(); 114 | }, 115 | 116 | colorFunction() { 117 | const self = this; 118 | if (this.par.colorType === 'gradient') { 119 | return d => d3.interpolateHsl(self.par.col1, self.par.col2)( 120 | (d.endAngle - d.startAngle) / self.angMax); 121 | } else if (self.par.colorType === 'category') { 122 | const cols = self.par.colorArray; 123 | return (d, i) => cols[i]; 124 | } 125 | return () => 'gray'; 126 | }, 127 | 128 | onEvent(obj) { 129 | const self = this; 130 | const { d, e } = obj; 131 | switch (e) { 132 | case 'mouseover': 133 | this.svg.selectAll('.pie-sector') 134 | .style('fill-opacity', 0.15) 135 | .style('stroke-opacity', 0) 136 | .style('stroke-width', '5px') 137 | .style('stroke', (dd, i) => self.colFunc(dd, i)); 138 | this.svg.selectAll(`#pie-sector-${d.id}`) 139 | .style('fill-opacity', 0.5) 140 | .style('stroke-opacity', 1); 141 | break; 142 | case 'mouseout': 143 | this.svg.selectAll('.pie-sector') 144 | .style('fill-opacity', 1) 145 | .style('stroke', 'white') 146 | .style('stroke-width', '2px') 147 | .style('stroke-opacity', 1); 148 | break; 149 | default: 150 | } 151 | }, 152 | 153 | mouseoverSector(d) { 154 | // pass the event to the partent component 155 | this.reactComp.handleChartEvent(d.data, 'mouseover'); 156 | 157 | // show tooltip 158 | this.tooltip.html(this.par.tooltip(d.data)) 159 | .style('opacity', 1) 160 | .style('top', `${(d3.event.pageY - 10)}px`) 161 | .style('left', `${(d3.event.pageX + 10)}px`); 162 | }, 163 | 164 | mouseoutSector(d) { 165 | // pass the event to the partent component 166 | this.reactComp.handleChartEvent(d.data, 'mouseout'); 167 | 168 | // hide tooltip 169 | this.tooltip.style('opacity', 0); 170 | }, 171 | 172 | mousemoveSector() { 173 | // note: we do not pass that event to parent component 174 | 175 | // move tooltip 176 | this.tooltip 177 | .style('top', `${(d3.event.pageY)}px`) 178 | .style('left', `${(d3.event.pageX + 10)}px`); 179 | }, 180 | }; 181 | -------------------------------------------------------------------------------- /apps/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | // COORDINATE HIGHLIGHTS 2 | export const SET_EVENT = 'SET_EVENT'; 3 | -------------------------------------------------------------------------------- /apps/main.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import { applyMiddleware, combineReducers, createStore } from 'redux'; 4 | import thunkMiddleware from 'redux-thunk'; 5 | import { Provider } from 'react-redux'; 6 | 7 | import D3Container from './CoreComponent/d3container'; 8 | import wrapper from './CoreComponent/wrapper'; 9 | 10 | import * as reducers from './reducers'; 11 | 12 | const reducer = combineReducers(reducers); 13 | 14 | const createStoreWithMiddleware = applyMiddleware( 15 | thunkMiddleware 16 | )(createStore); 17 | 18 | const store = createStoreWithMiddleware(reducer); 19 | 20 | export const Main = (props) => { 21 | const { component, Loader } = props; 22 | if (component) { // wrapper 23 | const Comp = wrapper(component); 24 | return ( 25 | 29 | ); 30 | } else if (Loader) { // external loader 31 | return ( 32 | 36 | ); 37 | } 38 | return ( 39 | 43 | ); 44 | }; 45 | 46 | Main.defaultProps = { 47 | component: 0, 48 | }; 49 | 50 | Main.propTypes = { 51 | component: PropTypes.any, 52 | Loader: PropTypes.any, 53 | }; 54 | -------------------------------------------------------------------------------- /apps/reducers/chartReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_EVENT, 3 | } from '../constants/ActionTypes'; 4 | 5 | const initialState = { 6 | data: {}, 7 | eventGroup: [], 8 | event: '', 9 | timeStamp: 0, 10 | 11 | }; 12 | 13 | export default function chartReducer(state = initialState, action) { 14 | switch (action.type) { 15 | case SET_EVENT: 16 | { 17 | const { data, event, eventGroup } = action.event; 18 | const timeStamp = new Date().getTime(); 19 | const newState = Object.assign( 20 | {}, 21 | state, 22 | { 23 | data, 24 | event, 25 | eventGroup, 26 | timeStamp, 27 | } 28 | ); 29 | return newState; 30 | } 31 | default: 32 | return state; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/reducers/index.js: -------------------------------------------------------------------------------- 1 | export { default as d3ReactSquared } from './chartReducer'; 2 | -------------------------------------------------------------------------------- /dist/d3-react-squared.js: -------------------------------------------------------------------------------- 1 | /*! Thanks to all the providers of the components. See the respectivegithub pages for their licenses. */ 2 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("d3")):"function"==typeof define&&define.amd?define(["react","d3"],e):"object"==typeof exports?exports["d3-react-squared"]=e(require("react"),require("d3")):t["d3-react-squared"]=e(t.react,t.d3)}(this,function(t,e){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.Main=void 0;var o=Object.assign||function(t){for(var e=1;e1)for(var r=1;rn&&this.incomingEvent(o,["default"]),this.setState({lastEvent:o.timeStamp})}},{key:"shouldComponentUpdate",value:function(t){return t.eventData.timeStamp<=this.state.lastEvent}},{key:"componentWillUnmount",value:function(){var t=this.state.chartObject;t.destroyFunction&&t.destroyFunction()}},{key:"incomingEvent",value:function(t){var e=t.data,r=t.event,n=t.eventGroup,i=this.props,o=i.highlightListen,s=i.highlight,a=this.state.chartObject,u=o,c=n.filter(function(t){return u.indexOf(t)!==-1});c.length&&s&&a.onEvent&&a.onEvent({d:e,e:r})}},{key:"createNewChart",value:function(t,e){var r=this.props,n=r.paddingBottom,i=r.setEvent;h.select(this.node).select("#d3graphSVG").remove();var o=void 0;switch(t){case"bar":o=Object.create(f.barChart);break;case"line":o=Object.create(y.lineChart);break;case"pie":o=Object.create(d.pieChart);break;case"custom":o=Object.create(e.chartModule);break;default:o=Object.create(f.barChart)}o.setEvent=i,this.setState({chartObject:o,chartStyle:Object.assign({},this.state.chartStyle,{paddingBottom:n})}),o.mainFunction(h.select(this.node),e.data,e.params,this)}},{key:"handleChartEvent",value:function(t,e){var r=this.props,n=r.onChartEvent,i=r.highlightEmit;n&&n(t,e);var o={data:t,event:e,eventGroup:i};this.props.setEvent(o)}},{key:"render",value:function(){var t=this,e=this.props.paddingBottom,r=this.state.chartStyle;return e&&(r=Object.assign({},r,{paddingBottom:e})),l["default"].createElement("div",{ref:function(e){return t.node=e},style:r})}}]),e}(c.Component);e["default"]=v,v.defaultProps={params:{},chartType:"bar",paddingBottom:"100%",chartModule:f.barChart,data:[],highlight:!0,highlightEmit:["default"],highlightListen:["default"]},v.propTypes={chartModule:c.PropTypes.object,chartType:c.PropTypes.string,data:c.PropTypes.array,eventData:c.PropTypes.object.isRequired,highlight:c.PropTypes.bool,highlightEmit:c.PropTypes.array,highlightListen:c.PropTypes.array,onChartEvent:c.PropTypes.func,paddingBottom:c.PropTypes.string,params:c.PropTypes.object,setEvent:c.PropTypes.func.isRequired}},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r]);return e["default"]=t,e}function o(t){return{eventData:t.d3ReactSquared}}function s(t){return(0,a.bindActionCreators)(l,t)}Object.defineProperty(e,"__esModule",{value:!0});var a=r(3),u=r(6),c=r(7),l=i(c),p=r(14),h=n(p);e["default"]=(0,u.connect)(o,s)(h["default"])},function(t,e,r){"use strict";function n(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r]);return e["default"]=t,e}function i(t){return{eventData:t.d3ReactSquared}}function o(t){return(0,s.bindActionCreators)(c,t)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t){return(0,a.connect)(i,o)(t)};var s=r(3),a=r(6),u=r(7),c=n(u)},function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},i=r(4);e.barChart={defaultParams:{size:1e3,aspectRatio:1,labelSize:1,col1:"green",col2:"red",defaultDuration:500,rx:5,ry:5,yLabel:"Value",colorType:"gradient",colorArray:i.schemeCategory20,tooltip:function(t){return"
ID: "+t.id+"
Value: "+t.value+"
"}},mainFunction:function(t,e,r,n){var o=this;this.reactComp=n,o.par=Object.assign({},this.defaultParams,r),this.size=this.par.size;var s=this.par.labelSize;this.fontSize=s*this.size/100,this.margin={top:this.size/100,right:this.size/50,bottom:this.fontSize+this.size/100,left:40*(1+s/10)},this.width=this.size-this.margin.left-this.margin.right,this.height=this.size*this.par.aspectRatio-this.margin.top-this.margin.bottom,this.fullWidth=this.size,this.fullHeight=this.size*this.par.aspectRatio,this.x=i.scaleBand().rangeRound([0,this.width],.1),this.y=i.scaleLinear().range([this.height,0]),this.xAxis=i.axisBottom(this.x).tickSizeInner([this.size/250]).tickSizeOuter([this.size/250]).tickPadding([this.size/250]),this.yAxis=i.axisLeft(this.y).tickSizeInner([this.size/250]).tickSizeOuter([this.size/250]).tickPadding([this.size/250]).tickFormat(i.format(".2s")),this.svg=t.append("svg").attr("id","d3graphSVG").style("display","inline-block").style("position","absolute").attr("preserveAspectRatio","xMinYMin slice").attr("viewBox","0 0 "+this.fullWidth+" "+this.fullHeight).append("g").attr("transform","translate("+this.margin.left+","+this.margin.top+")"),this.x.domain(e.map(function(t){return t.id})),this.yMax=i.max(e,function(t){return t.value})||100,this.y.domain([0,this.yMax]),this.xAx=this.svg.append("g").attr("class","x axis").style("stroke-width",this.par.size/1e3+"px").style("font-size",this.fontSize+"px").style("font-family","sans-serif").attr("transform","translate(0,"+this.height+")").call(this.xAxis),this.yAx=this.svg.append("g").attr("class","y axis").style("stroke-width",this.par.size/1e3+"px").style("font-size",this.fontSize+"px").style("font-family","sans-serif").call(this.yAxis),this.yAx.append("text").attr("transform","rotate(-90)").attr("y",this.fontSize/2).attr("dy",".71em").style("text-anchor","end").text(o.par.yLabel),this.tooltip=i.select("body").append("div").style("background","rgba(238, 238, 238, 0.85)").style("padding","5px").style("border-radius","5px").style("border-color","#999").style("border-width","2px").style("border-style","solid").style("pointer-events","none").style("position","absolute").style("z-index","10").style("opacity",0),this.updateFunction(e,r)},destroyFunction:function(){this.tooltip.remove()},updateFunction:function(t,e){var r=this,n=this;n.par=Object.assign({},this.defaultParams,e),n.colFunc=this.colorFunction(n.par),this.x.domain(t.map(function(t){return t.id})),this.yMax=i.max(t,function(t){return t.value})||100,this.y.domain([0,this.yMax]),this.yAx.transition().duration(n.par.defaultDuration).call(this.yAxis),this.xAx.transition().duration(n.par.defaultDuration).call(this.xAxis),this.svg.selectAll(".axis line").style("fill","none").style("stroke","#000").style("shape-rendering","crispEdges"),this.svg.selectAll(".axis path").style("fill","none").style("stroke","#000").style("shape-rendering","crispEdges"),this.join=this.svg.selectAll(".bar").data(t,function(t){return t.id}),this.join.transition().duration(n.par.defaultDuration).attr("y",function(t){return n.y(t.value)}).attr("height",function(t){return n.height-n.y(t.value)}).attr("width",n.x.bandwidth()).attr("x",function(t){return n.x(t.id)}).style("fill",function(t,e){return n.colFunc(t,e)}),this.join.enter().append("rect").attr("width",0).attr("height",0).attr("y",0).attr("rx",n.par.rx).attr("ry",n.par.ry).style("stroke","transparent").style("stroke-width","2px").on("mouseover",function(t){n.mouseoverBar.call(n,t,r)}).on("mouseout",function(t){n.mouseoutBar.call(n,t,r)}).on("mousemove",function(t){n.mousemoveBar.call(n,t,r)}).transition().duration(n.par.defaultDuration).attr("class","bar bar").attr("id",function(t){return"bar-"+t.id}).attr("x",function(t){return n.x(t.id)}).attr("width",n.x.bandwidth()).attr("y",function(t){return n.y(t.value)}).attr("height",function(t){return n.height-n.y(t.value)}).style("fill",function(t,e){return n.colFunc(t,e)}),this.join.exit().transition().duration(n.par.defaultDuration).attr("width",0).attr("height",0).remove()},onEvent:function(t){var e=this,r=t.d,n=t.e;switch(n){case"mouseover":this.svg.selectAll(".bar").style("fill-opacity",.15).style("stroke-opacity",0).style("stroke-width","5px").style("stroke",function(t,r){return e.colFunc(t,r)}),this.svg.selectAll("#bar-"+r.id).style("fill-opacity",.5).style("stroke-opacity",1);break;case"mouseout":this.svg.selectAll(".bar").style("fill-opacity",1).style("stroke","transparent").style("stroke-width","2px").style("stroke-opacity",1)}},colorFunction:function(t){var e=this;if("gradient"===t.colorType)return function(r){return i.interpolateHsl(t.col1,t.col2)(r.value/e.yMax)};if("category"===t.colorType){var r=function(){var e=t.colorArray;return{v:function(t,r){return e[r]}}}();if("object"===("undefined"==typeof r?"undefined":n(r)))return r.v}return function(){return"gray"}},mouseoverBar:function(t){this.reactComp.handleChartEvent(t,"mouseover"),this.tooltip.html(this.par.tooltip(t)).style("opacity",1).style("top",i.event.pageY-10+"px").style("left",i.event.pageX+10+"px")},mouseoutBar:function(t){this.reactComp.handleChartEvent(t,"mouseout"),this.tooltip.style("opacity",0)},mousemoveBar:function(){this.tooltip.style("top",i.event.pageY+"px").style("left",i.event.pageX+10+"px")}}},function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=r(4);e.lineChart={defaultParams:{defaultDuration:500,size:1e3,aspectRatio:.5,labelSize:1,yLabel:"Value",xLabel:"Value",colorArray:n.schemeCategory20,strokeWidth:3,yAxisPlacement:"left",xMax:-(1/0),yMax:-(1/0),xMin:1/0,yMin:1/0,interpolate:"linear",tooltip:function(t){return"
ID: "+t.id+"
"}},mainFunction:function(t,e,r,i){var o=this;this.reactComp=i,o.par=Object.assign({},this.defaultParams,r),this.size=this.par.size;var s=this.par.labelSize;this.fontSize=s*this.size/100;var a=1,u=1;"left"===this.par.yAxisPlacement?a=5*(1+s/2):u=5*(1+s/2),this.margin={top:this.size/20,right:u*this.size/100,bottom:this.fontSize+this.size/20,left:a*this.size/100},this.width=this.size-this.margin.left-this.margin.right,this.height=this.size*this.par.aspectRatio-this.margin.top-this.margin.bottom,this.fullWidth=this.size,this.fullHeight=this.size*this.par.aspectRatio,this.x=n.scaleLinear().range([0,this.width]),this.y=n.scaleLinear().range([this.height,0]),this.xAxis=n.axisBottom(this.x).tickSizeInner([this.size/250]).tickSizeOuter([this.size/250]).tickPadding([this.size/250]),this.yAxis=n.axisRight(this.y).tickSizeInner([this.size/250]).tickSizeOuter([this.size/250]).tickPadding([this.size/250]),this.vb=t.append("svg").attr("id","d3graphSVG").style("display","inline-block").style("position","absolute").attr("preserveAspectRatio","xMinYMin slice").attr("viewBox","0 0 "+this.fullWidth+" "+this.fullHeight),this.svg=this.vb.append("g").attr("transform","translate("+this.margin.left+","+this.margin.top+")"),this.tooltip=n.select("body").append("div").style("background","rgba(238, 238, 238, 0.85)").style("padding","5px").style("border-radius","5px").style("border-color","#999").style("border-width","2px").style("border-style","solid").style("pointer-events","none").style("position","absolute").style("z-index","10").style("opacity",0),this.xAx=this.svg.append("g").attr("class","x axis").style("stroke-width",this.par.size/1e3+"px").style("font-size",this.fontSize+"px").style("font-family","sans-serif").attr("transform","translate(0,"+this.height+")").call(this.xAxis),this.xAx.append("text").attr("x","left"===o.par.yAxisPlacement?this.width:0).attr("y",-this.fontSize/2).style("text-anchor","left"===o.par.yAxisPlacement?"end":"start").text(o.par.xLabel),this.yAx=this.svg.append("g").attr("class","y axis").style("stroke-width",this.par.size/1e3+"px").style("font-size",this.fontSize+"px").style("font-family","sans-serif").attr("transform","translate("+("left"===o.par.yAxisPlacement?0:1)*this.width+", 0)").call(this.yAxis),this.yAx.append("text").attr("transform","rotate(-90)").attr("y","left"===o.par.yAxisPlacement?this.fontSize/2:-this.fontSize).attr("dy",".71em").style("text-anchor","end").text(o.par.yLabel),this.updateFunction(e,r)},destroyFunction:function(){this.tooltip.remove()},updateFunction:function(t,e){var r=this,i=this;i.par=Object.assign({},this.defaultParams,e);var o=n.curveLinear();switch(i.par.interpolate){case"linear-closed":o=n.curveLinearClosed;break;case"step":o=n.curveStep;break;case"step-after":o=n.curveStepAfter;break;case"step-before":o=n.curveStepBefore;break;case"basis":o=n.curveBasis;break;case"basisClosed":o=n.curveBasisClosed;break;case"bundle":o=n.curveBundle;break;case"cardinal":o=n.curveCardinal;break;case"cardinal-open":o=n.curveCardinalOpen;break;case"cardinal-closed":o=n.curveCardinalClosed;break;case"monotone":o=n.curveMonotoneX;break;default:o=n.curveLinear}this.line=n.line().curve(o).x(function(t){return r.x(t.x)}).y(function(t){return r.y(t.y)});var s=i.par,a=s.xMax,u=s.yMax,c=s.xMin,l=s.yMin;t.forEach(function(t){t.values.forEach(function(t){a=Math.max(t.x,a),u=Math.max(t.y,u),c=Math.min(t.x,c),l=Math.min(t.y,l)})}),a===-(1/0)&&c===1/0&&(a=c=0),u===-(1/0)&&l===1/0&&(u=l=0),this.x.domain([c,a]),this.y.domain([l,u]),this.xAx.transition().duration(i.par.defaultDuration).call(this.xAxis),this.yAx.transition().duration(i.par.defaultDuration).call(this.yAxis),this.svg.select(".y.axis").transition().duration(i.par.defaultDuration).call(this.yAxis),this.svg.select(".x.axis").transition().duration(i.par.defaultDuration).call(this.xAxis),this.svg.selectAll(".axis line").style("fill","none").style("stroke","#000").style("shape-rendering","crispEdges"),this.svg.selectAll(".axis path").style("fill","none").style("stroke","#000").style("shape-rendering","crispEdges"),this.joinLine=this.svg.selectAll(".lineGroup").data(t,function(t){return t.id}),this.lineGroup=this.joinLine.enter().append("g").attr("class","lineGroup"),this.joinLine.select("path").transition().duration(i.par.defaultDuration).attr("d",function(t){return r.line(t.values)}),this.lineGroup.append("path").attr("class","line").attr("id",function(t){return"line"+t.id}).style("fill","none").style("stroke",function(t,e){return i.par.colorArray[e]}).style("stroke-width",i.par.strokeWidth).style("stroke-linecap","round").on("mouseover",function(t){return r.mouseoverLine.call(i,t,r)}).on("mouseout",function(t){return r.mouseoutLine.call(i,t,r)}).on("mousemove",function(t){return r.mousemoveLine.call(i,t,r)}).attr("d",function(t){return r.line(t.values)}).style("opacity",0).transition().duration(i.par.defaultDuration).style("opacity",1),this.joinLine.exit().transition().duration(i.par.defaultDuration).style("opacity",0).remove()},onEvent:function(t){var e=this,r=t.d,n=t.e;switch(n){case"mouseover":this.svg.selectAll(".line").style("stroke-width",e.par.strokeWidth),this.svg.select("#line"+r.id).style("stroke-width",3*e.par.strokeWidth);break;case"mouseout":this.svg.selectAll(".line").style("stroke-width",e.par.strokeWidth)}},mouseoverLine:function(t){this.reactComp.handleChartEvent(t,"mouseover"),this.svg.select("#line"+t.id).style("stroke-width",3*this.par.strokeWidth),this.tooltip.html(this.par.tooltip(t)).style("opacity",1).style("top",n.event.pageY-10+"px").style("left",n.event.pageX+10+"px")},mouseoutLine:function(t){this.reactComp.handleChartEvent(t,"mouseout"),this.svg.select("#line"+t.id).transition().duration(this.par.defaultDuration).style("stroke-width",this.par.strokeWidth),this.tooltip.style("opacity",0)},mousemoveLine:function(){this.tooltip.style("top",n.event.pageY+"px").style("left",+!this.par.debugMode*n.event.pageX+10+"px")}}},function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},i=r(4);e.pieChart={defaultParams:{size:1e3,col1:"green",col2:"red",defaultDuration:500,innerRadius:0,cornerRadius:5,colorType:"gradient",colorArray:i.schemeCategory20,tooltip:function(t){return"
ID: "+t.id+"
Value: "+t.value+"
"}},mainFunction:function(t,e,r,n){this.reactComp=n,this.par=Object.assign({},this.defaultParams,r);var o=this.par.size,s=o-20,a=o-20,u=Math.min(s,a)/2,c=o,l=o;this.arc=i.arc().outerRadius(u-10),this.pie=i.pie().sort(null).value(function(t){return t.value}),this.svg=t.append("svg").attr("id","d3graphSVG").style("display","inline-block").style("position","absolute").attr("preserveAspectRatio","xMinYMin slice").attr("viewBox","0 0 "+c+" "+l).append("g").attr("transform","translate("+s/2+","+a/2+")"),this.tooltip=i.select("body").append("div").style("background","rgba(238, 238, 238, 0.85)").style("padding","5px").style("border-radius","5px").style("border-color","#999").style("border-width","2px").style("border-style","solid").style("pointer-events","none").style("position","absolute").style("z-index","10").style("opacity",0),this.updateFunction(e,r)},destroyFunction:function(){this.tooltip.remove()},tweenFunc:function(t,e){var r=i.interpolate(this._current||t,t);return this._current=r(0),function(t){return e.arc(r(t))}},updateFunction:function(t,e){var r=this,n=this;this.par=Object.assign({},this.defaultParams,e),this.colFunc=this.colorFunction(this.par),this.arc.innerRadius(this.par.innerRadius).cornerRadius(this.par.cornerRadius),this.join=this.svg.selectAll(".pie").data(this.pie(t),function(t){return t.data.id}),this.angMax=i.max(this.pie(t).map(function(t){return t.endAngle-t.startAngle})),this.join.transition().duration(500).attrTween("d",function(t){return n.tweenFunc.apply(r,[t,n])}).style("fill",function(t,e){return n.colFunc(t,e)}),this.join.enter().append("path").attr("class","pie pie-sector").attr("id",function(t){return"pie-sector-"+t.data.id}).style("stroke","white").style("stroke-width","2px").attr("d",this.arc).each(function(t){r._current=t}).style("fill",function(t,e){return n.colFunc(t,e)}).on("mouseover",function(t){n.mouseoverSector.call(n,t,r)}).on("mouseout",function(t){n.mouseoutSector.call(n,t,r)}).on("mousemove",function(t){n.mousemoveSector.call(n,t,r)}),this.join.exit().remove()},colorFunction:function(){var t=this;if("gradient"===this.par.colorType)return function(e){return i.interpolateHsl(t.par.col1,t.par.col2)((e.endAngle-e.startAngle)/t.angMax)};if("category"===t.par.colorType){var e=function(){var e=t.par.colorArray;return{v:function(t,r){return e[r]}}}();if("object"===("undefined"==typeof e?"undefined":n(e)))return e.v}return function(){return"gray"}},onEvent:function(t){var e=this,r=t.d,n=t.e;switch(n){case"mouseover":this.svg.selectAll(".pie-sector").style("fill-opacity",.15).style("stroke-opacity",0).style("stroke-width","5px").style("stroke",function(t,r){return e.colFunc(t,r)}),this.svg.selectAll("#pie-sector-"+r.id).style("fill-opacity",.5).style("stroke-opacity",1);break;case"mouseout":this.svg.selectAll(".pie-sector").style("fill-opacity",1).style("stroke","white").style("stroke-width","2px").style("stroke-opacity",1)}},mouseoverSector:function(t){this.reactComp.handleChartEvent(t.data,"mouseover"),this.tooltip.html(this.par.tooltip(t.data)).style("opacity",1).style("top",i.event.pageY-10+"px").style("left",i.event.pageX+10+"px")},mouseoutSector:function(t){this.reactComp.handleChartEvent(t.data,"mouseout"),this.tooltip.style("opacity",0)},mousemoveSector:function(){this.tooltip.style("top",i.event.pageY+"px").style("left",i.event.pageX+10+"px")}}},function(t,e,r){"use strict";function n(){var t=arguments.length<=0||void 0===arguments[0]?o:arguments[0],e=arguments[1];switch(e.type){case i.SET_EVENT:var r=e.event,n=r.data,s=r.event,a=r.eventGroup,u=(new Date).getTime(),c=Object.assign({},t,{data:n,event:s,eventGroup:a,timeStamp:u});return c;default:return t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var i=r(8),o={data:{},eventGroup:[],event:"",timeStamp:0}},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=r(20);Object.defineProperty(e,"d3ReactSquared",{enumerable:!0,get:function(){return n(i)["default"]}})},function(t,e){"use strict";var r={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,mixins:!0,propTypes:!0,type:!0},n={name:!0,length:!0,prototype:!0,caller:!0,arguments:!0,arity:!0},i="function"==typeof Object.getOwnPropertySymbols;t.exports=function(t,e,o){if("string"!=typeof e){var s=Object.getOwnPropertyNames(e);i&&(s=s.concat(Object.getOwnPropertySymbols(e)));for(var a=0;a does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions."))}e.__esModule=!0,e["default"]=void 0;var u=r(2),c=r(9),l=n(c),p=r(10),h=n(p),f=!1,d=function(t){function e(r,n){i(this,e);var s=o(this,t.call(this,r,n));return s.store=r.store,s}return s(e,t),e.prototype.getChildContext=function(){return{store:this.store}},e.prototype.render=function(){var t=this.props.children;return u.Children.only(t)},e}(u.Component);e["default"]=d,"production"!==t.env.NODE_ENV&&(d.prototype.componentWillReceiveProps=function(t){var e=this.store,r=t.store;e!==r&&a()}),d.propTypes={store:l["default"].isRequired, 3 | children:u.PropTypes.element.isRequired},d.childContextTypes={store:l["default"].isRequired}}).call(e,r(1))},function(t,e,r){(function(t){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function s(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t){return t.displayName||t.name||"Component"}function u(t,e){try{return t.apply(e)}catch(r){return T.value=r,T}}function c(e,r,n){var c=arguments.length<=3||void 0===arguments[3]?{}:arguments[3],h=Boolean(e),d=e||_,v=void 0;v="function"==typeof r?r:r?(0,m["default"])(r):j;var b=n||k,x=c.pure,P=void 0===x||x,S=c.withRef,A=void 0!==S&&S,C=P&&b!==k,z=M++;return function(e){function r(t,e){(0,w["default"])(t)||(0,g["default"])(e+"() in "+c+" must return a plain object. "+("Instead received "+t+"."))}function n(e,n,i){var o=b(e,n,i);return"production"!==t.env.NODE_ENV&&r(o,"mergeProps"),o}var c="Connect("+a(e)+")",m=function(a){function f(t,e){i(this,f);var r=o(this,a.call(this,t,e));r.version=z,r.store=t.store||e.store,(0,E["default"])(r.store,'Could not find "store" in either the context or '+('props of "'+c+'". ')+"Either wrap the root component in a , "+('or explicitly pass "store" as a prop to "'+c+'".'));var n=r.store.getState();return r.state={storeState:n},r.clearCache(),r}return s(f,a),f.prototype.shouldComponentUpdate=function(){return!P||this.haveOwnPropsChanged||this.hasStoreStateChanged},f.prototype.computeStateProps=function(e,n){if(!this.finalMapStateToProps)return this.configureFinalMapState(e,n);var i=e.getState(),o=this.doStatePropsDependOnOwnProps?this.finalMapStateToProps(i,n):this.finalMapStateToProps(i);return"production"!==t.env.NODE_ENV&&r(o,"mapStateToProps"),o},f.prototype.configureFinalMapState=function(e,n){var i=d(e.getState(),n),o="function"==typeof i;return this.finalMapStateToProps=o?i:d,this.doStatePropsDependOnOwnProps=1!==this.finalMapStateToProps.length,o?this.computeStateProps(e,n):("production"!==t.env.NODE_ENV&&r(i,"mapStateToProps"),i)},f.prototype.computeDispatchProps=function(e,n){if(!this.finalMapDispatchToProps)return this.configureFinalMapDispatch(e,n);var i=e.dispatch,o=this.doDispatchPropsDependOnOwnProps?this.finalMapDispatchToProps(i,n):this.finalMapDispatchToProps(i);return"production"!==t.env.NODE_ENV&&r(o,"mapDispatchToProps"),o},f.prototype.configureFinalMapDispatch=function(e,n){var i=v(e.dispatch,n),o="function"==typeof i;return this.finalMapDispatchToProps=o?i:v,this.doDispatchPropsDependOnOwnProps=1!==this.finalMapDispatchToProps.length,o?this.computeDispatchProps(e,n):("production"!==t.env.NODE_ENV&&r(i,"mapDispatchToProps"),i)},f.prototype.updateStatePropsIfNeeded=function(){var t=this.computeStateProps(this.store,this.props);return(!this.stateProps||!(0,y["default"])(t,this.stateProps))&&(this.stateProps=t,!0)},f.prototype.updateDispatchPropsIfNeeded=function(){var t=this.computeDispatchProps(this.store,this.props);return(!this.dispatchProps||!(0,y["default"])(t,this.dispatchProps))&&(this.dispatchProps=t,!0)},f.prototype.updateMergedPropsIfNeeded=function(){var t=n(this.stateProps,this.dispatchProps,this.props);return!(this.mergedProps&&C&&(0,y["default"])(t,this.mergedProps))&&(this.mergedProps=t,!0)},f.prototype.isSubscribed=function(){return"function"==typeof this.unsubscribe},f.prototype.trySubscribe=function(){h&&!this.unsubscribe&&(this.unsubscribe=this.store.subscribe(this.handleChange.bind(this)),this.handleChange())},f.prototype.tryUnsubscribe=function(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)},f.prototype.componentDidMount=function(){this.trySubscribe()},f.prototype.componentWillReceiveProps=function(t){P&&(0,y["default"])(t,this.props)||(this.haveOwnPropsChanged=!0)},f.prototype.componentWillUnmount=function(){this.tryUnsubscribe(),this.clearCache()},f.prototype.clearCache=function(){this.dispatchProps=null,this.stateProps=null,this.mergedProps=null,this.haveOwnPropsChanged=!0,this.hasStoreStateChanged=!0,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,this.renderedElement=null,this.finalMapDispatchToProps=null,this.finalMapStateToProps=null},f.prototype.handleChange=function(){if(this.unsubscribe){var t=this.store.getState(),e=this.state.storeState;if(!P||e!==t){if(P&&!this.doStatePropsDependOnOwnProps){var r=u(this.updateStatePropsIfNeeded,this);if(!r)return;r===T&&(this.statePropsPrecalculationError=T.value),this.haveStatePropsBeenPrecalculated=!0}this.hasStoreStateChanged=!0,this.setState({storeState:t})}}},f.prototype.getWrappedInstance=function(){return(0,E["default"])(A,"To access the wrapped instance, you need to specify { withRef: true } as the fourth argument of the connect() call."),this.refs.wrappedInstance},f.prototype.render=function(){var t=this.haveOwnPropsChanged,r=this.hasStoreStateChanged,n=this.haveStatePropsBeenPrecalculated,i=this.statePropsPrecalculationError,o=this.renderedElement;if(this.haveOwnPropsChanged=!1,this.hasStoreStateChanged=!1,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,i)throw i;var s=!0,a=!0;P&&o&&(s=r||t&&this.doStatePropsDependOnOwnProps,a=t&&this.doDispatchPropsDependOnOwnProps);var u=!1,c=!1;n?u=!0:s&&(u=this.updateStatePropsIfNeeded()),a&&(c=this.updateDispatchPropsIfNeeded());var h=!0;return h=!!(u||c||t)&&this.updateMergedPropsIfNeeded(),!h&&o?o:(A?this.renderedElement=(0,p.createElement)(e,l({},this.mergedProps,{ref:"wrappedInstance"})):this.renderedElement=(0,p.createElement)(e,this.mergedProps),this.renderedElement)},f}(p.Component);return m.displayName=c,m.WrappedComponent=e,m.contextTypes={store:f["default"]},m.propTypes={store:f["default"]},"production"!==t.env.NODE_ENV&&(m.prototype.componentWillUpdate=function(){this.version!==z&&(this.version=z,this.trySubscribe(),this.clearCache())}),(0,O["default"])(m,e)}}var l=Object.assign||function(t){for(var e=1;e0?"Unexpected "+(s.length>1?"keys":"key")+" "+('"'+s.join('", "')+'" found in '+o+". ")+"Expected to find one of the known reducer keys instead: "+('"'+i.join('", "')+'". Unexpected keys will be ignored.'):void 0}function s(t){Object.keys(t).forEach(function(e){var r=t[e],n=r(void 0,{type:u.ActionTypes.INIT});if("undefined"==typeof n)throw new Error('Reducer "'+e+'" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined.');var i="@@redux/PROBE_UNKNOWN_ACTION_"+Math.random().toString(36).substring(7).split("").join(".");if("undefined"==typeof r(void 0,{type:i}))throw new Error('Reducer "'+e+'" returned undefined when probed with a random type. '+("Don't try to handle "+u.ActionTypes.INIT+' or other actions in "redux/*" ')+"namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined.")})}function a(e){for(var r=Object.keys(e),n={},a=0;a 46 |
47 | Here, we have a plain component wrapped in a HOC 48 | ( 49 | 53 | high-order component 54 | ), 55 | to have access to the chart events! (from anywhere in our app) 56 |

57 | We can read from event system: 58 | Last event {eventType} at:{' '} 59 | {formattedTime} 60 | {eventId}{eventGroup} 61 |

62 | Or initiate events: 63 | 68 | 73 |
74 |
75 | Remember: you can define all sorts of fancy events in the charts 76 | (onEvent method on chart object). 77 | In our examples, we just defined mouseover/mouseout on id. 78 |
79 | We might add some fancy stuff later, to give a better idea. 80 |
81 |
82 | Oh: and you can pass props through the wrapper, as you might expect: 83 | Example: {passThruProp} (see source code 84 | for the origin of this; 85 | spoiler: outside the wrapper) 86 |
87 |
); 88 | return (
89 | {content} 90 |
); 91 | } 92 | } 93 | 94 | PlainComponent.propTypes = { 95 | eventData: PropTypes.object, 96 | passThruProp: PropTypes.string, 97 | setEvent: PropTypes.func.isRequired, 98 | }; 99 | -------------------------------------------------------------------------------- /example/WrappedComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Main as Wrapper } from '../apps/main'; 4 | import PlainComponent from './PlainComponent'; 5 | 6 | export const WrappedComponent = () => ; 10 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Main as Chart } from '../apps/main'; 4 | 5 | import { WrappedComponent } from './WrappedComponent'; 6 | 7 | export default class Example extends Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | fakeLineData: [], 12 | lineInterpolate: 'none', 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | this.fakeLineData(); 18 | } 19 | 20 | handleLineInterpolate(lineInterpolate) { 21 | this.setState({ lineInterpolate }); 22 | } 23 | 24 | fakeLineData() { 25 | const numberLines = Math.floor(10 * Math.random()) + 1; 26 | const newData = []; 27 | 28 | const sortAsc = (a, b) => a - b; 29 | 30 | for (let i = 0; i < numberLines; i += 1) { 31 | let points = new Set(); 32 | for (let j = 0; j < Math.floor(5 * Math.random()) + 3; j += 1) { 33 | points.add(Math.floor(10 * Math.random())); 34 | } 35 | points = [...points].sort(sortAsc); 36 | 37 | const values = []; 38 | for (let ii = 0; ii < points.length; ii += 1) { 39 | values.push({ 40 | x: points[ii], 41 | y: Math.random(), 42 | }); 43 | } 44 | 45 | newData.push({ 46 | id: i, 47 | values, 48 | }); 49 | } 50 | this.setState({ fakeLineData: newData }); 51 | } 52 | 53 | render() { 54 | const fakeData = () => [ 55 | { 56 | id: 0, 57 | value: 1 + Math.floor(10 * Math.random()), 58 | }, 59 | { 60 | id: 1, 61 | value: 1 + Math.floor(10 * Math.random()), 62 | }, 63 | { 64 | id: 2, 65 | value: 1 + Math.floor(10 * Math.random()), 66 | }, 67 | ]; 68 | 69 | const interpols = 70 | ['linear', 71 | 'linear-closed', 72 | 'step', 73 | 'step-before', 74 | 'step-after', 75 | 'basis', 76 | 'basis-closed', 77 | 'bundle', 78 | 'cardinal', 79 | 'cardinal-open', 80 | 'cardinal-closed', 81 | 'monotone']; 82 | const interpolButtons = []; 83 | interpols.forEach((inter) => { 84 | interpolButtons.push( 85 | ); 91 | }); 92 | 93 | return (
94 | 95 | And some exemplary charts (do note:{' '} 96 | d3-react-squared is a not a charts-library!): 97 |
98 | 101 |
102 | {interpolButtons} 103 | 113 | 125 | 126 |
127 | 136 |
137 |
138 | 145 |
146 |
147 | 156 |
157 |
158 | 167 |
168 |
169 | 178 |
179 | 180 | 184 |
); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | d3-react-squared 7 | 8 | 9 | 10 |

Examples for d3-react-squared

11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | // load example component 7 | import Example from './example'; 8 | 9 | require('babel/polyfill'); 10 | 11 | ReactDOM.render(, document.getElementById('app')); 12 | -------------------------------------------------------------------------------- /img/dr2overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgrsquared/d3-react-squared/fa10af3d5f1652fd0b4c4e60f4a5d6ee19bb2e84/img/dr2overview.png -------------------------------------------------------------------------------- /img/explPieBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgrsquared/d3-react-squared/fa10af3d5f1652fd0b4c4e60f4a5d6ee19bb2e84/img/explPieBar.png -------------------------------------------------------------------------------- /img/explPieBar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgrsquared/d3-react-squared/fa10af3d5f1652fd0b4c4e60f4a5d6ee19bb2e84/img/explPieBar2.png -------------------------------------------------------------------------------- /img/explPieBarLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgrsquared/d3-react-squared/fa10af3d5f1652fd0b4c4e60f4a5d6ee19bb2e84/img/explPieBarLine.png -------------------------------------------------------------------------------- /img/explPlayground2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgrsquared/d3-react-squared/fa10af3d5f1652fd0b4c4e60f4a5d6ee19bb2e84/img/explPlayground2.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-react-squared", 3 | "version": "0.6.0", 4 | "description": "Load basic or customized D3 charts in React.js.", 5 | "main": "./dist/d3-react-squared.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "NODE_ENV=dev webpack-dev-server --devtool eval-source-map --progress --colors --hot --content-base example/", 9 | "ex": "NODE_ENV=example webpack-dev-server --devtool eval-source-map --progress --colors --hot --content-base example/", 10 | "prepublish": "NODE_ENV=production webpack -p" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/bgrsquared/d3-react-squared.git" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "d3", 19 | "react-component", 20 | "charts" 21 | ], 22 | "author": "christian.roth@bgrsquared.com", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/bgrsquared/d3-react-squared/issues" 26 | }, 27 | "homepage": "https://github.com/bgrsquared/d3-react-squared#readme", 28 | "devDependencies": { 29 | "babel-core": "^6.14.0", 30 | "babel-loader": "^6.2.5", 31 | "babel-polyfill": "^6.13.0", 32 | "babel-preset-es2015": "^6.14.0", 33 | "babel-preset-react": "^6.11.1", 34 | "babel-preset-stage-2": "^6.13.0", 35 | "eslint": "^3.5.0", 36 | "eslint-config-airbnb": "^11.1.0", 37 | "eslint-loader": "^1.5.0", 38 | "eslint-plugin-import": "^1.15.0", 39 | "eslint-plugin-jsx-a11y": "^2.2.2", 40 | "eslint-plugin-react": "^6.3.0", 41 | "node-libs-browser": "^1.0.0", 42 | "react": ">=15.3.2", 43 | "webpack": "^1.13.2", 44 | "webpack-dev-server": "^1.16.1" 45 | }, 46 | "dependencies": { 47 | "babel": "^6.5.2", 48 | "d3": "^4.2.4", 49 | "react-dom": ">=15.3.2", 50 | "react-redux": "^4.4.5", 51 | "redux": "^3.6.0", 52 | "redux-thunk": "^2.1.0" 53 | }, 54 | "peerDependencies": { 55 | "react": ">=15.3.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //this file could be massively improved. 2 | //inspired by https://christianalfoni.github.io/react-webpack-cookbook/ 3 | //please do visit this link! Awesome. 4 | 5 | var webpack = require('webpack'); 6 | var path = require('path'); 7 | var appDir = path.join(__dirname, 'apps'); 8 | var explDir = path.join(__dirname, 'example'); 9 | var licenseBanner = 'Thanks to all the providers of the components. See the respective' + 10 | 'github pages for their licenses.'; 11 | 12 | var outputPath, filename, entry, externals, output; 13 | switch (process.env.NODE_ENV) { 14 | case 'example': 15 | outputPath = __dirname + '/example'; 16 | filename = 'bundle.js'; 17 | entry = ['webpack/hot/dev-server', 18 | ( 19 | './example/main.js' 20 | )]; 21 | output = { 22 | path: outputPath, 23 | filename: filename 24 | }; 25 | externals = []; 26 | break; 27 | case 'production': 28 | outputPath = __dirname + '/dist'; 29 | filename = 'd3-react-squared.js'; 30 | entry = './apps/main.js'; 31 | output = { 32 | path: outputPath, 33 | library: 'd3-react-squared', 34 | libraryTarget: 'umd', 35 | filename: filename, 36 | }; 37 | externals = [ 38 | { 39 | react: 'react', 40 | 'react-dom': 'react-dom', 41 | d3: 'd3', 42 | } 43 | ]; 44 | break; 45 | default: 46 | outputPath = __dirname + '/dist'; 47 | filename = 'bundle.js'; 48 | entry = ['webpack/hot/dev-server', 49 | ( 50 | './example/main.js' 51 | )]; 52 | output = { 53 | path: outputPath, 54 | filename: filename 55 | }; 56 | externals = []; 57 | } 58 | 59 | var config = { 60 | context: __dirname, 61 | 62 | entry: entry, 63 | 64 | output: output, 65 | 66 | module: { 67 | noParse: [], 68 | loaders: [ 69 | { 70 | test: /\.js$/, 71 | include: [appDir, explDir], 72 | loader: 'babel-loader', 73 | }, 74 | ], 75 | preLoaders: [ 76 | { 77 | test: /\.js$/, 78 | include: [appDir, explDir], 79 | loader: 'eslint', 80 | }, 81 | ], 82 | }, 83 | 84 | externals: externals, 85 | 86 | plugins: [ 87 | new webpack.BannerPlugin(licenseBanner), 88 | ], 89 | }; 90 | 91 | module.exports = config; --------------------------------------------------------------------------------