Skip to content

Commit

Permalink
feat(calendar): add ability to define custom tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte authored and Raphaël Benitte committed Jun 4, 2018
1 parent d3b8951 commit 7a076bf
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 31 deletions.
1 change: 1 addition & 0 deletions packages/nivo-calendar/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ declare module '@nivo/calendar' {

// tooltip
tooltipFormat: (value: number) => string | number
tooltip: React.StatelessComponent<CalendarDayData>

// legends
legends: CalendarLegend[]
Expand Down
2 changes: 2 additions & 0 deletions packages/nivo-calendar/src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const Calendar = ({
// interactivity
isInteractive,
tooltipFormat,
tooltip,
onClick,

legends,
Expand Down Expand Up @@ -89,6 +90,7 @@ const Calendar = ({
showTooltip={showTooltip}
hideTooltip={hideTooltip}
tooltipFormat={tooltipFormat}
tooltip={tooltip}
theme={theme}
onClick={onClick}
/>
Expand Down
10 changes: 8 additions & 2 deletions packages/nivo-calendar/src/CalendarDay.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ CalendarDay.propTypes = {
borderColor: PropTypes.string.isRequired,

tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
tooltip: PropTypes.func,
showTooltip: PropTypes.func.isRequired,
hideTooltip: PropTypes.func.isRequired,

Expand All @@ -66,8 +67,8 @@ const enhance = compose(
onClick: event => onClick(data, event),
})),
withPropsOnChange(
['data', 'color', 'showTooltip', 'theme', 'tooltipFormat'],
({ data, color, showTooltip, theme, tooltipFormat }) => {
['data', 'color', 'showTooltip', 'tooltipFormat', 'tooltip', 'theme'],
({ data, color, showTooltip, tooltipFormat, tooltip, theme }) => {
if (data.value === undefined) return { showTooltip: noop }

return {
Expand All @@ -80,6 +81,11 @@ const enhance = compose(
color={color}
theme={theme}
format={tooltipFormat}
renderContent={
typeof tooltip === 'function'
? tooltip.bind(null, { color, ...data })
: null
}
/>,
event
),
Expand Down
1 change: 1 addition & 0 deletions packages/nivo-calendar/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const CalendarPropTypes = {
isInteractive: PropTypes.bool,
onClick: PropTypes.func.isRequired,
tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
tooltip: PropTypes.func,

legends: PropTypes.arrayOf(
PropTypes.shape({
Expand Down
21 changes: 21 additions & 0 deletions packages/nivo-calendar/stories/calendar.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,24 @@ stories.add(
'vertical calendar',
withInfo()(() => <Calendar direction="vertical" {...commonProps} height={600} />)
)

stories.add(
'custom tooltip',
withInfo()(() => (
<Calendar
tooltip={({ day, value, color }) => (
<strong style={{ color }}>
{day}: {value}
</strong>
)}
theme={{
tooltip: {
container: {
background: '#333',
},
},
}}
{...commonProps}
/>
))
)
103 changes: 74 additions & 29 deletions website/src/components/charts/calendar/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import generateCode from '../../../lib/generateChartCode'
import CalendarControls from './CalendarControls'
import { ResponsiveCalendar, CalendarDefaultProps } from '@nivo/calendar'
import ComponentPropsDocumentation from '../../properties/ComponentPropsDocumentation'
import nivoTheme from '../../../nivoTheme'
import properties from './props'
import propsMapper from './propsMapper'
import config from '../../../config'

const Tooltip = data => {
/* return custom tooltip */
}

export default class Calendar extends Component {
state = {
Expand Down Expand Up @@ -52,6 +59,8 @@ export default class Calendar extends Component {

// interactivity
isInteractive: true,
'custom tooltip example': false,
tooltip: null,

legends: [
{
Expand All @@ -64,6 +73,8 @@ export default class Calendar extends Component {
itemDirection: 'top-to-bottom',
},
],

theme: nivoTheme,
},
}

Expand All @@ -79,26 +90,79 @@ export default class Calendar extends Component {
const { data } = this.props
const { settings } = this.state

const code = generateCode('ResponsiveCalendar', settings, {
pkg: '@nivo/calendar',
defaults: CalendarDefaultProps,
})
const mappedSettings = propsMapper(settings)

const code = generateCode(
'ResponsiveCalendar',
{
...mappedSettings,
tooltip: mappedSettings.tooltip ? Tooltip : undefined,
},
{
pkg: '@nivo/calendar',
defaults: CalendarDefaultProps,
}
)

const header = (
<ChartHeader chartClass="Calendar" tags={['calendar', 'react', 'isomorphic']} />
)

const description = (
<div className="chart-description">
<p className="description">
This component is heavily inspired by{' '}
<a
href="https://bl.ocks.org/mbostock/4063318"
target="_blank"
rel="noopener noreferrer"
>
this block
</a>.
</p>
<p>
This component is suitable for isomorphic rendering but require to use the{' '}
<code>Calendar</code> component not the <code>ResponsiveCalendar</code> one.
</p>
<p className="description">
This component is available in the{' '}
<a
href="https://github.com/plouc/nivo-api"
target="_blank"
rel="noopener noreferrer"
>
nivo-api
</a>, you can <Link to="/calendar/api">try it using the API client</Link>. You
can also see more example usages in{' '}
<a
href={`${config.storybookUrl}?selectedKind=Calendar&selectedStory=default`}
target="_blank"
rel="noopener noreferrer"
>
the storybook
</a>.
</p>
<p className="description">
See the <Link to="/guides/legends">dedicated guide</Link> on how to setup
legends for this component.
</p>
</div>
)

return (
<div className="page_content grid">
<div className="chart-page_main">
<MediaQuery query="(max-width: 1000px)">{header}</MediaQuery>
<MediaQuery query="(max-width: 1000px)">
{header}
{description}
</MediaQuery>
<ChartTabs chartClass="calendar" code={code} data={data}>
<ResponsiveCalendar
from={settings.from}
to={settings.to}
data={data}
onClick={this.handleNodeClick}
{...settings}
{...mappedSettings}
/>
</ChartTabs>
<CalendarControls
Expand All @@ -109,29 +173,10 @@ export default class Calendar extends Component {
<ComponentPropsDocumentation chartClass="Calendar" properties={properties} />
</div>
<div className="chart-page_aside">
<MediaQuery query="(min-width: 1000px)">{header}</MediaQuery>
<p className="description">
This component is heavily inspired by{' '}
<a
href="https://bl.ocks.org/mbostock/4063318"
target="_blank"
rel="noopener noreferrer"
>
this block
</a>.
</p>
<p className="description">
This component renders the calendar using d3 only for computing positions.
DOM mutations are managed by React.
</p>
<p>
This component is suitable for isomorphic rendering but require to use the{' '}
<code>Calendar</code> component not the <code>ResponsiveCalendar</code> one.
</p>
<p className="description">
See the <Link to="/guides/legends">dedicated guide</Link> on how to setup
legends for this component.
</p>
<MediaQuery query="(min-width: 1000px)">
{header}
{description}
</MediaQuery>
</div>
</div>
)
Expand Down
39 changes: 39 additions & 0 deletions website/src/components/charts/calendar/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,43 @@ export default [
type: '{Function}',
required: false,
},
{
key: 'custom tooltip example',
scopes: ['Calendar'],
excludeFromDoc: true,
description: (
<span>
You can customize the tooltip using the <code>tooltip</code> property and{' '}
<code>theme.tooltip</code> object.
</span>
),
type: '{boolean}',
controlType: 'switch',
controlGroup: 'Interactivity',
},
{
key: 'tooltip',
scopes: ['Calendar'],
type: '{Function}',
required: false,
description: (
<div>
A function allowing complete tooltip customisation, it must return a valid HTML
element and will receive the following data:
<pre className="code code-block">
{dedent`
{
day: {string},
date: {Date},
value: {number},
color: {string},
x: {number},
y: {number},
size: {number}
}
`}
</pre>
</div>
),
},
]
61 changes: 61 additions & 0 deletions website/src/components/charts/calendar/propsMapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 styled from 'styled-components'
import { settingsMapper } from '../../../lib/settings'

const TooltipWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 12px;
`
const TooltipKey = styled.span`
font-weight: 600;
`

const CustomTooltip = node => (
<TooltipWrapper style={{ color: node.color }}>
<TooltipKey>day</TooltipKey>
<span>{node.day}</span>
<TooltipKey>value</TooltipKey>
<span>{node.value}</span>
<TooltipKey>x</TooltipKey>
<span>{node.x}</span>
<TooltipKey>y</TooltipKey>
<span>{node.y}</span>
<TooltipKey>size</TooltipKey>
<span>{node.size}</span>
</TooltipWrapper>
)

export default settingsMapper(
{
theme: (value, values) => {
if (!values['custom tooltip example']) return value

return {
...values.theme,
tooltip: {
container: {
...values.theme.tooltip.container,
background: '#333',
},
},
}
},
tooltip: (value, values) => {
if (!values['custom tooltip example']) return null

return CustomTooltip
},
},
{
exclude: ['custom tooltip example'],
}
)

0 comments on commit 7a076bf

Please sign in to comment.