├── .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 | [](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 | 
109 |
110 | #### Playground ([--> See here](http://bgrsquared.com/DR2/)) to learn about parameters:
111 | 
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;e
1)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 | this.highlightID0()}
65 | >
66 | Simulate mouseover @ id 0 (default group)
67 |
68 | this.mouseout()}
70 | >
71 | Simulate mouseout (default group)
72 |
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 (