Skip to content

Commit

Permalink
feat(timerange): add from, to, and emptyColor props (#1722)
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeary96 committed Aug 23, 2021
1 parent d24f23e commit d49a790
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 18 deletions.
20 changes: 15 additions & 5 deletions packages/calendar/src/TimeRange.tsx
@@ -1,15 +1,13 @@
import { useMemo } from 'react'

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

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

import { useMonthLegends, useColorScale } from './hooks'
import { TimeRangeDay } from './TimeRangeDay'
import { CalendarMonthLegends } from './CalendarMonthLegends'
Expand All @@ -24,6 +22,9 @@ const InnerTimeRange = ({
square = timeRangeDefaultProps.square,
colors = timeRangeDefaultProps.colors,
colorScale,
emptyColor = timeRangeDefaultProps.emptyColor,
from,
to,
data: _data,
direction = timeRangeDefaultProps.direction,
minValue = timeRangeDefaultProps.minValue,
Expand Down Expand Up @@ -70,10 +71,16 @@ const InnerTimeRange = ({
const theme = useTheme()
const colorScaleFn = useColorScale({ data, minValue, maxValue, colors, colorScale })

const totalDays = computeTotalDays({
from,
to,
data,
})

const { cellHeight, cellWidth } = computeCellSize({
square,
offset: weekdayLegendOffset,
totalDays: data.length + data[0].date.getDay(),
totalDays: totalDays,
width: innerWidth,
height: innerHeight,
daySpacing,
Expand All @@ -83,8 +90,11 @@ const InnerTimeRange = ({
const days = computeCellPositions({
offset: weekdayLegendOffset,
colorScale: colorScaleFn,
emptyColor,
cellHeight,
cellWidth,
from,
to,
data,
direction,
daySpacing,
Expand Down Expand Up @@ -134,7 +144,7 @@ const InnerTimeRange = ({
{days.map(d => {
return (
<TimeRangeDay
key={d.day.toString()}
key={d.date.toString()}
data={d}
x={d.coordinates.x}
rx={dayRadius}
Expand Down
12 changes: 12 additions & 0 deletions packages/calendar/src/TimeRangeDay.tsx
Expand Up @@ -26,6 +26,10 @@ export const TimeRangeDay = memo(

const handleMouseEnter = useCallback(
event => {
if (!('value' in data)) {
return
}

const formatedData = {
...data,
value: formatValue(data.value),
Expand All @@ -37,6 +41,10 @@ export const TimeRangeDay = memo(
)
const handleMouseMove = useCallback(
event => {
if (!('value' in data)) {
return
}

const formatedData = {
...data,
value: formatValue(data.value),
Expand All @@ -48,6 +56,10 @@ export const TimeRangeDay = memo(
)
const handleMouseLeave = useCallback(
event => {
if (!('value' in data)) {
return
}

hideTooltip()
onMouseLeave?.(data, event)
},
Expand Down
80 changes: 73 additions & 7 deletions packages/calendar/src/compute/timeRange.ts
@@ -1,4 +1,7 @@
import { timeWeek } from 'd3-time'
import { timeWeek, timeDays, timeDay } from 'd3-time'
import { timeFormat } from 'd3-time-format'
import { DateOrString } from '../types'
import { isDate } from 'lodash'

// Interfaces
interface ComputeBaseProps {
Expand Down Expand Up @@ -26,12 +29,15 @@ interface ComputeCellPositions
extends ComputeBaseProps,
ComputeBaseSpaceProps,
ComputeBaseDimensionProps {
from?: DateOrString
to?: DateOrString
data: {
date: Date
day: string
value: number
}[]
colorScale: (value: number) => string
emptyColor: string
}

interface ComputeWeekdays
Expand All @@ -53,7 +59,7 @@ interface Day {
date: Date
color: string
day: string
value: number
value?: number
}

interface Month {
Expand All @@ -76,6 +82,19 @@ interface ComputeMonths
days: Day[]
}

interface ComputeTotalDays {
from?: DateOrString
to?: DateOrString
data: {
date: Date
day: string
value: number
}[]
}

// used for days range and data matching
const dayFormat = timeFormat('%Y-%m-%d')

/**
* Compute day cell size according to
* current context.
Expand Down Expand Up @@ -145,6 +164,9 @@ function computeGrid({
export const computeCellPositions = ({
direction,
colorScale,
emptyColor,
from,
to,
data,
cellWidth,
cellHeight,
Expand All @@ -161,11 +183,23 @@ export const computeCellPositions = ({
}

// we need to determine whether we need to add days to move to correct position
const startDate = data[0].date
const dataWithCellPosition = data.map(dateValue => {
const start = from ? from : data[0].date
const end = to ? to : data[data.length - 1].date
const startDate = isDate(start) ? start : new Date(start)
const endDate = isDate(end) ? end : new Date(end)
const dateRange = timeDays(startDate, endDate).map(dayDate => {
return {
date: dayDate,
day: dayFormat(dayDate),
}
})

const dataWithCellPosition = dateRange.map(day => {
const dayData = data.find(item => item.day === day.day)

const { currentColumn, currentRow, firstWeek, year, month, date } = computeGrid({
startDate,
date: dateValue.date,
date: day.date,
direction,
})

Expand All @@ -174,14 +208,28 @@ export const computeCellPositions = ({
y: y + daySpacing * currentRow + cellHeight * currentRow,
}

if (!dayData) {
return {
...day,
coordinates,
firstWeek,
month,
year,
date,
color: emptyColor,
width: cellWidth,
height: cellHeight,
}
}

return {
...dateValue,
...dayData,
coordinates,
firstWeek,
month,
year,
date,
color: colorScale(dateValue.value),
color: colorScale(dayData.value),
width: cellWidth,
height: cellHeight,
}
Expand Down Expand Up @@ -273,3 +321,21 @@ export const computeMonthLegends = ({
return acc
}, accumulator)
}

export const computeTotalDays = ({ from, to, data }: ComputeTotalDays) => {
let startDate
let endDate
if (from) {
startDate = isDate(from) ? from : new Date(from)
} else {
startDate = data[0].date
}

if (from && to) {
endDate = isDate(to) ? to : new Date(to)
} else {
endDate = data[data.length - 1].date
}

return startDate.getDay() + timeDay.count(startDate, endDate)
}
6 changes: 4 additions & 2 deletions packages/calendar/src/types.ts
Expand Up @@ -174,7 +174,7 @@ export type CalendarCanvasProps = Dimensions &
}
>

export type TimeRangeDayData = CalendarDatum & {
export type TimeRangeDayData = (Omit<CalendarDatum, 'value'> | CalendarDatum) & {
coordinates: {
x: number
y: number
Expand All @@ -193,9 +193,11 @@ export type TimeRangeTooltipProps = Omit<TimeRangeDayData, 'date' | 'value'> & {
}

export type TimeRangeSvgProps = Dimensions & { data: CalendarDatum[] } & Partial<
Omit<CalendarData, 'data'>
> &
Partial<
Omit<
CommonCalendarProps,
| 'emptyColor'
| 'yearLegend'
| 'yearSpacing'
| 'yearLegendOffset'
Expand Down
53 changes: 51 additions & 2 deletions packages/calendar/stories/timeRange.stories.tsx
@@ -1,6 +1,6 @@
import { storiesOf } from '@storybook/react'
import { withKnobs, number, date, boolean } from '@storybook/addon-knobs'
import { generateOrderedDayCounts } from '@nivo/generators'
import { withKnobs, number, date, boolean, color } from '@storybook/addon-knobs'
import { generateOrderedDayCounts, generateDayCounts } from '@nivo/generators'

import { TimeRange, ResponsiveTimeRange } from '../src'

Expand Down Expand Up @@ -124,3 +124,52 @@ stories.add('TimeRange vertical', () => {
/>
)
})

stories.add('custom date range', () => {
const from = new Date(date('from', new Date(2020, 6, 27)))
const to = new Date(date('to', new Date(2020, 11, 7)))
const dataFrom = new Date(date('data start', new Date(2020, 7, 27)))
const dataTo = new Date(date('data end', new Date(2020, 10, 7)))
const data = generateDayCounts(dataFrom, dataTo)

return (
<div
style={{
height: number('height', 250),
width: number('width', 655),
}}
>
<ResponsiveTimeRange
{...{
square: boolean('square', true),
dayRadius: number('dayRadius', 5),
formatValue: value => value,
margin: {
top: number('margin-top', 40),
right: number('margin-right', 40),
bottom: number('margin-bottom', 40),
left: number('margin-left', 40),
},
from: from,
to: to,
emptyColor: color('emptyColor', '#EEEEEE'),
data: data,
daySpacing: number('daySpacing', 0),
}}
weekdayTicks={[]} // hide weekday tickmarks
legendFormat={value => value / 10 + 'M'}
legends={[
{
anchor: 'bottom',
direction: 'row',
itemCount: 4,
itemWidth: 42,
itemHeight: 36,
itemsSpacing: 14,
translateY: -30,
},
]}
/>
</div>
)
})
23 changes: 23 additions & 0 deletions website/src/data/components/time-range/props.js
Expand Up @@ -29,6 +29,20 @@ const props = [
type: 'object[]',
required: true,
},
{
key: 'from',
group: 'Base',
help: 'start date',
type: 'string | Date',
required: false,
},
{
key: 'to',
group: 'Base',
help: 'end date',
type: 'string | Date',
required: false,
},
{
key: 'width',
enableControlForFlavors: ['api'],
Expand Down Expand Up @@ -162,6 +176,15 @@ const props = [
required: false,
defaultValue: defaults.colors,
},
{
key: 'emptyColor',
help: 'color to use to fill days without available value.',
type: 'string',
required: false,
defaultValue: defaults.emptyColor,
controlType: 'colorPicker',
group: 'Style',
},
// Months
{
key: 'monthLegend',
Expand Down
4 changes: 2 additions & 2 deletions website/src/pages/time-range/index.js
Expand Up @@ -8,7 +8,7 @@
*/
import React from 'react'
import { ResponsiveTimeRange, timeRangeDefaultProps } from '@nivo/calendar'
import { generateOrderedDayCounts } from '@nivo/generators'
import { generateDayCounts } from '@nivo/generators'
import ComponentTemplate from '../../components/components/ComponentTemplate'
import meta from '../../data/components/time-range/meta.yml'
import mapper from '../../data/components/time-range/mapper'
Expand All @@ -20,7 +20,7 @@ const Tooltip = data => {

const from = new Date(2018, 3, 1)
const to = new Date(2018, 7, 12)
const generateData = () => generateOrderedDayCounts(from, to)
const generateData = () => generateDayCounts(from, to)

const initialProperties = {
from: '2018-04-01',
Expand Down

0 comments on commit d49a790

Please sign in to comment.