Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(calendar): enhanced calendar with month granularity support #1173

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions packages/calendar/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ declare module '@nivo/calendar' {
ticks(count?: number): number[]
}

export type YearLegend = Partial<{
yearLegend: (year: number) => string | number
yearSpacing: number
yearLegendOffset: number
yearLegendPosition: 'before' | 'after'
}>

export interface BlockLegendData {
firstMonth: number
firstYear: number
lastMonth: number
lastYear: number
}

export type BlockLegend = Partial<{
blockLegend: (block: BlockLegendData) => string | number
blockSpacing: number
blockLegendOffset: number
blockLegendPosition: 'before' | 'after'
}>

export type CalendarCommonProps = Partial<{
minValue: 'auto' | number
maxValue: 'auto' | number
Expand All @@ -61,11 +82,6 @@ declare module '@nivo/calendar' {
margin: Box
align: BoxAlign

yearLegend: (year: number) => string | number
yearSpacing: number
yearLegendOffset: number
yearLegendPosition: 'before' | 'after'

monthLegend: (year: number, month: number, date: Date) => string | number
monthSpacing: number
monthBorderWidth: number
Expand Down Expand Up @@ -95,15 +111,37 @@ declare module '@nivo/calendar' {
theme: Theme
}>

export type EnhancedCalendarCommonProps = Partial<{
weekDirection: CalendarDirection
breakpoint: number
granularity: 'year' | 'month'
}>

export type CalendarSvgProps = CalendarData &
CalendarCommonProps &
YearLegend &
Partial<{
onClick: (datum: CalendarDayData, event: React.MouseEvent<SVGRectElement>) => void
}>

export type EnhancedCalendarSvgProps = CalendarData &
CalendarCommonProps &
EnhancedCalendarCommonProps &
BlockLegend &
Partial<{
onClick: (datum: CalendarDayData, event: React.MouseEvent<SVGRectElement>) => void
role: string
}>

export class Calendar extends React.Component<CalendarSvgProps & Dimensions> {}
export class EnhancedCalendar extends React.Component<EnhancedCalendarSvgProps & Dimensions> {}
export class ResponsiveCalendar extends React.Component<CalendarSvgProps> {}
export class ResponsiveEnhancedCalendar extends React.Component<EnhancedCalendarSvgProps> {}
export class CalendarCanvas extends React.Component<CalendarSvgProps & Dimensions> {}
export class EnhancedCalendarCanvas extends React.Component<
EnhancedCalendarSvgProps & Dimensions
> {}
export class ResponsiveCalendarCanvas extends React.Component<CalendarSvgProps> {}
export class ResponsiveEnhancedCalendarCanvas extends React.Component<
ResponsiveEnhancedCalendarCanvas
> {}
}
10 changes: 8 additions & 2 deletions packages/calendar/src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { CalendarPropTypes, CalendarDefaultProps } from './props'
import CalendarYearLegends from './CalendarYearLegends'
import CalendarMonthPath from './CalendarMonthPath'
import CalendarMonthLegends from './CalendarMonthLegends'
import { useMonthLegends, useYearLegends, useCalendarLayout, useDays, useColorScale } from './hooks'
import { useCalendarLayout } from './hooks'
import { useMonthLegends, useBlockLegends, useDays, useColorScale } from './sharedHooks'
import CalendarDay from './CalendarDay'

const Calendar = ({
Expand Down Expand Up @@ -84,7 +85,12 @@ const Calendar = ({
monthLegendPosition,
monthLegendOffset,
})
const yearLegends = useYearLegends({ years, direction, yearLegendPosition, yearLegendOffset })
const yearLegends = useBlockLegends({
blocks: years,
direction,
blockLegendPosition: yearLegendPosition,
blockLegendOffset: yearLegendOffset,
})
const days = useDays({ days: rest.days, data, colorScale: colorScaleFn, emptyColor })
const formatLegend = useValueFormatter(legendFormat)
const formatValue = useValueFormatter(valueFormat)
Expand Down
44 changes: 44 additions & 0 deletions packages/calendar/src/CalendarBlockLegends.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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, { memo } from 'react'
import PropTypes from 'prop-types'

const CalendarBlockLegends = memo(({ blocks, legend, theme }) => {
return (
<>
{blocks.map(block => {
return (
<text
key={
String(block.firstYear) +
String(block.firstMonth) +
String(block.lastYear) +
String(block.lastMonth)
}
transform={`translate(${block.x},${block.y}) rotate(${block.rotation})`}
textAnchor="middle"
style={theme.labels.text}
>
{legend(block)}
</text>
)
})}
</>
)
})

CalendarBlockLegends.propTypes = {
blocks: PropTypes.array.isRequired,
legend: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
}

CalendarBlockLegends.displayName = 'CalendarBlockLegends'

export default CalendarBlockLegends
11 changes: 6 additions & 5 deletions packages/calendar/src/CalendarCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
} from '@nivo/core'
import { renderLegendToCanvas } from '@nivo/legends'
import { CalendarCanvasPropTypes, CalendarCanvasDefaultProps } from './props'
import { useCalendarLayout, useColorScale, useMonthLegends, useYearLegends, useDays } from './hooks'
import { useCalendarLayout } from './hooks'
import { useMonthLegends, useBlockLegends, useDays, useColorScale } from './sharedHooks'
import { useTooltip } from '@nivo/tooltip'

const findDayUnderCursor = (event, canvasEl, days, size, dayBorderWidth, margin) => {
Expand Down Expand Up @@ -105,11 +106,11 @@ const CalendarCanvas = memo(
monthLegendPosition,
monthLegendOffset,
})
const yearLegends = useYearLegends({
years,
const yearLegends = useBlockLegends({
blocks: years,
direction,
yearLegendPosition,
yearLegendOffset,
blockLegendPosition: yearLegendPosition,
blockLegendOffset: yearLegendOffset,
})
const days = useDays({ days: rest.days, data, colorScale: colorScaleFn, emptyColor })
const [currentDay, setCurrentDay] = useState(null)
Expand Down
8 changes: 2 additions & 6 deletions packages/calendar/src/CalendarDay.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,9 @@ const CalendarDay = memo(
hideTooltip()
onMouseLeave && onMouseLeave(data, event)
},
[isInteractive, hideTooltip, data, onMouseLeave]
[hideTooltip, data, onMouseLeave]
)
const handleClick = useCallback(event => onClick && onClick(data, event), [
isInteractive,
data,
onClick,
])
const handleClick = useCallback(event => onClick && onClick(data, event), [data, onClick])

return (
<rect
Expand Down
6 changes: 3 additions & 3 deletions packages/calendar/src/CalendarTooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ const CalendarTooltip = memo(({ value, day, color }) => {

CalendarTooltip.displayName = 'CalendarTooltip'
CalendarTooltip.propTypes = {
value: PropTypes.object.isRequired,
day: PropTypes.object.isRequired,
color: PropTypes.object.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
day: PropTypes.string,
color: PropTypes.string,
}

export default CalendarTooltip
172 changes: 172 additions & 0 deletions packages/calendar/src/EnhancedCalendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* 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 { SvgWrapper, useTheme, useDimensions, withContainer, useValueFormatter } from '@nivo/core'
import { BoxLegendSvg } from '@nivo/legends'

import CalendarBlockLegends from './CalendarBlockLegends'
import CalendarMonthPath from './CalendarMonthPath'
import CalendarMonthLegends from './CalendarMonthLegends'

import { useCalendarLayout } from './enhancedHooks'
import { useMonthLegends, useBlockLegends, useDays, useColorScale } from './sharedHooks'
import CalendarDay from './CalendarDay'
import { EnhancedCalendarPropTypes, EnhancedCalendarDefaultProps } from './enhancedProps'

const EnhancedCalendar = ({
margin: partialMargin,
width,
height,

align,
colors,
colorScale,
data,
direction,
emptyColor,
from,
to,
minValue,
maxValue,
valueFormat,
legendFormat,

blockLegend,
blockLegendOffset,
blockLegendPosition,
blockSpacing,

monthBorderColor,
monthBorderWidth,
monthLegend,
monthLegendOffset,
monthLegendPosition,
monthSpacing,

dayBorderColor,
dayBorderWidth,
daySpacing,

granularity,
weekDirection,
breakpoint,

isInteractive,
tooltip,
onClick,
onMouseEnter,
onMouseLeave,
onMouseMove,

legends,
role,
}) => {
const theme = useTheme()
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
width,
height,
partialMargin
)
let { months, blocks, days } = useCalendarLayout({
width: innerWidth,
height: innerHeight,
from,
to,
direction,
blockSpacing,
monthSpacing,
daySpacing,
align,
granularity,
weekDirection,
breakpoint,
})
colorScale = useColorScale({ data, minValue, maxValue, colors, colorScale })
const monthLegends = useMonthLegends({
months,
direction,
monthLegendPosition,
monthLegendOffset,
})
const blockLegends = useBlockLegends({
blocks,
direction,
blockLegendPosition,
blockLegendOffset,
})
days = useDays({ days, data, colorScale, emptyColor })
const formatLegend = useValueFormatter(legendFormat)
const formatValue = useValueFormatter(valueFormat)

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
theme={theme}
role={role}
>
{days.map(d => (
<CalendarDay
key={d.date.toString()}
data={d}
x={d.x}
y={d.y}
size={d.size}
spacing={daySpacing}
color={d.color}
borderWidth={dayBorderWidth}
borderColor={dayBorderColor}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
isInteractive={isInteractive}
tooltip={tooltip}
theme={theme}
onClick={onClick}
formatValue={formatValue}
/>
))}
{months.map(m => (
<CalendarMonthPath
key={m.date.toString()}
path={m.path}
borderWidth={monthBorderWidth}
borderColor={monthBorderColor}
/>
))}
<CalendarMonthLegends months={monthLegends} legend={monthLegend} theme={theme} />
<CalendarBlockLegends blocks={blockLegends} legend={blockLegend} theme={theme} />
{legends.map((legend, i) => {
const legendData = colorScale.ticks(legend.itemCount).map(value => ({
id: value,
label: formatLegend(value),
color: colorScale(value),
}))

return (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
theme={theme}
/>
)
})}
</SvgWrapper>
)
}

EnhancedCalendar.displayName = 'EnhancedCalendar'
EnhancedCalendar.defaultProps = EnhancedCalendarDefaultProps
EnhancedCalendar.propTypes = EnhancedCalendarPropTypes

export default withContainer(EnhancedCalendar)
Loading