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): add TimeRange component and storybook #1503

Merged
merged 9 commits into from
May 29, 2021
Merged
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
69 changes: 63 additions & 6 deletions packages/calendar/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ declare module '@nivo/calendar' {
}

export interface ColorScale {
(value: number | { valueOf(): number }): Range
(value: number | { valueOf(): number }): any[]
ticks(count?: number): number[]
}

Expand All @@ -66,10 +66,10 @@ declare module '@nivo/calendar' {
yearLegendOffset: number
yearLegendPosition: 'before' | 'after'

monthLegend: (year: number, month: number, date: Date) => string | number
monthSpacing: number
monthBorderWidth: number
monthBorderColor: string
monthLegend: (year: number, month: number, date: Date) => string | number
monthLegendOffset: number
monthLegendPosition: 'before' | 'after'

Expand Down Expand Up @@ -102,8 +102,65 @@ declare module '@nivo/calendar' {
role: string
}>

export class Calendar extends React.Component<CalendarSvgProps & Dimensions> {}
export class ResponsiveCalendar extends React.Component<CalendarSvgProps> {}
export class CalendarCanvas extends React.Component<CalendarSvgProps & Dimensions> {}
export class ResponsiveCalendarCanvas extends React.Component<CalendarSvgProps> {}
export class Calendar extends React.Component<CalendarSvgProps & Dimensions> { }
export class ResponsiveCalendar extends React.Component<CalendarSvgProps> { }
export class CalendarCanvas extends React.Component<CalendarSvgProps & Dimensions> { }
export class ResponsiveCalendarCanvas extends React.Component<CalendarSvgProps> { }

export type TimeRangeCommonProps = Partial<{
minValue: 'auto' | number
maxValue: 'auto' | number
direction: CalendarDirection
colors: string[]
colorScale: ColorScale
margin: Box
square?: boolean
daySpacing: number
dayRadius: number
dayBorderWidth: number
dayBorderColor: string
emptyColor: string
isInteractive: boolean
onClick?: CalendarMouseHandler
onMouseMove?: CalendarMouseHandler
onMouseLeave?: CalendarMouseHandler
onMouseEnter?: CalendarMouseHandler
tooltip: React.FunctionComponent<CalendarDayData>
valueFormat?: string | ValueFormatter
legendFormat?: string | ValueFormatter
legends: CalendarLegend[]
theme: Theme
weekdayLegendsOffset: number
monthLegend: (year: number, month: number, date: Date) => string | number
monthLegendOffset: number
monthLegendPosition: 'before' | 'after'
}>

export interface TimeRangeDatum {
day: string
date: Date
value: number
}

export interface TimeRangeData {
from: Date
to: Date
data: TimeRangeDatum[]
}
export type TimeRangeProps = TimeRangeData &
TimeRangeCommonProps &
Partial<{
onClick: (datum: CalendarDayData, event: React.MouseEvent<SVGRectElement>) => void
role: string
}> &
Dimensions

export type TimeRangeSvgProps = TimeRangeData &
TimeRangeCommonProps &
Partial<{
onClick: (datum: CalendarDayData, event: React.MouseEvent<SVGRectElement>) => void
role: string
}>

export class ResponsiveTimeRange extends React.Component<TimeRangeSvgProps> { }
}
11 changes: 11 additions & 0 deletions packages/calendar/src/ResponsiveTimeRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { ResponsiveWrapper } from '@nivo/core'
import TimeRange from './TimeRange'

const ResponsiveTimeRange = props => (
<ResponsiveWrapper>
{({ width, height }) => <TimeRange width={width} height={height} {...props} />}
</ResponsiveWrapper>
)

export default ResponsiveTimeRange
181 changes: 181 additions & 0 deletions packages/calendar/src/TimeRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React from 'react'
import { timeFormat } from 'd3-time-format'

import { SvgWrapper, withContainer, useValueFormatter, useTheme, useDimensions } from '@nivo/core'
import { BoxLegendSvg } from '@nivo/legends'

import {
Direction,
computeWeekdays,
computeCellSize,
computeCellPositions,
computeMonthLegends,
} from './compute-timeRange'

import { useMonthLegends, useColorScale } from './hooks'
import TimeRangeDay from './TimeRangeDay'
import CalendarTooltip from './CalendarTooltip'
import CalendarMonthLegends from './CalendarMonthLegends'

const monthLabelFormat = timeFormat('%b')

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

square,
colors = ['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560'],
colorScale,
data,
direction = Direction.HORIZONTAL,
minValue = 0,
maxValue = 'auto',
valueFormat,
legendFormat,
role,
tooltip = CalendarTooltip,
onClick,
onMouseEnter,
onMouseLeave,
onMouseMove,
isInteractive = true,
legends = [],
dayBorderColor = '#fff',
dayBorderWidth = 0,
dayRadius = 0,
daySpacing,
daysInRange,
weekdayLegendsOffset,
monthLegend = (_year, _month, date) => monthLabelFormat(date),
monthLegendOffset = 0,
monthLegendPosition = 'before',
}) => {
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
width,
height,
partialMargin
)

const theme = useTheme()
const colorScaleFn = useColorScale({ data, minValue, maxValue, colors, colorScale })

const { cellHeight, cellWidth } = computeCellSize({
square,
offset: weekdayLegendsOffset,
totalDays: data.length + data[0].date.getDay(),
width: innerWidth,
height: innerHeight,
daySpacing,
direction,
})

const days = computeCellPositions({
offset: weekdayLegendsOffset,
daysInRange,
colorScale: colorScaleFn,
cellHeight,
cellWidth,
data,
direction,
daySpacing,
})

// map the days and reduce the month
const months = Object.values(
computeMonthLegends({
daySpacing,
direction,
cellHeight,
cellWidth,
days,
daysInRange,
}).months
)

const weekdayLegends = computeWeekdays({
direction,
cellHeight,
cellWidth,
daySpacing,
})

const monthLegends = useMonthLegends({
months,
direction,
monthLegendPosition,
monthLegendOffset,
})

const formatValue = useValueFormatter(valueFormat)
const formatLegend = useValueFormatter(legendFormat)

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
role={role}
theme={theme}
>
{weekdayLegends.map(legend => (
<text
key={legend.value}
transform={`translate(${legend.x},${legend.y}) rotate(${legend.rotation})`}
textAnchor="left"
style={theme.labels.text}
>
{legend.value}
</text>
))}
{days.map(d => {
return (
<TimeRangeDay
key={d.day.toString()}
data={d}
x={d.coordinates.x}
rx={dayRadius}
y={d.coordinates.y}
ry={dayRadius}
spacing={daySpacing}
width={cellWidth}
height={cellHeight}
color={d.color}
borderWidth={dayBorderWidth}
borderColor={dayBorderColor}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
isInteractive={isInteractive}
tooltip={tooltip}
theme={theme}
onClick={onClick}
formatValue={formatValue}
/>
)
})}
<CalendarMonthLegends months={monthLegends} legend={monthLegend} theme={theme} />

{legends?.map((legend, i) => {
const legendData = colorScaleFn.ticks(legend.itemCount).map(value => ({
id: value,
label: formatLegend(value),
color: colorScaleFn(value),
}))

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

export default withContainer(TimeRange)
Loading