From 18c43473bb28764c4515dc12a9a8bf24f55e5b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Benitte?= Date: Wed, 9 Aug 2017 15:37:37 +0900 Subject: [PATCH] feat(markers): improve radar & line charts markers --- src/Nivo.js | 4 + src/components/charts/SvgWrapper.js | 8 + src/components/charts/bubble/Bubble.js | 4 +- src/components/charts/line/Line.js | 11 ++ src/components/charts/line/LineMarkers.js | 165 ++++++++++++-------- src/components/charts/radar/Radar.js | 14 ++ src/components/charts/radar/RadarMarkers.js | 144 ++++++++++------- src/components/markers/MarkersItem.js | 68 ++++++++ src/components/markers/index.js | 10 ++ src/index.js | 1 + src/lib/propertiesConverters.js | 4 +- 11 files changed, 307 insertions(+), 126 deletions(-) create mode 100644 src/components/markers/MarkersItem.js create mode 100644 src/components/markers/index.js diff --git a/src/Nivo.js b/src/Nivo.js index 4251a056e..c807f8073 100644 --- a/src/Nivo.js +++ b/src/Nivo.js @@ -37,6 +37,10 @@ export const defaultTheme = { strokeWidth: 1, strokeDasharray: '', }, + markers: { + textColor: '#000', + fontSize: '11px', + }, } export default { diff --git a/src/components/charts/SvgWrapper.js b/src/components/charts/SvgWrapper.js index 1d9ee7877..c81de75b1 100644 --- a/src/components/charts/SvgWrapper.js +++ b/src/components/charts/SvgWrapper.js @@ -1,3 +1,11 @@ +/* + * 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' const SvgWrapper = ({ width, height, margin, children }) => diff --git a/src/components/charts/bubble/Bubble.js b/src/components/charts/bubble/Bubble.js index d207d20ba..c6db88590 100644 --- a/src/components/charts/bubble/Bubble.js +++ b/src/components/charts/bubble/Bubble.js @@ -8,7 +8,7 @@ */ import React, { Component } from 'react' import _ from 'lodash' -import { convertLabel } from '../../../lib/propertiesConverters' +import { getLabelGenerator } from '../../../lib/propertiesConverters' import { bubblePropTypes, bubbleDefaultProps } from './BubbleProps' import BubblePlaceholders from './BubblePlaceholders' import { getColorGenerator } from '../../../lib/colorUtils' @@ -23,7 +23,7 @@ const createNodes = ({ labelTextColor, labelTextDY, }) => { - const label = convertLabel(_label, labelFormat) + const label = getLabelGenerator(_label, labelFormat) const borderColorFn = getColorGenerator(borderColor) const textColorFn = getColorGenerator(labelTextColor) diff --git a/src/components/charts/line/Line.js b/src/components/charts/line/Line.js index ca9a41596..d088359c4 100644 --- a/src/components/charts/line/Line.js +++ b/src/components/charts/line/Line.js @@ -57,6 +57,10 @@ const Line = ({ markersColor, markersBorderWidth, markersBorderColor, + enableMarkersLabel, + markersLabel, + markersLabelFormat, + markersLabelYOffset, // theming theme, @@ -89,6 +93,11 @@ const Line = ({ color={getInheritedColorGenerator(markersColor)} borderWidth={markersBorderWidth} borderColor={getInheritedColorGenerator(markersBorderColor)} + enableLabel={enableMarkersLabel} + label={markersLabel} + labelFormat={markersLabelFormat} + labelYOffset={markersLabelYOffset} + theme={theme} {...motionProps} /> ) @@ -193,6 +202,7 @@ Line.propTypes = { markersColor: PropTypes.any.isRequired, markersBorderWidth: PropTypes.number.isRequired, markersBorderColor: PropTypes.any.isRequired, + enableMarkersLabel: PropTypes.bool.isRequired, // theming theme: PropTypes.object.isRequired, @@ -223,6 +233,7 @@ export const LineDefaultProps = { markersColor: 'inherit', markersBorderWidth: 0, markersBorderColor: 'inherit', + enableMarkersLabel: false, // theming theme: {}, diff --git a/src/components/charts/line/LineMarkers.js b/src/components/charts/line/LineMarkers.js index 89db8a787..f20d6535c 100644 --- a/src/components/charts/line/LineMarkers.js +++ b/src/components/charts/line/LineMarkers.js @@ -10,6 +10,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { TransitionMotion, spring } from 'react-motion' import { motionPropTypes } from '../../../props' +import { getLabelGenerator } from '../../../lib/propertiesConverters' +import MarkersItem from '../../markers/MarkersItem' export default class LineMarkers extends Component { static propTypes = { @@ -22,13 +24,29 @@ export default class LineMarkers extends Component { color: PropTypes.func.isRequired, borderWidth: PropTypes.number.isRequired, borderColor: PropTypes.func.isRequired, + + // labels + enableLabel: PropTypes.bool.isRequired, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, + labelFormat: PropTypes.string, + labelYOffset: PropTypes.number, + + // theming + theme: PropTypes.shape({ + markers: PropTypes.shape({ + textColor: PropTypes.string.isRequired, + fontSize: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + + // motion ...motionPropTypes, } static defaultProps = { - animate: true, - motionStiffness: 90, - motionDamping: 15, + // labels + enableLabel: false, + label: 'y', } render() { @@ -38,84 +56,103 @@ export default class LineMarkers extends Component { color, borderWidth, borderColor, + + // labels + enableLabel, + label, + labelFormat, + labelYOffset, + + // theming + theme, + + // motion animate, motionStiffness, motionDamping, } = this.props + const getLabel = getLabelGenerator(label, labelFormat) + const points = lines.reduce((acc, line) => { const { id, points } = line return [ ...acc, - ...points.map(point => ({ - key: `${id}.${point.x}`, - x: point.x, - y: point.y, - fill: color(line), - stroke: borderColor(line), - })), + ...points.map(point => { + const pointData = { + serie: { id }, + x: point.key, + y: point.value, + } + + return { + key: `${id}.${point.x}`, + x: point.x, + y: point.y, + fill: color(line), + stroke: borderColor(line), + label: enableLabel ? getLabel(pointData) : null, + } + }), ] }, []) - let circles - if (animate === true) { - const springConfig = { - motionDamping, - motionStiffness, - } - - circles = ( - { - return { - key: point.key, - data: { - fill: point.fill, - stroke: point.stroke, - }, - style: { - x: spring(point.x, springConfig), - y: spring(point.y, springConfig), - size: spring(size, springConfig), - }, - } - })} - > - {interpolatedStyles => - - {interpolatedStyles.map(({ key, style, data }) => - - )} - } - - ) - } else { - circles = points.map(point => - + if (animate !== true) { + return ( + + {points.map(point => + + )} + ) } + const springConfig = { + motionDamping, + motionStiffness, + } return ( - - {circles} - + { + return { + key: point.key, + data: point, + style: { + x: spring(point.x, springConfig), + y: spring(point.y, springConfig), + size: spring(size, springConfig), + }, + } + })} + > + {interpolatedStyles => + + {interpolatedStyles.map(({ key, style, data: point }) => + + )} + } + ) } } diff --git a/src/components/charts/radar/Radar.js b/src/components/charts/radar/Radar.js index 744539376..434989a6f 100644 --- a/src/components/charts/radar/Radar.js +++ b/src/components/charts/radar/Radar.js @@ -53,6 +53,10 @@ const Radar = ({ markersColor, markersBorderWidth, markersBorderColor, + enableMarkersLabel, + markersLabel, + markersLabelFormat, + markersLabelYOffset, // theming theme, @@ -94,6 +98,7 @@ const Radar = ({ /> {enableMarkers && } @@ -140,6 +150,10 @@ Radar.propTypes = { markersColor: PropTypes.any, markersBorderWidth: PropTypes.number, markersBorderColor: PropTypes.any, + enableMarkersLabel: PropTypes.bool, + markersLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + markersLabelFormat: PropTypes.string, + markersLabelYOffset: PropTypes.number, // theming theme: PropTypes.object.isRequired, diff --git a/src/components/charts/radar/RadarMarkers.js b/src/components/charts/radar/RadarMarkers.js index 7f7cf3dd8..4b9b91ae4 100644 --- a/src/components/charts/radar/RadarMarkers.js +++ b/src/components/charts/radar/RadarMarkers.js @@ -12,9 +12,13 @@ import { TransitionMotion, spring } from 'react-motion' import { motionPropTypes } from '../../../props' import { getInheritedColorGenerator } from '../../../lib/colorUtils' import { positionFromAngle } from '../../../lib/arcUtils' +import { getLabelGenerator } from '../../../lib/propertiesConverters' +import MarkersItem from '../../markers/MarkersItem' export default class RadarMarkers extends Component { static propTypes = { + facets: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])) + .isRequired, data: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -28,6 +32,21 @@ export default class RadarMarkers extends Component { color: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, borderWidth: PropTypes.number.isRequired, borderColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, + + // labels + enableLabel: PropTypes.bool.isRequired, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, + labelFormat: PropTypes.string, + labelYOffset: PropTypes.number, + + // theming + theme: PropTypes.shape({ + markers: PropTypes.shape({ + textColor: PropTypes.string.isRequired, + fontSize: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + ...motionPropTypes, } @@ -37,13 +56,14 @@ export default class RadarMarkers extends Component { borderWidth: 0, borderColor: 'inherit', - animate: true, - motionStiffness: 90, - motionDamping: 15, + // labels + enableLabel: false, + label: 'value', } render() { const { + facets, data, radiusScale, angleStep, @@ -52,6 +72,15 @@ export default class RadarMarkers extends Component { borderWidth, borderColor, + // labels + enableLabel, + label, + labelFormat, + labelYOffset, + + // theming + theme, + // motion animate, motionStiffness, @@ -60,6 +89,7 @@ export default class RadarMarkers extends Component { const fillColor = getInheritedColorGenerator(color) const strokeColor = getInheritedColorGenerator(borderColor) + const getLabel = getLabelGenerator(label, labelFormat) const points = data.reduce((acc, serie) => { const { id, data: serieData } = serie @@ -67,74 +97,74 @@ export default class RadarMarkers extends Component { return [ ...acc, ...serieData.map((d, i) => { + const pointData = { value: d, serie, facet: facets[i] } + return { key: `${id}.${i}`, fill: fillColor(serie), stroke: strokeColor(serie), ...positionFromAngle(angleStep * i - Math.PI / 2, radiusScale(d)), + ...pointData, + label: enableLabel ? getLabel(pointData) : null, } }), ] }, []) - let circles - if (animate === true) { - const springConfig = { - motionDamping, - motionStiffness, - } - - circles = ( - { - return { - key: point.key, - data: { - fill: point.fill, - stroke: point.stroke, - }, - style: { - x: spring(point.x, springConfig), - y: spring(point.y, springConfig), - size: spring(size, springConfig), - }, - } - })} - > - {interpolatedStyles => - - {interpolatedStyles.map(({ key, style, data }) => - - )} - } - - ) - } else { - circles = points.map(point => - + if (animate !== true) { + return ( + + {points.map(point => + + )} + ) } + const springConfig = { + motionDamping, + motionStiffness, + } + return ( - - {circles} - + ({ + key: point.key, + data: point, + style: { + x: spring(point.x, springConfig), + y: spring(point.y, springConfig), + size: spring(size, springConfig), + }, + }))} + > + {interpolatedStyles => + + {interpolatedStyles.map(({ key, style, data: point }) => + + )} + } + ) } } diff --git a/src/components/markers/MarkersItem.js b/src/components/markers/MarkersItem.js new file mode 100644 index 000000000..b78451d98 --- /dev/null +++ b/src/components/markers/MarkersItem.js @@ -0,0 +1,68 @@ +/* + * 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' + +const MarkersItem = ({ + x, + y, + size, + color, + borderWidth, + borderColor, + label, + labelTextAnchor, + labelYOffset, + theme, +}) => { + return ( + + + {label && + + {label} + } + + ) +} + +MarkersItem.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, + borderWidth: PropTypes.number.isRequired, + borderColor: PropTypes.string.isRequired, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + labelTextAnchor: PropTypes.oneOf(['start', 'middle', 'end']), + labelYOffset: PropTypes.number.isRequired, + theme: PropTypes.shape({ + markers: PropTypes.shape({ + textColor: PropTypes.string.isRequired, + fontSize: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, +} + +export const MarkersItemDefaultProps = { + labelTextAnchor: 'middle', + labelYOffset: -12, +} + +MarkersItem.defaultProps = MarkersItemDefaultProps + +export default pure(MarkersItem) diff --git a/src/components/markers/index.js b/src/components/markers/index.js new file mode 100644 index 000000000..6229ddbb5 --- /dev/null +++ b/src/components/markers/index.js @@ -0,0 +1,10 @@ +/* + * 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 { default as MarkersItem } from './MarkersItem' +export * from './MarkersItem' diff --git a/src/index.js b/src/index.js index a96c81169..4f7a09171 100644 --- a/src/index.js +++ b/src/index.js @@ -20,4 +20,5 @@ export * from './components/charts/radar' export * from './components/charts/treemap' export * from './components/charts/voronoi' export * from './components/axes' +export * from './components/markers' export * from './props' diff --git a/src/lib/propertiesConverters.js b/src/lib/propertiesConverters.js index ffedc8f25..2b3c8b6ff 100644 --- a/src/lib/propertiesConverters.js +++ b/src/lib/propertiesConverters.js @@ -10,7 +10,7 @@ import _ from 'lodash' import { format } from 'd3-format' -export const convertLabel = (_label, labelFormat) => { +export const getLabelGenerator = (_label, labelFormat) => { if (_.isFunction(_label)) { return _label } @@ -33,8 +33,6 @@ export const convertLabel = (_label, labelFormat) => { } } -export const getLabelGenerator = convertLabel - export const convertGetter = _property => { if (_.isFunction(_property)) { return _property