Skip to content

Commit

Permalink
Add day labels to calendar and timerange
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeWobbler committed Sep 12, 2024
1 parent 0f5ac2f commit f88902d
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 44 deletions.
26 changes: 25 additions & 1 deletion packages/calendar/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ import { CalendarMonthPath } from './CalendarMonthPath'
import { CalendarMonthLegends } from './CalendarMonthLegends'
import { CalendarDay } from './CalendarDay'
import { calendarDefaultProps } from './props'
import { useMonthLegends, useYearLegends, useCalendarLayout, useDays, useColorScale } from './hooks'
import {
useMonthLegends,
useYearLegends,
useCalendarLayout,
useDays,
useColorScale,
useFontSize,
useColorFormatter,
useDayLabels,
} from './hooks'

const InnerCalendar = ({
margin: partialMargin,
Expand Down Expand Up @@ -42,6 +51,11 @@ const InnerCalendar = ({
dayBorderWidth = calendarDefaultProps.dayBorderWidth,
daySpacing = calendarDefaultProps.daySpacing,

dayLabel = calendarDefaultProps.dayLabel,
dayLabelFormat,
dayLabelColor = calendarDefaultProps.dayLabelColor,
dayLabelStyle = calendarDefaultProps.dayLabelStyle,

isInteractive = calendarDefaultProps.isInteractive,
tooltip = calendarDefaultProps.tooltip,
onClick,
Expand Down Expand Up @@ -81,6 +95,11 @@ const InnerCalendar = ({
const formatLegend = useValueFormatter(legendFormat)
const formatValue = useValueFormatter(valueFormat)

const formatDayLabel = useValueFormatter(dayLabelFormat)
const dayLabels = useDayLabels(data, formatDayLabel)
const dayLabelFontSize = useFontSize(dayLabels)
const setLabelColor = useColorFormatter(dayLabelColor)

return (
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin} role={role}>
{days.map(d => (
Expand All @@ -96,6 +115,11 @@ const InnerCalendar = ({
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
displayLabel={dayLabel}
label={dayLabels[d.day]}
labelSize={dayLabelFontSize}
labelColor={setLabelColor}
labelStyle={dayLabelStyle}
isInteractive={isInteractive}
tooltip={tooltip}
onClick={onClick}
Expand Down
44 changes: 41 additions & 3 deletions packages/calendar/src/CalendarCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ import {
} from '@nivo/core'
import { renderLegendToCanvas } from '@nivo/legends'
import { calendarCanvasDefaultProps } from './props'
import { useCalendarLayout, useColorScale, useMonthLegends, useYearLegends, useDays } from './hooks'
import {
useCalendarLayout,
useColorScale,
useMonthLegends,
useYearLegends,
useDays,
useFontSize,
useColorFormatter,
useDayLabels,
} from './hooks'
import { useTooltip } from '@nivo/tooltip'
import { CalendarCanvasProps } from './types'

Expand Down Expand Up @@ -74,6 +83,11 @@ const InnerCalendarCanvas = memo(
dayBorderWidth = calendarCanvasDefaultProps.dayBorderWidth,
daySpacing = calendarCanvasDefaultProps.daySpacing,

dayLabel = calendarCanvasDefaultProps.dayLabel,
dayLabelFormat,
dayLabelColor = calendarCanvasDefaultProps.dayLabelColor,
dayLabelFontWeight = calendarCanvasDefaultProps.dayLabelFontWeight,

isInteractive = calendarCanvasDefaultProps.isInteractive,
tooltip = calendarCanvasDefaultProps.tooltip,
onClick,
Expand Down Expand Up @@ -121,6 +135,11 @@ const InnerCalendarCanvas = memo(
const formatValue = useValueFormatter(valueFormat)
const formatLegend = useValueFormatter(legendFormat)

const formatDayLabel = useValueFormatter(dayLabelFormat)
const dayLabels = useDayLabels(data, formatDayLabel)
const dayLabelFontSize = useFontSize(dayLabels)
const setLabelColor = useColorFormatter(dayLabelColor)

const { showTooltipFromEvent, hideTooltip } = useTooltip()

useEffect(() => {
Expand All @@ -139,6 +158,9 @@ const InnerCalendarCanvas = memo(
ctx.fillRect(0, 0, outerWidth, outerHeight)
ctx.translate(margin.left, margin.top)

ctx.textAlign = 'center'
ctx.textBaseline = 'middle'

days.forEach(day => {
ctx.fillStyle = day.color
if (dayBorderWidth > 0) {
Expand All @@ -150,13 +172,24 @@ const InnerCalendarCanvas = memo(
ctx.rect(day.x, day.y, day.size, day.size)
ctx.fill()

if (dayLabel && dayLabels[day.day] && 'data' in day) {
ctx.font = `${dayLabelFontWeight} ${dayLabelFontSize(
day.size - dayBorderWidth,
day.size - dayBorderWidth
)} ${theme.labels.text.fontFamily}`
ctx.fillStyle = setLabelColor(day.data)
ctx.fillText(
dayLabels[day.day],
day.x + dayBorderWidth / 2 + (day.size - dayBorderWidth) / 2,
day.y + dayBorderWidth / 2 + (day.size - dayBorderWidth) / 2
)
}

if (dayBorderWidth > 0) {
ctx.stroke()
}
})

ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = theme.labels.text.fill ?? ''
ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`

Expand Down Expand Up @@ -202,6 +235,11 @@ const InnerCalendarCanvas = memo(
days,
dayBorderColor,
dayBorderWidth,
dayLabel,
setLabelColor,
dayLabelFontSize,
dayLabelFontWeight,
formatDayLabel,
colorScale,
yearLegend,
yearLegends,
Expand Down
53 changes: 38 additions & 15 deletions packages/calendar/src/CalendarDay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export const CalendarDay = memo(
color,
borderWidth,
borderColor,
displayLabel,
label,
labelSize,
labelColor,
labelStyle,
isInteractive,
tooltip,
onMouseEnter,
Expand Down Expand Up @@ -71,21 +76,39 @@ export const CalendarDay = memo(
)

return (
<rect
x={x}
y={y}
width={size}
height={size}
style={{
fill: color,
strokeWidth: borderWidth,
stroke: borderColor,
}}
onMouseEnter={isInteractive ? handleMouseEnter : undefined}
onMouseMove={isInteractive ? handleMouseMove : undefined}
onMouseLeave={isInteractive ? handleMouseLeave : undefined}
onClick={isInteractive ? handleClick : undefined}
/>
<>
<rect
x={x}
y={y}
width={size}
height={size}
style={{
fill: color,
strokeWidth: borderWidth,
stroke: borderColor,
}}
onMouseEnter={isInteractive ? handleMouseEnter : undefined}
onMouseMove={isInteractive ? handleMouseMove : undefined}
onMouseLeave={isInteractive ? handleMouseLeave : undefined}
onClick={isInteractive ? handleClick : undefined}
/>
{displayLabel && label && 'value' in data && (
<text
x={x + size / 2}
y={y + size / 2}
width={size}
height={size}
dy={(size - borderWidth) * 0.05}
dominantBaseline="middle"
textAnchor="middle"
fill={labelColor(data)}
fontSize={labelSize(size - borderWidth, size - borderWidth)}
style={{ pointerEvents: 'none', ...labelStyle }}
>
{label}
</text>
)}
</>
)
}
)
23 changes: 22 additions & 1 deletion packages/calendar/src/TimeRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import {
computeMonthLegends,
computeTotalDays,
} from './compute/timeRange'
import { useMonthLegends, useColorScale } from './hooks'
import {
useMonthLegends,
useColorScale,
useFontSize,
useColorFormatter,
useDayLabels,
} from './hooks'
import { TimeRangeDay } from './TimeRangeDay'
import { CalendarMonthLegends } from './CalendarMonthLegends'
import { TimeRangeSvgProps } from './types'
Expand Down Expand Up @@ -44,6 +50,11 @@ const InnerTimeRange = ({
daySpacing = timeRangeDefaultProps.daySpacing,
dayRadius = timeRangeDefaultProps.dayRadius,

dayLabel = timeRangeDefaultProps.dayLabel,
dayLabelFormat,
dayLabelColor = timeRangeDefaultProps.dayLabelColor,
dayLabelStyle = timeRangeDefaultProps.dayLabelStyle,

isInteractive = timeRangeDefaultProps.isInteractive,
tooltip = timeRangeDefaultProps.tooltip,
onClick,
Expand Down Expand Up @@ -133,6 +144,11 @@ const InnerTimeRange = ({
const formatValue = useValueFormatter(valueFormat)
const formatLegend = useValueFormatter(legendFormat)

const formatDayLabel = useValueFormatter(dayLabelFormat)
const dayLabels = useDayLabels(data, formatDayLabel)
const dayLabelFontSize = useFontSize(dayLabels)
const setLabelColor = useColorFormatter(dayLabelColor)

return (
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin} role={role}>
{weekdayLegends.map(legend => (
Expand All @@ -159,6 +175,11 @@ const InnerTimeRange = ({
color={d.color}
borderWidth={dayBorderWidth}
borderColor={dayBorderColor}
displayLabel={dayLabel}
label={dayLabels[d.day]}
labelSize={dayLabelFontSize}
labelColor={setLabelColor}
labelStyle={dayLabelStyle}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
Expand Down
57 changes: 40 additions & 17 deletions packages/calendar/src/TimeRangeDay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export const TimeRangeDay = memo(
color,
borderWidth,
borderColor,
displayLabel,
label,
labelSize,
labelColor,
labelStyle,
isInteractive,
tooltip,
onMouseEnter,
Expand Down Expand Up @@ -71,23 +76,41 @@ export const TimeRangeDay = memo(
)

return (
<rect
x={x}
y={y}
rx={rx}
ry={ry}
width={width}
height={height}
style={{
fill: color,
strokeWidth: borderWidth,
stroke: borderColor,
}}
onMouseEnter={isInteractive ? handleMouseEnter : undefined}
onMouseMove={isInteractive ? handleMouseMove : undefined}
onMouseLeave={isInteractive ? handleMouseLeave : undefined}
onClick={isInteractive ? handleClick : undefined}
/>
<>
<rect
x={x}
y={y}
rx={rx}
ry={ry}
width={width}
height={height}
style={{
fill: color,
strokeWidth: borderWidth,
stroke: borderColor,
}}
onMouseEnter={isInteractive ? handleMouseEnter : undefined}
onMouseMove={isInteractive ? handleMouseMove : undefined}
onMouseLeave={isInteractive ? handleMouseLeave : undefined}
onClick={isInteractive ? handleClick : undefined}
/>
{displayLabel && label && 'value' in data && (
<text
x={x + width / 2}
y={y + height / 2}
width={width}
height={height}
dy={(height - borderWidth) * 0.05}
dominantBaseline="middle"
textAnchor="middle"
fill={labelColor(data)}
fontSize={labelSize(width - borderWidth, height - borderWidth)}
style={{ pointerEvents: 'none', ...labelStyle }}
>
{label}
</text>
)}
</>
)
}
)
31 changes: 30 additions & 1 deletion packages/calendar/src/compute/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { alignBox } from '@nivo/core'
import { timeFormat } from 'd3-time-format'
import { timeDays, timeWeek, timeWeeks, timeMonths, timeYear } from 'd3-time'
import { ScaleQuantize } from 'd3-scale'
import { BBox, CalendarSvgProps, ColorScale, Datum, Year } from '../types'
import { BBox, CalendarDatum, CalendarSvgProps, ColorScale, Datum, Year } from '../types'

/**
* Compute min/max values.
Expand Down Expand Up @@ -466,3 +466,32 @@ export const computeMonthLegendPositions = <Month extends { bbox: BBox }>({
}
})
}

export const computeMaxLabelLength = (labels: string[]): number => {
return labels.reduce((max, label) => {
return Math.max(label.length, max)
}, 0)
}

export const createFontSizeCalculator = (maxLength: number) => {
const cache = { width: 0, height: 0, fontSize: '0px' }
return (width: number, height: number) => {
if (width != cache.width || height != cache.height) {
cache.fontSize = `${Math.max(Math.min(width / (0.5 + maxLength * 0.5), height), 0)}px`
cache.height = height
cache.width = width
}
return cache.fontSize
}
}

export const computeDayLabels = (
data: CalendarDatum[],
format: (value: number, day: CalendarDatum) => string
): { [day: string]: string } => {
const dayLabelPairs: { [day: string]: string } = {}
data.forEach(day => {
dayLabelPairs[day.day] = format(day.value, day)
})
return dayLabelPairs
}
Loading

0 comments on commit f88902d

Please sign in to comment.