From 2e67e448563727a2144a92aab4278817210f4b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Benitte?= Date: Sat, 12 Aug 2017 16:08:46 +0900 Subject: [PATCH] feat(stream): add stack tooltip on stream chart --- src/components/charts/stream/Stream.js | 73 +++++++++++++++---- src/components/charts/stream/StreamLayers.js | 1 - src/components/charts/stream/StreamSlices.js | 46 ++++++++++++ .../charts/stream/StreamSlicesItem.js | 73 +++++++++++++++++++ src/constants/directions.js | 1 - src/constants/index.js | 9 +++ src/index.js | 1 + 7 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 src/components/charts/stream/StreamSlices.js create mode 100644 src/components/charts/stream/StreamSlicesItem.js create mode 100644 src/constants/index.js diff --git a/src/components/charts/stream/Stream.js b/src/components/charts/stream/Stream.js index 5c97668e5..e9505b50a 100644 --- a/src/components/charts/stream/Stream.js +++ b/src/components/charts/stream/Stream.js @@ -8,8 +8,8 @@ */ import React from 'react' import PropTypes from 'prop-types' -import { merge, isEqual, min, max, range } from 'lodash' -import { stack as d3Stack, area as d3Area } from 'd3-shape' +import { min, max, range, sortBy } from 'lodash' +import { stack as d3Stack, area } from 'd3-shape' import { scaleLinear, scalePoint } from 'd3-scale' import compose from 'recompose/compose' import pure from 'recompose/pure' @@ -32,6 +32,7 @@ import Container from '../Container' import Axes from '../../axes/Axes' import Grid from '../../axes/Grid' import StreamLayers from './StreamLayers' +import StreamSlices from './StreamSlices' const stackMin = layers => min(layers.reduce((acc, layer) => [...acc, ...layer.map(d => d[0])], [])) const stackMax = layers => max(layers.reduce((acc, layer) => [...acc, ...layer.map(d => d[1])], [])) @@ -42,7 +43,7 @@ const Stream = ({ order, offsetType, - curveInterpolator, + areaGenerator, // dimensions margin, @@ -71,6 +72,9 @@ const Stream = ({ // interactivity isInteractive, + + // stack tooltip + enableStackTooltip, }) => { const stack = d3Stack() .keys(keys) @@ -85,17 +89,34 @@ const Stream = ({ const xScale = scalePoint().domain(range(data.length)).range([0, width]) const yScale = scaleLinear().domain([minValue, maxValue]).range([height, 0]) - const area = d3Area() - .x((d, i) => xScale(i)) - .y0(d => yScale(d[0])) - .y1(d => yScale(d[1])) - .curve(curveInterpolator) - - const enhancedLayers = layers.map((layer, i) => ({ - id: keys[i], - layer, - path: area(layer), - color: getColor(i), + const enhancedLayers = layers.map((points, i) => { + const layer = points.map(([y1, y2], i) => ({ + index: i, + value: y2 - y1, + x: xScale(i), + y1: yScale(y1), + y2: yScale(y2), + })) + + return { + id: keys[i], + layer, + path: areaGenerator(layer), + color: getColor(i), + } + }) + + const slices = range(data.length).map(i => ({ + index: i, + x: enhancedLayers[0].layer[i].x, + stack: sortBy( + enhancedLayers.map(layer => ({ + id: layer.id, + color: layer.color, + ...layer.layer[i], + })), + 'y2' + ), })) const motionProps = { @@ -118,7 +139,6 @@ const Stream = ({ /> + {isInteractive && + enableStackTooltip && + } } ) @@ -149,7 +177,7 @@ Stream.propTypes = { order: stackOrderPropType.isRequired, offsetType: stackOffsetPropType.isRequired, curve: areaCurvePropType.isRequired, - curveInterpolator: PropTypes.func.isRequired, + areaGenerator: PropTypes.func.isRequired, // dimensions width: PropTypes.number.isRequired, @@ -177,6 +205,9 @@ Stream.propTypes = { // interactivity isInteractive: PropTypes.bool, + + // stack tooltip + enableStackTooltip: PropTypes.bool.isRequired, } export const StreamDefaultProps = { @@ -201,6 +232,9 @@ export const StreamDefaultProps = { // interactivity isInteractive: true, + + // stack tooltip + enableStackTooltip: true, } const enhance = compose( @@ -208,6 +242,13 @@ const enhance = compose( withTheme(), withCurve(), withMargin(), + withPropsOnChange(['curveInterpolator'], ({ curveInterpolator }) => ({ + areaGenerator: area() + .x(({ x }) => x) + .y0(({ y1 }) => y1) + .y1(({ y2 }) => y2) + .curve(curveInterpolator), + })), withPropsOnChange(['colors'], ({ colors }) => ({ getColor: getColorRange(colors), })), diff --git a/src/components/charts/stream/StreamLayers.js b/src/components/charts/stream/StreamLayers.js index c2d955cb2..0474cecbf 100644 --- a/src/components/charts/stream/StreamLayers.js +++ b/src/components/charts/stream/StreamLayers.js @@ -80,7 +80,6 @@ const StreamLayers = ({ } StreamLayers.propTypes = { - area: PropTypes.func.isRequired, fillOpacity: PropTypes.number.isRequired, // motion diff --git a/src/components/charts/stream/StreamSlices.js b/src/components/charts/stream/StreamSlices.js new file mode 100644 index 000000000..62cb52350 --- /dev/null +++ b/src/components/charts/stream/StreamSlices.js @@ -0,0 +1,46 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React from 'react' +import PropTypes from 'prop-types' +import pure from 'recompose/pure' +import StreamSlicesItem from './StreamSlicesItem' + +const StreamSlices = ({ slices, height, showTooltip, hideTooltip }) => + + {slices.map(slice => + + )} + + +StreamSlices.propTypes = { + slices: PropTypes.arrayOf( + PropTypes.shape({ + index: PropTypes.number.isRequired, + x: PropTypes.number.isRequired, + stack: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + color: PropTypes.string.isRequired, + }) + ).isRequired, + }) + ).isRequired, + height: PropTypes.number.isRequired, + showTooltip: PropTypes.func.isRequired, + hideTooltip: PropTypes.func.isRequired, +} + +export default pure(StreamSlices) diff --git a/src/components/charts/stream/StreamSlicesItem.js b/src/components/charts/stream/StreamSlicesItem.js new file mode 100644 index 000000000..4b776765e --- /dev/null +++ b/src/components/charts/stream/StreamSlicesItem.js @@ -0,0 +1,73 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React from 'react' +import PropTypes from 'prop-types' +import compose from 'recompose/compose' +import pure from 'recompose/pure' +import withState from 'recompose/withState' +import withHandlers from 'recompose/withHandlers' +import withPropsOnChange from 'recompose/withPropsOnChange' +import TableTooltip from '../../tooltip/TableTooltip' + +const Chip = ({ color }) => + + +const StreamSlicesItem = ({ slice, height, showTooltip, hideTooltip, isHover }) => + + {isHover && + } + + + +StreamSlicesItem.propTypes = { + slice: PropTypes.object.isRequired, + height: PropTypes.number.isRequired, + showTooltip: PropTypes.func.isRequired, + hideTooltip: PropTypes.func.isRequired, + isHover: PropTypes.bool.isRequired, +} + +const enhance = compose( + withState('isHover', 'setIsHover', false), + withPropsOnChange(['slice'], ({ slice }) => ({ + tooltip: ( + [, p.id, p.value])} /> + ), + })), + withHandlers({ + showTooltip: ({ showTooltip, setIsHover, tooltip }) => e => { + setIsHover(true) + showTooltip(tooltip, e) + }, + hideTooltip: ({ hideTooltip, setIsHover }) => () => { + setIsHover(false) + hideTooltip() + }, + }), + pure +) + +export default enhance(StreamSlicesItem) diff --git a/src/constants/directions.js b/src/constants/directions.js index 6b2e57993..1337aa482 100644 --- a/src/constants/directions.js +++ b/src/constants/directions.js @@ -6,6 +6,5 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ - export const DIRECTION_HORIZONTAL = 'horizontal' export const DIRECTION_VERTICAL = 'vertical' diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 000000000..6dfe553bc --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,9 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +export * from './directions' diff --git a/src/index.js b/src/index.js index e5e3e7799..6c361cd01 100644 --- a/src/index.js +++ b/src/index.js @@ -22,4 +22,5 @@ export * from './components/charts/voronoi' export * from './components/charts/stream' export * from './components/axes' export * from './components/markers' +export * from './constants' export * from './props'