From fa879a05e6cd206371e75e3652642e6750262b9c Mon Sep 17 00:00:00 2001 From: Raphael Benitte Date: Sun, 8 May 2016 14:17:59 +0900 Subject: [PATCH] feat(stack): add StackDots component --- README.md | 1 + src/components/charts/stack/Stack.js | 21 +++- src/components/charts/stack/StackDots.js | 146 +++++++++++++++++++++++ src/components/charts/stack/index.js | 1 + src/lib/charts/stack/StackD3.js | 66 ++++++++++ 5 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 src/components/charts/stack/StackDots.js create mode 100644 src/lib/charts/stack/StackD3.js diff --git a/README.md b/README.md index eb21a9101..bd1c6b0c2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ - Stack - [``](https://plouc.github.io/nivo/#/stack) - [``](https://plouc.github.io/nivo/#/stack) + - [``](https://plouc.github.io/nivo/#/stack) - TreeMap - [``](https://plouc.github.io/nivo/#/treemap/d3) - [``](https://plouc.github.io/nivo/#/treemap/d3) diff --git a/src/components/charts/stack/Stack.js b/src/components/charts/stack/Stack.js index ac24660b4..461cf7762 100644 --- a/src/components/charts/stack/Stack.js +++ b/src/components/charts/stack/Stack.js @@ -16,6 +16,7 @@ import Nivo from '../../../Nivo'; import { lineInterpolation } from '../../../PropTypes'; import { getColorRange } from '../../../ColorUtils'; import { margin as marginPropType } from '../../../PropTypes'; +import decoratorsFromReactChildren from '../../../lib/decoratorsFromReactChildren'; class Stack extends Component { @@ -78,19 +79,33 @@ class Stack extends Component { .attr('d', area) .style('fill', (d, i) => color(i)) ; + + const stackContext = { + element: wrapper, + width, height, + stacked, + xScale, yScale, + color, + transitionDuration, transitionEasing + }; + + this.decorators.forEach(decorator => { + decorator(stackContext); + }); } shouldComponentUpdate(nextProps) { + this.decorators = decoratorsFromReactChildren(nextProps.children, 'decorateStack'); + this.renderD3(nextProps); return false; } componentDidMount() { - this.renderD3(this.props); - } + this.decorators = decoratorsFromReactChildren(this.props.children, 'decorateStack'); - componentWillMount() { + this.renderD3(this.props); } render() { diff --git a/src/components/charts/stack/StackDots.js b/src/components/charts/stack/StackDots.js new file mode 100644 index 000000000..12e6adf48 --- /dev/null +++ b/src/components/charts/stack/StackDots.js @@ -0,0 +1,146 @@ +/* + * This file is part of the nivo library. + * + * (c) Raphaël Benitte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +'use strict'; + +import React, { Component, PropTypes } from 'react'; +import invariant from 'invariant'; +import d3 from 'd3'; +import { getColorGenerator } from '../../../ColorUtils'; + + +class StackDots extends Component { + static decorateStack(element) { + const { props } = element; + + const colorFn = getColorGenerator(props.color); + const borderColorFn = getColorGenerator(props.borderColor); + const { showOnOver } = props; + + // Receive context from Parent Stack component + return ({ + element, + stacked, + width, height, + xScale, yScale, + color, + transitionDuration, transitionEasing + }) => { + const slices = []; + stacked.forEach(layer => { + layer.forEach((datum, i) => { + if (!slices[i]) { + slices[i] = []; + } + + slices[i].push(datum); + }); + }); + + const elements = element.selectAll('.nivo_stack_slices').data(slices); + + const newSlices = elements.enter().append('g') + .attr('class', 'nivo_stack_slices') + .attr('transform', (d, i) => `translate(${xScale(i)},0)`) + .style('opacity', showOnOver ? 0 : 1) + .style('cursor', 'pointer') + .style('pointer-events', 'all') + ; + + newSlices.append('rect') + .attr('width', props.radius * 2 + 20) + .attr('height', height) + .attr('transform', `translate(-${props.radius + 10},0)`) + .style('fill', 'none') + .style('pointer-events', 'all') + ; + + newSlices.selectAll('circle').data(d => d).enter().append('circle') + .attr('r', props.radius) + .attr('transform', d => { + return `translate(0,${yScale(d.y0 + d.y)})`; + }) + .style('fill', (d, i) => colorFn({ color: color(i) })) + .style('stroke-width', props.borderWidth) + .style('stroke', (d, i) => borderColorFn({ color: color(i) })) + ; + + elements + .style('opacity', showOnOver ? 0 : 1) + .on('mouseover', function () { + if (!showOnOver) { + return; + } + + d3.select(this) + .transition() + .duration(100) + .style('opacity', 1) + ; + }) + .on('mouseout', function () { + if (!showOnOver) { + return; + } + + d3.select(this) + .transition() + .duration(100) + .style('opacity', 0) + ; + }) + .transition() + .duration(transitionDuration) + .ease(transitionEasing) + .attr('transform', (d, i) => `translate(${xScale(i)},0)`) + ; + + elements + .selectAll('circle').data(d => d) + .transition() + .duration(transitionDuration) + .ease(transitionEasing) + .attr('r', props.radius) + .attr('transform', d => { + return `translate(0,${yScale(d.y0 + d.y)})`; + }) + .style('fill', (d, i) => colorFn({ color: color(i) })) + .style('stroke-width', props.borderWidth) + .style('stroke', (d, i) => borderColorFn({ color: color(i) })) + ; + }; + } + + render() { + invariant( + false, + ' element is for Stack configuration only and should not be rendered' + ); + } +} + +const { number, bool, any } = PropTypes; + +StackDots.propTypes = { + showOnOver: bool.isRequired, + radius: number.isRequired, + color: any.isRequired, + borderWidth: number.isRequired, + borderColor: any.isRequired, +}; + +StackDots.defaultProps = { + showOnOver: false, + radius: 4, + color: 'inherit', + borderWidth: 1, + borderColor: 'inherit:darker(.4)', +}; + + +export default StackDots; diff --git a/src/components/charts/stack/index.js b/src/components/charts/stack/index.js index b09ce2968..5db5aba4d 100644 --- a/src/components/charts/stack/index.js +++ b/src/components/charts/stack/index.js @@ -10,3 +10,4 @@ export Stack from './Stack'; export ResponsiveStack from './ResponsiveStack'; +export StackDots from './StackDots'; diff --git a/src/lib/charts/stack/StackD3.js b/src/lib/charts/stack/StackD3.js new file mode 100644 index 000000000..7d60939ef --- /dev/null +++ b/src/lib/charts/stack/StackD3.js @@ -0,0 +1,66 @@ +/* + * This file is part of the nivo library. + * + * (c) Raphaël Benitte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +'use strict'; + +import { flatten } from '../../../DataUtils'; + + +/** + * This wrapper is responsible for computing stack chart positions. + * It's used for all Stack related chart components. + * + * @returns {{ compute: (function) }} + * @constructor + */ +const StackD3 = () => { + const layout = d3.layout.pack(); + + return { + /** + * + * @param {number} width + * @param {number} height + * @param {object} data + * @param {string} identityProperty + * @param {function} valueAccessor + * @param {number} padding + * @param {function} color + * @returns {array} + */ + compute({ + width, height, + data, + identityProperty, valueAccessor, + padding, + color + }) { + layout + .value(valueAccessor) + .sort(null) + .size([width, height]) + .padding(padding) + ; + + const flattened = flatten(data, identityProperty); + const nodes = layout.nodes(flattened) + .filter(d => !d.children) + .map(d => { + d.color = color(d.parentId); + + return d; + }) + ; + + return nodes; + } + } +}; + + +export default StackD3;