From 149e664ee8b2360353c1861637c6046624b7e186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Benitte?= Date: Tue, 5 Dec 2017 06:25:07 +0900 Subject: [PATCH] feat(calendar): add support for tooltip --- src/components/charts/calendar/Calendar.js | 112 ++++++++++++----- src/components/charts/calendar/CalendarDay.js | 116 +++++++++++------- .../charts/calendar/CalendarMonthPath.js | 40 +++--- .../charts/calendar/StaticCalendar.js | 105 ---------------- src/components/charts/calendar/enhance.js | 24 +++- src/components/charts/calendar/props.js | 11 ++ src/lib/charts/calendar/CalendarLayout.js | 1 + 7 files changed, 207 insertions(+), 202 deletions(-) delete mode 100644 src/components/charts/calendar/StaticCalendar.js diff --git a/src/components/charts/calendar/Calendar.js b/src/components/charts/calendar/Calendar.js index dbb73c0bd..c71c6d9fe 100644 --- a/src/components/charts/calendar/Calendar.js +++ b/src/components/charts/calendar/Calendar.js @@ -7,22 +7,24 @@ * file that was distributed with this source code. */ import React from 'react' -import { minBy, maxBy } from 'lodash' -import { scaleQuantize } from 'd3-scale' import computeCalendar from '../../../lib/charts/calendar/CalendarLayout' import { CalendarPropTypes } from './props' -import StaticCalendar from './StaticCalendar' +import { timeFormat } from 'd3-time-format' +import { DIRECTION_HORIZONTAL } from '../../../constants/directions' +import CalendarDay from './CalendarDay' +import CalendarMonthPath from './CalendarMonthPath' import Container from '../Container' import SvgWrapper from '../SvgWrapper' import enhance from './enhance' +const monthLegendFormat = timeFormat('%b') + const Calendar = ({ data, from, to, - domain, - colors, + colorScale, // dimensions margin, @@ -31,7 +33,6 @@ const Calendar = ({ outerWidth, outerHeight, - onDayClick, direction, emptyColor, yearSpacing, @@ -44,18 +45,12 @@ const Calendar = ({ monthLegendOffset, theme, -}) => { - let colorDomain - if (domain === 'auto') { - colorDomain = [minBy(data, 'value').value, maxBy(data, 'value').value] - } else { - colorDomain = [...domain] - } - - const colorScale = scaleQuantize() - .domain(colorDomain) - .range(colors) + // interactivity + isInteractive, + tooltipFormat, + onClick, +}) => { const { years, months, days } = computeCalendar({ width, height, @@ -70,22 +65,77 @@ const Calendar = ({ }) return ( - + {({ showTooltip, hideTooltip }) => ( - + {days.map(d => ( + + ))} + {months.map(m => ( + + ))} + {months.map(month => { + let transform + if (direction === DIRECTION_HORIZONTAL) { + transform = `translate(${month.bbox.x + month.bbox.width / 2},${month + .bbox.y - monthLegendOffset})` + } else { + transform = `translate(${month.bbox.x - monthLegendOffset},${month.bbox + .y + + month.bbox.height / 2}) rotate(-90)` + } + + return ( + + {monthLegendFormat(month.date)} + + ) + })} + {years.map(year => { + let transform + if (direction === DIRECTION_HORIZONTAL) { + transform = `translate(${year.bbox.x - yearLegendOffset},${year.bbox.y + + year.bbox.height / 2}) rotate(-90)` + } else { + transform = `translate(${year.bbox.x + year.bbox.width / 2},${year.bbox + .y - yearLegendOffset})` + } + + return ( + + {year.year} + + ) + })} )} diff --git a/src/components/charts/calendar/CalendarDay.js b/src/components/charts/calendar/CalendarDay.js index 72fcdc80b..5186fe550 100644 --- a/src/components/charts/calendar/CalendarDay.js +++ b/src/components/charts/calendar/CalendarDay.js @@ -6,53 +6,87 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { Component } from 'react' +import React from 'react' import PropTypes from 'prop-types' +import compose from 'recompose/compose' +import withPropsOnChange from 'recompose/withPropsOnChange' +import pure from 'recompose/pure' +import noop from '../../../lib/noop' +import BasicTooltip from '../../tooltip/BasicTooltip' -class CalendarDay extends Component { - constructor(props) { - super(props) +const CalendarDay = ({ + x, + y, + size, + color, + borderWidth, + borderColor, + onClick, + showTooltip, + hideTooltip, +}) => ( + +) - this.handleClick = this.handleClick.bind(this) - } - - handleClick() { - const { onClick, data } = this.props - onClick(data) - } +CalendarDay.propTypes = { + onClick: PropTypes.func.isRequired, + data: PropTypes.object.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, + borderWidth: PropTypes.number.isRequired, + borderColor: PropTypes.string.isRequired, - render() { - const { x, y, size, color, borderWidth, borderColor } = this.props + tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + showTooltip: PropTypes.func.isRequired, + hideTooltip: PropTypes.func.isRequired, - return ( - - ) - } + theme: PropTypes.shape({ + tooltip: PropTypes.shape({}).isRequired, + }).isRequired, } -const { number, string, object, func } = PropTypes +const enhance = compose( + withPropsOnChange(['data', 'onClick'], ({ data, onClick }) => ({ + onClick: event => onClick(data, event), + })), + withPropsOnChange( + ['data', 'color', 'showTooltip', 'theme', 'tooltipFormat'], + ({ data, color, showTooltip, theme, tooltipFormat }) => { + if (data.value === undefined) return { showTooltip: noop } -CalendarDay.propTypes = { - onClick: func.isRequired, - data: object.isRequired, - x: number.isRequired, - y: number.isRequired, - size: number.isRequired, - color: string.isRequired, - borderWidth: number.isRequired, - borderColor: string.isRequired, -} + return { + showTooltip: event => + showTooltip( + , + event + ), + } + } + ), + pure +) -export default CalendarDay +export default enhance(CalendarDay) diff --git a/src/components/charts/calendar/CalendarMonthPath.js b/src/components/charts/calendar/CalendarMonthPath.js index 09e817910..dad14c21f 100644 --- a/src/components/charts/calendar/CalendarMonthPath.js +++ b/src/components/charts/calendar/CalendarMonthPath.js @@ -6,33 +6,25 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { Component } from 'react' +import React from 'react' +import pure from 'recompose/pure' import PropTypes from 'prop-types' -class CalendarMonthPath extends Component { - render() { - const { path, borderWidth, borderColor } = this.props - - return ( - - ) - } -} - -const { number, string } = PropTypes +const CalendarMonthPath = ({ path, borderWidth, borderColor }) => ( + +) CalendarMonthPath.propTypes = { - path: string.isRequired, - borderWidth: number.isRequired, - borderColor: string.isRequired, + path: PropTypes.string.isRequired, + borderWidth: PropTypes.number.isRequired, + borderColor: PropTypes.string.isRequired, } -export default CalendarMonthPath +export default pure(CalendarMonthPath) diff --git a/src/components/charts/calendar/StaticCalendar.js b/src/components/charts/calendar/StaticCalendar.js deleted file mode 100644 index e7cbcd8c7..000000000 --- a/src/components/charts/calendar/StaticCalendar.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, { Component } from 'react' -import { timeFormat } from 'd3-time-format' -import { DIRECTION_HORIZONTAL } from '../../../constants/directions' -import CalendarDay from './CalendarDay' -import CalendarMonthPath from './CalendarMonthPath' - -class StaticCalendar extends Component { - render() { - const { - onDayClick, - years, - months, - days, - direction, - yearLegendOffset, - dayBorderWidth, - dayBorderColor, - monthBorderWidth, - monthBorderColor, - monthLegendOffset, - } = this.props - - const monthLegendFormat = timeFormat('%b') - - return ( - - {days.map(d => ( - - ))} - {months.map(m => ( - - ))} - {months.map(month => { - let transform - if (direction === DIRECTION_HORIZONTAL) { - transform = `translate(${month.bbox.x + month.bbox.width / 2},${month.bbox - .y - monthLegendOffset})` - } else { - transform = `translate(${month.bbox.x - monthLegendOffset},${month.bbox.y + - month.bbox.height / 2}) rotate(-90)` - } - - return ( - - {monthLegendFormat(month.date)} - - ) - })} - {years.map(year => { - let transform - if (direction === DIRECTION_HORIZONTAL) { - transform = `translate(${year.bbox.x - yearLegendOffset},${year.bbox.y + - year.bbox.height / 2}) rotate(-90)` - } else { - transform = `translate(${year.bbox.x + year.bbox.width / 2},${year.bbox.y - - yearLegendOffset})` - } - - return ( - - {year.year} - - ) - })} - - ) - } -} - -StaticCalendar.propTypes = {} - -export default StaticCalendar diff --git a/src/components/charts/calendar/enhance.js b/src/components/charts/calendar/enhance.js index 669f6b9e6..394944c65 100644 --- a/src/components/charts/calendar/enhance.js +++ b/src/components/charts/calendar/enhance.js @@ -9,9 +9,31 @@ import React from 'react' import compose from 'recompose/compose' import defaultProps from 'recompose/defaultProps' +import withPropsOnChange from 'recompose/withPropsOnChange' import pure from 'recompose/pure' +import { minBy, maxBy } from 'lodash' +import { scaleQuantize } from 'd3-scale' import { withTheme, withDimensions } from '../../../hocs' import { CalendarDefaultProps } from './props' export default Component => - compose(defaultProps(CalendarDefaultProps), withTheme(), withDimensions(), pure)(Component) + compose( + defaultProps(CalendarDefaultProps), + withTheme(), + withDimensions(), + withPropsOnChange(['data', 'domain', 'colors'], ({ data, domain, colors }) => { + let colorDomain + if (domain === 'auto') { + colorDomain = [minBy(data, 'value').value, maxBy(data, 'value').value] + } else { + colorDomain = [...domain] + } + + const colorScale = scaleQuantize() + .domain(colorDomain) + .range(colors) + + return { colorScale } + }), + pure + )(Component) diff --git a/src/components/charts/calendar/props.js b/src/components/charts/calendar/props.js index 1e3d94b5a..66654b960 100644 --- a/src/components/charts/calendar/props.js +++ b/src/components/charts/calendar/props.js @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ import PropTypes from 'prop-types' +import noop from '../../../lib/noop' import { DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from '../../../constants/directions' /** @@ -27,6 +28,7 @@ export const CalendarPropTypes = { domain: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.arrayOf(PropTypes.number)]) .isRequired, colors: PropTypes.arrayOf(PropTypes.string).isRequired, + colorScale: PropTypes.func.isRequired, onDayClick: PropTypes.func.isRequired, direction: PropTypes.oneOf([DIRECTION_HORIZONTAL, DIRECTION_VERTICAL]), @@ -45,6 +47,11 @@ export const CalendarPropTypes = { daySpacing: PropTypes.number.isRequired, dayBorderWidth: PropTypes.number.isRequired, dayBorderColor: PropTypes.string.isRequired, + + // interactivity + isInteractive: PropTypes.bool, + onClick: PropTypes.func.isRequired, + tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), } /** @@ -73,4 +80,8 @@ export const CalendarDefaultProps = { daySpacing: 0, dayBorderWidth: 1, dayBorderColor: '#000', + + // interactivity + isInteractive: true, + onClick: noop, } diff --git a/src/lib/charts/calendar/CalendarLayout.js b/src/lib/charts/calendar/CalendarLayout.js index a2b7ae527..8bcd41f16 100644 --- a/src/lib/charts/calendar/CalendarLayout.js +++ b/src/lib/charts/calendar/CalendarLayout.js @@ -284,6 +284,7 @@ const CalendarLayout = ({ day.color = emptyColor data.forEach(dataDay => { if (dataDay.day === day.day) { + day.value = dataDay.value day.color = colorScale(dataDay.value) } })