d3-scale#scaleTime JavaScript Examples
The following examples show how to use
d3-scale#scaleTime.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: sparklines.js From website with Apache License 2.0 | 5 votes |
StatisticSparkline = ({ data, field, color }) => {
const height = 40
const width = 120
const marginTop = 5
const marginRight = 20
const dates = []
const values = []
const averages = []
data.forEach((item, key) => {
if (key < 14) {
return
}
let sum = 0
for (let i = key; i > key - 7; i -= 1) {
sum += data[i][field]
}
averages.push({
value: sum / 7,
date: DateTime.fromISO(item.date).toJSDate(),
})
})
averages.forEach(item => {
dates.push(item.date)
values.push(item.value)
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([0, width])
const yMaxEffective = max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height, 0])
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveNatural)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<svg
className={sparklineStyles.sparkline}
viewBox={`0 0 ${width + marginRight} ${height - marginTop}`}
aria-hidden
>
<g transform={`translate(0 ${marginTop})`}>
<defs>
<marker
id={`triangle-${field}`}
refX="10"
refY="6"
markerWidth="30"
markerHeight="30"
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 12 6 0 12 3 6" style={{ fill: color }} />
</marker>
</defs>
<path
d={lineFn(averages)}
stroke={color}
strokeWidth={3}
fill="none"
markerEnd={`url(#triangle-${field})`}
/>
</g>
</svg>
)
}
Example #2
Source File: area-chart.js From website with Apache License 2.0 | 4 votes |
AreaChart = ({
annotations,
data,
fill,
height,
labelOrder,
marginBottom,
marginLeft,
marginRight,
marginTop,
xTicks,
width,
yFormat,
yMax,
yTicks,
showTicks,
focusable,
dateExtent,
renderTooltipContents,
}) => {
const grouped = nest()
.key(d => d.label)
.entries(data)
const sorted = !labelOrder
? grouped
: labelOrder
.map(label => {
const match = grouped.find(d => d.key === label)
return match
})
.filter(d => d)
const domain = dateExtent || extent(data, d => d.date)
const valueMax = max(data, d => d.value)
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const fillFn = typeof fill === 'string' ? fill : d => fill(d.key)
const xScale = scaleTime()
.domain(domain)
.range([0, width - totalXMargin])
const yScale = scaleLinear()
.domain([0, yMax || valueMax])
.range([height - totalYMargin, 0])
const a = area()
.x(d => xScale(d.date))
.y0(d => yScale(d.value))
.y1(height - totalYMargin)
const [tooltip, setTooltip] = useState(null)
function svgPoint(svg, x, y) {
const pt = svg.createSVGPoint()
pt.x = x
pt.y = y
return pt.matrixTransform(svg.getScreenCTM().inverse())
}
const handleMouseMove = event => {
const isTouchEvent = !event.clientX
if (!renderTooltipContents) return
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
const result = svgPoint(event.currentTarget, eventX, eventY)
const date = xScale.invert(result.x - marginLeft + 8)
date.setHours(0, 0, 0)
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
date,
})
}
const handleMouseLeave = () => setTooltip(null)
const dateMap = useMemo(
() =>
merge(
...data.map(d => ({
[d.date]: {
date: d.date,
[d.label]: d.value,
},
})),
),
[data],
)
const handleTouchEndCapture = event => {
setTooltip(null)
event.preventDefault()
}
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
focusable={focusable}
aria-hidden
onTouchStart={handleMouseMove}
onTouchEndCapture={handleTouchEndCapture}
onMouseMoveCapture={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
{showTicks ? (
<g transform={`translate(${marginLeft} ${marginTop})`}>
<g transform={`translate(0 ${height - totalYMargin})`}>
{xScale.ticks(xTicks).map(tick => (
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={tick}
x={xScale(tick)}
y={20}
>
{formatDate(tick)}
</text>
))}
</g>
<g>
{yScale.ticks(yTicks).map((tick, i) => (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 4}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{yFormat
? yFormat(tick, i, yScale.ticks(yTicks).length)
: formatNumber(tick)}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
))}
</g>
</g>
) : (
<line
className={chartStyles.gridLine}
x1={0}
x2={width}
y1={height - 1}
y2={height - 1}
/>
)}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{sorted.map(d => (
<path key={d.key} d={a(d.values)} opacity={0.8} fill={fillFn(d)} />
))}
</g>
{annotations && (
<g transform={`translate(${marginLeft} ${marginTop})`}>
{annotations.map(d => (
<line
key={d.date}
stroke="black"
strokeWidth="2px"
x1={xScale(d.date) - 1}
x2={xScale(d.date) - 1}
y1="0"
y2={height - marginTop - marginBottom}
/>
))}
</g>
)}
</svg>
{tooltip && renderTooltipContents && dateMap[tooltip.date] && (
<Tooltip {...tooltip}>
{renderTooltipContents(dateMap[tooltip.date])}
</Tooltip>
)}
</>
)
}
Example #3
Source File: bar-chart.js From website with Apache License 2.0 | 4 votes |
BarChart = ({
data,
lineData,
refLineData,
annotations,
handleAnnotationClick,
fill,
lineColor,
marginBottom,
marginLeft,
marginRight,
marginTop,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
renderTooltipContents,
perCapLabel,
}) => {
const chartRef = useRef()
const [tooltip, setTooltip] = useState(null)
// Used for tooltip optimization
const [timeoutRef, setTimeoutRef] = useState(null)
const [keyboardFocus, setKeyboardFocus] = useState(false)
// Used when placing annotations
const getValueForDate = date => {
const dateData = data.find(d => d.date.getTime() === date.getTime())
return dateData && dateData.value
}
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
// The x range is over the area in which the chart should be displaying.
// We don't use an X transform to place it in the correct spot, we use range
// instead
const xScale = scaleBand()
.domain(data.map(d => d.date))
.range([marginLeft, width - marginRight])
.padding(0.1)
const dateDomain = extent(data, d => d.date)
// Should probably refactor to use a single x-axis scale
// but the bars make use of the band.
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective =
yMax || max([...data, ...(refLineData || [])], d => d.value)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
let lineFn = null
if (lineData) {
lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
}
const hover = (event, d) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
useEffect(() => {
if (keyboardFocus === false || typeof data[keyboardFocus] === 'undefined') {
return
}
const column = data[keyboardFocus]
setTooltip({
top: chartRef.current.getBoundingClientRect().top,
left: chartRef.current.getBoundingClientRect().left,
d: column,
})
}, [keyboardFocus])
return (
<>
<svg
className={classnames(chartStyles.chart, styles.chart)}
viewBox={`0 0 ${width} ${height}`}
tabIndex="0"
aria-hidden
ref={chartRef}
onBlur={() => {
setTooltip(null)
setKeyboardFocus(false)
}}
onKeyDown={event => {
if (event.key === 'Escape') {
setTooltip(null)
setKeyboardFocus(false)
chartRef.current.blur()
}
if (event.key === 'ArrowRight') {
setKeyboardFocus(
keyboardFocus < data.length ? keyboardFocus + 1 : data.length,
)
}
if (
(event.shiftKey && event.key === 'Tab') ||
event.key === 'ArrowLeft'
) {
setKeyboardFocus(keyboardFocus > 0 ? keyboardFocus - 1 : 0)
}
}}
>
<g transform={`translate(${marginLeft} ${marginTop})`}>
<text className={classnames(chartStyles.label, styles.directions)}>
Use arrows to move, Escape to leave.
</text>
</g>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{/* bars (data) */}
{data.map((d, key) => (
<rect
key={d.date + d.value}
x={xScale(d.date)}
y={yScale(d.value)}
height={yScale(0) - yScale(d.value)}
width={xScale.bandwidth()}
fillOpacity={lineData ? 1 : 0.8}
fill={fill}
className={classnames(
renderTooltipContents && styles.interactiveBar,
key === keyboardFocus && styles.selected,
)}
onMouseOver={event => hover(event, d)}
onFocus={event => hover(event, d)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
))}
{/* line */}
{lineData && (
<path
d={lineFn(lineData)}
stroke={lineColor}
strokeWidth="3"
fill="none"
/>
)}
{/* reference line */}
{refLineData && (
<path
d={lineFn(refLineData)}
stroke="black"
strokeWidth="2"
strokeDasharray="4"
fill="none"
/>
)}
</g>
{/* annotations */}
{annotations && (
<g transform={`translate(0 ${marginTop})`}>
{annotations
.filter(
annotation =>
xScaleTime(annotation.date) >= xScaleTime(dateDomain[0]) &&
xScaleTime(annotation.date) <= xScaleTime(dateDomain[1]),
)
.map(d => (
<AnnotationBubble
content={d}
xScaleTime={xScaleTime}
yScale={yScale}
handleAnnotationClick={handleAnnotationClick}
getValueForDate={getValueForDate}
/>
))}
</g>
)}
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}
Example #4
Source File: line-chart.js From website with Apache License 2.0 | 4 votes |
LineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
renderTooltipContents,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
item.data.forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
const [tooltip, setTooltip] = useState(null)
const [timeoutRef, setTimeoutRef] = useState(null)
const hover = (event, dataLine) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d: dataLine,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(dataLine => (
<>
<path
d={lineFn(dataLine.data)}
stroke={dataLine.color}
strokeWidth={dataLine.stroke}
fill="none"
/>
{/* Add a wider hidden path for tooltips. */}
<path
d={lineFn(dataLine.data)}
stroke="transparent"
strokeWidth={6}
fill="none"
onMouseOver={event => hover(event, dataLine)}
onFocus={event => hover(event, dataLine)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
</>
))}
</>
)}
</g>
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}
Example #5
Source File: multi-line-chart.js From website with Apache License 2.0 | 4 votes |
MultiLineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
Object.keys(item.data).forEach(category => {
item.data[category].forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
// TODO make this dry-er w/r/t the single line chart component
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(item => (
<>
{Object.keys(item.data).map(category => (
<path
d={lineFn(item.data[category])}
stroke={item.colorMap[category]}
strokeWidth="1px"
fill="none"
/>
))}
</>
))}
</>
)}
</g>
</svg>
</>
)
}
Example #6
Source File: Minigraphs.js From covid19india-react with MIT License | 4 votes |
function Minigraphs({timeseries, date: timelineDate}) {
const refs = useRef([]);
const endDate = timelineDate || getIndiaDateYesterdayISO();
let [wrapperRef, {width}] = useMeasure();
width = Math.min(width, maxWidth);
const dates = useMemo(() => {
const pastDates = Object.keys(timeseries || {}).filter(
(date) => date <= endDate
);
const lastDate = pastDates[pastDates.length - 1];
const cutOffDateLower = formatISO(
subDays(parseIndiaDate(lastDate), MINIGRAPH_LOOKBACK_DAYS),
{representation: 'date'}
);
return pastDates.filter((date) => date >= cutOffDateLower);
}, [endDate, timeseries]);
const getMinigraphStatistic = useCallback(
(date, statistic) => {
return getStatistic(timeseries?.[date], 'delta', statistic);
},
[timeseries]
);
useEffect(() => {
if (!width) return;
const T = dates.length;
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const xScale = scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1]) || endDate,
])
.range([margin.left, chartRight]);
refs.current.forEach((ref, index) => {
const svg = select(ref);
const statistic = LEVEL_STATISTICS[index];
const color = STATISTIC_CONFIGS[statistic].color;
const dailyMaxAbs = max(dates, (date) =>
Math.abs(getMinigraphStatistic(date, statistic))
);
const yScale = scaleLinear()
.clamp(true)
.domain([-dailyMaxAbs, dailyMaxAbs])
.range([chartBottom, margin.top]);
const linePath = line()
.curve(curveMonotoneX)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) => yScale(getMinigraphStatistic(date, statistic)));
let pathLength;
svg
.selectAll('path')
.data(T ? [dates] : [])
.join(
(enter) =>
enter
.append('path')
.attr('fill', 'none')
.attr('stroke', color + '99')
.attr('stroke-width', 2.5)
.attr('d', linePath)
.attr('stroke-dasharray', function () {
return (pathLength = this.getTotalLength());
})
.call((enter) =>
enter
.attr('stroke-dashoffset', pathLength)
.transition()
.delay(100)
.duration(2500)
.attr('stroke-dashoffset', 0)
),
(update) =>
update
.attr('stroke-dasharray', null)
.transition()
.duration(500)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(date);
return interpolatePath(previous, current);
})
.selection()
);
svg
.selectAll('circle')
.data(T ? [dates[T - 1]] : [])
.join(
(enter) =>
enter
.append('circle')
.attr('fill', color)
.attr('r', 2.5)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 0)
.call((enter) =>
enter
.transition()
.delay(2100)
.duration(500)
.style('opacity', 1)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
),
(update) =>
update
.transition()
.duration(500)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 1)
.selection()
);
});
}, [endDate, dates, width, getMinigraphStatistic]);
return (
<div className="Minigraph">
{LEVEL_STATISTICS.map((statistic, index) => (
<div
key={statistic}
className={classnames('svg-parent')}
ref={index === 0 ? wrapperRef : null}
style={{width: `calc(${100 / LEVEL_STATISTICS.length}%)`}}
>
<svg
ref={(el) => {
refs.current[index] = el;
}}
preserveAspectRatio="xMidYMid meet"
width={width}
height={height}
/>
</div>
))}
</div>
);
}
Example #7
Source File: Timeseries.js From covid19india-react with MIT License | 4 votes |
function Timeseries({
statistics,
timeseries,
dates,
endDate,
chartType,
isUniform,
isLog,
isMovingAverage,
noRegionHighlightedDistrictData,
}) {
const {t} = useTranslation();
const refs = useRef([]);
const [wrapperRef, {width, height}] = useMeasure();
const [highlightedDate, setHighlightedDate] = useState(
dates[dates.length - 1]
);
useEffect(() => {
setHighlightedDate(dates[dates.length - 1]);
}, [dates]);
const getTimeseriesStatistic = useCallback(
(date, statistic, movingAverage = isMovingAverage) => {
return getStatistic(timeseries?.[date], chartType, statistic, {
movingAverage,
});
},
[chartType, isMovingAverage, timeseries]
);
const condenseChart = useMemo(() => {
const T = dates.length;
const days = differenceInDays(
parseIndiaDate(dates[T - 1]),
parseIndiaDate(dates[0])
);
// Chart extremes
const chartRight = width - margin.right;
// Bar widths
const axisWidth = Math.max(0, chartRight - margin.left) / (1.25 * days);
return axisWidth < 4;
}, [width, dates]);
const xScale = useMemo(() => {
const T = dates.length;
const chartRight = width - margin.right;
return scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1] || endDate),
])
.range([margin.left, chartRight]);
}, [width, endDate, dates]);
const yScales = useMemo(() => {
const chartBottom = height - margin.bottom;
const addScaleBuffer = (scale, log = false) => {
const domain = scale.domain();
if (log) {
scale.domain([
domain[0],
domain[0] * Math.pow(domain[1] / domain[0], 1 / yScaleShrinkFactor),
]);
} else {
scale.domain([
domain[0],
domain[0] + (domain[1] - domain[0]) / yScaleShrinkFactor,
]);
}
return scale;
};
const [uniformScaleMin, uniformScaleMax] = extent(
dates.reduce((res, date) => {
res.push(
...PRIMARY_STATISTICS.map((statistic) =>
getTimeseriesStatistic(date, statistic)
)
);
return res;
}, [])
);
const yScaleUniformLinear = addScaleBuffer(
scaleLinear()
.clamp(true)
.domain([Math.min(0, uniformScaleMin), Math.max(1, uniformScaleMax)])
.range([chartBottom, margin.top])
.nice(4)
);
const yScaleUniformLog = addScaleBuffer(
scaleLog()
.clamp(true)
.domain([1, Math.max(10, uniformScaleMax)])
.range([chartBottom, margin.top])
.nice(4),
true
);
return statistics.map((statistic) => {
if (isUniform) {
if (
chartType === 'total' &&
isLog &&
PRIMARY_STATISTICS.includes(statistic)
) {
return yScaleUniformLog;
} else if (PRIMARY_STATISTICS.includes(statistic)) {
return yScaleUniformLinear;
}
}
const [scaleMin, scaleMax] = extent(dates, (date) =>
getTimeseriesStatistic(date, statistic)
);
if (chartType === 'total' && isLog) {
return addScaleBuffer(
scaleLog()
.clamp(true)
.domain([1, Math.max(10, scaleMax)])
.range([chartBottom, margin.top])
.nice(4),
true
);
} else {
return addScaleBuffer(
scaleLinear()
.clamp(true)
.domain([
Math.min(0, scaleMin),
STATISTIC_CONFIGS[statistic].format === '%'
? Math.min(100, Math.max(1, scaleMax))
: Math.max(1, scaleMax),
])
.range([chartBottom, margin.top])
.nice(4)
);
}
});
}, [
height,
chartType,
isUniform,
isLog,
statistics,
dates,
getTimeseriesStatistic,
]);
useEffect(() => {
if (!width || !height) return;
const T = dates.length;
// Chart extremes
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const isDiscrete = chartType === 'delta' && !isMovingAverage;
const xAxis = (g) =>
g
.attr('class', 'x-axis')
.call(axisBottom(xScale).ticks(numTicksX(width)));
const xAxis2 = (g, yScale) => {
g.attr('class', 'x-axis2')
.call(axisBottom(xScale).tickValues([]).tickSize(0))
.select('.domain')
.style('transform', `translate3d(0, ${yScale(0)}px, 0)`);
if (yScale(0) !== chartBottom) g.select('.domain').attr('opacity', 0.4);
else g.select('.domain').attr('opacity', 0);
};
const yAxis = (g, yScale, format) =>
g.attr('class', 'y-axis').call(
axisRight(yScale)
.ticks(4)
.tickFormat((num) => formatNumber(num, format))
.tickPadding(4)
);
function mousemove(event) {
const xm = pointer(event)[0];
const date = xScale.invert(xm);
if (!isNaN(date)) {
const bisectDate = bisector((date) => parseIndiaDate(date)).left;
const index = bisectDate(dates, date, 1);
const dateLeft = dates[index - 1];
const dateRight = dates[index];
setHighlightedDate(
date - parseIndiaDate(dateLeft) < parseIndiaDate(dateRight) - date
? dateLeft
: dateRight
);
}
}
function mouseout(event) {
setHighlightedDate(dates[T - 1]);
}
/* Begin drawing charts */
statistics.forEach((statistic, i) => {
const ref = refs.current[i];
const svg = select(ref);
const t = svg.transition().duration(D3_TRANSITION_DURATION);
const yScale = yScales[i];
const statisticConfig = STATISTIC_CONFIGS[statistic];
const format =
STATISTIC_CONFIGS[statistic].format === '%' ? '%' : 'short';
const isNonLinear = !!STATISTIC_CONFIGS[statistic]?.nonLinear;
/* X axis */
svg
.select('.x-axis')
.style('transform', `translate3d(0, ${chartBottom}px, 0)`)
.transition(t)
.call(xAxis);
svg.select('.x-axis2').transition(t).call(xAxis2, yScale);
/* Y axis */
svg
.select('.y-axis')
.style('transform', `translate3d(${chartRight}px, 0, 0)`)
.transition(t)
.call(yAxis, yScale, format);
/* Path dots */
svg
.selectAll('circle.normal')
.data(condenseChart ? [] : dates, (date) => date)
.join((enter) =>
enter
.append('circle')
.attr('class', 'normal')
.attr('fill', statisticConfig?.color)
.attr('stroke', statisticConfig?.color)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(isDiscrete ? 0 : getTimeseriesStatistic(date, statistic))
)
.attr('r', 2)
)
.transition(t)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));
const areaPath = (dates, allZero = false) =>
area()
.curve(curveStep)
.x((date) => xScale(parseIndiaDate(date)))
.y0(yScale(0))
.y1(
allZero
? yScale(0)
: (date) => yScale(getTimeseriesStatistic(date, statistic, false))
)(dates);
svg
.selectAll('.trend-area')
.data(
T && chartType === 'delta' && !isNonLinear && condenseChart
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-area')
.call((enter) =>
enter
.attr('d', (dates) => areaPath(dates, true))
.transition(t)
.attr('d', areaPath)
),
(update) =>
update.call((update) =>
update.transition(t).attrTween('d', function (dates) {
const previous = select(this).attr('d');
const current = areaPath(dates);
return interpolatePath(previous, current);
})
),
(exit) =>
exit.call((exit) =>
exit
.transition(t)
.attr('d', (dates) => areaPath(dates, true))
.remove()
)
)
.transition(t)
.attr('opacity', isMovingAverage ? 0.3 : 1);
svg
.selectAll('.stem')
.data(
T && chartType === 'delta' && !isNonLinear && !condenseChart
? dates
: [],
(date) => date
)
.join(
(enter) =>
enter
.append('line')
.attr('class', 'stem')
.attr('stroke-width', 4)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y2', yScale(0)),
(update) => update,
(exit) =>
exit.call((exit) =>
exit
.transition(t)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('y2', yScale(0))
.remove()
)
)
.transition(t)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y2', (date) =>
yScale(getTimeseriesStatistic(date, statistic, false))
)
.attr('opacity', isMovingAverage ? 0.2 : 1);
const linePath = (
movingAverage = isMovingAverage,
curve = curveMonotoneX
) =>
line()
.curve(curve)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) =>
yScale(getTimeseriesStatistic(date, statistic, movingAverage))
);
svg
.select('.trend')
.selectAll('path')
.data(
T && (chartType === 'total' || isNonLinear || isMovingAverage)
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend')
.attr('fill', 'none')
.attr('stroke-width', 4)
.attr('d', linePath())
.call((enter) => enter.transition(t).attr('opacity', 1)),
(update) =>
update.call((update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath()(date);
return interpolatePath(previous, current);
})
.attr('opacity', 1)
),
(exit) =>
exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
)
.attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));
svg
.select('.trend-background')
.selectAll('path')
.data(
T && chartType === 'delta' && isNonLinear && isMovingAverage
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-background')
.attr('fill', 'none')
.attr('stroke-width', 4)
.attr('d', linePath(false, curveLinear))
.call((enter) => enter.transition(t).attr('opacity', 0.2)),
(update) =>
update.call((update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(false, curveLinear)(date);
return interpolatePath(previous, current);
})
.attr('opacity', 0.2)
),
(exit) =>
exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
)
.attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));
svg.selectAll('*').attr('pointer-events', 'none');
svg
.on('mousemove', mousemove)
.on('touchmove', (event) => mousemove(event.touches[0]))
.on('mouseout touchend', mouseout);
});
}, [
width,
height,
chartType,
isMovingAverage,
condenseChart,
xScale,
yScales,
statistics,
getTimeseriesStatistic,
dates,
]);
useEffect(() => {
statistics.forEach((statistic, i) => {
const ref = refs.current[i];
const svg = select(ref);
const statisticConfig = STATISTIC_CONFIGS[statistic];
const yScale = yScales[i];
const t = svg.transition().duration(D3_TRANSITION_DURATION);
svg
.selectAll('circle.condensed')
.data(
condenseChart && highlightedDate ? [highlightedDate] : [],
(date) => date
)
.join((enter) =>
enter
.append('circle')
.attr('class', 'condensed')
.attr('fill', statisticConfig?.color)
.attr('stroke', statisticConfig?.color)
.attr('pointer-events', 'none')
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getTimeseriesStatistic(date, statistic))
)
.attr('r', 4)
)
.transition(t)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));
if (!condenseChart) {
svg
.selectAll('circle')
.attr('r', (date) => (date === highlightedDate ? 4 : 2));
}
});
}, [
condenseChart,
highlightedDate,
xScale,
yScales,
statistics,
getTimeseriesStatistic,
]);
const getTimeseriesStatisticDelta = useCallback(
(statistic) => {
if (!highlightedDate) return;
const currCount = getTimeseriesStatistic(highlightedDate, statistic);
if (STATISTIC_CONFIGS[statistic]?.hideZero && currCount === 0) return;
const prevDate =
dates[dates.findIndex((date) => date === highlightedDate) - 1];
const prevCount = getTimeseriesStatistic(prevDate, statistic);
return currCount - prevCount;
},
[dates, highlightedDate, getTimeseriesStatistic]
);
const trail = useMemo(
() =>
statistics.map((statistic, index) => ({
animationDelay: `${index * 250}ms`,
})),
[statistics]
);
return (
<div className="Timeseries">
{statistics.map((statistic, index) => {
const total = getTimeseriesStatistic(highlightedDate, statistic);
const delta = getTimeseriesStatisticDelta(statistic, index);
const statisticConfig = STATISTIC_CONFIGS[statistic];
return (
<div
key={statistic}
className={classnames('svg-parent fadeInUp', `is-${statistic}`)}
style={trail[index]}
ref={index === 0 ? wrapperRef : null}
>
{highlightedDate && (
<div className={classnames('stats', `is-${statistic}`)}>
<h5 className="title">
{t(capitalize(statisticConfig.displayName))}
</h5>
<h5>{formatDate(highlightedDate, 'dd MMMM')}</h5>
<div className="stats-bottom">
<h2>
{!noRegionHighlightedDistrictData ||
!statisticConfig?.hasPrimary
? formatNumber(
total,
statisticConfig.format !== 'short'
? statisticConfig.format
: 'long',
statistic
)
: '-'}
</h2>
<h6>
{!noRegionHighlightedDistrictData ||
!statisticConfig?.hasPrimary
? `${delta > 0 ? '+' : ''}${formatNumber(
delta,
statisticConfig.format !== 'short'
? statisticConfig.format
: 'long',
statistic
)}`
: ''}
</h6>
</div>
</div>
)}
<svg
ref={(element) => {
refs.current[index] = element;
}}
preserveAspectRatio="xMidYMid meet"
>
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
<g className="trend-background" />
<g className="trend" />
</svg>
</div>
);
})}
</div>
);
}
Example #8
Source File: TimeseriesBrush.js From covid19india-react with MIT License | 4 votes |
function TimeseriesBrush({
timeseries,
dates,
currentBrushSelection,
endDate,
lookback,
setBrushSelectionEnd,
setLookback,
animationIndex,
}) {
const chartRef = useRef();
const [wrapperRef, {width, height}] = useMeasure();
const endDateMin =
lookback !== null
? min([
formatISO(addDays(parseIndiaDate(dates[0]), lookback), {
representation: 'date',
}),
endDate,
])
: endDate;
const xScale = useMemo(() => {
const T = dates.length;
// Chart extremes
const chartRight = width - margin.right;
return scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1] || endDate),
])
.range([margin.left, chartRight]);
}, [width, endDate, dates]);
useEffect(() => {
if (!width || !height) return;
// Chart extremes
const chartBottom = height - margin.bottom;
const xAxis = (g) =>
g
.attr('class', 'x-axis')
.call(axisBottom(xScale).ticks(numTicksX(width)));
// Switched to daily confirmed instead of cumulative ARD
const timeseriesStacked = stack()
.keys(BRUSH_STATISTICS)
.value((date, statistic) =>
Math.max(0, getStatistic(timeseries[date], 'delta', statistic))
)(dates);
const yScale = scaleLinear()
.clamp(true)
.domain([
0,
max(
timeseriesStacked[timeseriesStacked.length - 1],
([, y1]) => yBufferTop * y1
),
])
.range([chartBottom, margin.top]);
const svg = select(chartRef.current);
const t = svg.transition().duration(D3_TRANSITION_DURATION);
svg
.select('.x-axis')
.attr('pointer-events', 'none')
.style('transform', `translate3d(0, ${chartBottom}px, 0)`)
.transition(t)
.call(xAxis);
const areaPath = area()
.curve(curveMonotoneX)
.x((d) => xScale(parseIndiaDate(d.data)))
.y0((d) => yScale(d[0]))
.y1((d) => yScale(d[1]));
svg
.select('.trend-areas')
.selectAll('.trend-area')
.data(timeseriesStacked)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-area')
.attr('fill', ({key}) => STATISTIC_CONFIGS[key].color)
.attr('fill-opacity', 0.4)
.attr('stroke', ({key}) => STATISTIC_CONFIGS[key].color)
.attr('d', areaPath)
.attr('pointer-events', 'none'),
(update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = areaPath(date);
return interpolatePath(previous, current);
})
.selection()
);
}, [dates, width, height, xScale, timeseries]);
const defaultSelection = currentBrushSelection.map((date) =>
xScale(parseIndiaDate(date))
);
const brush = useMemo(() => {
if (!width || !height) return;
// Chart extremes
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const brush = brushX()
.extent([
[margin.left, margin.top],
[chartRight, chartBottom],
])
.handleSize(20);
return brush;
}, [width, height]);
const brushed = useCallback(
({sourceEvent, selection}) => {
if (!sourceEvent) return;
const [brushStartDate, brushEndDate] = selection.map(xScale.invert);
ReactDOM.unstable_batchedUpdates(() => {
setBrushSelectionEnd(formatISO(brushEndDate, {representation: 'date'}));
setLookback(differenceInDays(brushEndDate, brushStartDate));
});
},
[xScale, setBrushSelectionEnd, setLookback]
);
const beforebrushstarted = useCallback(
(event) => {
const svg = select(chartRef.current);
const selection = brushSelection(svg.select('.brush').node());
if (!selection) return;
const dx = selection[1] - selection[0];
const [[cx]] = pointers(event);
const [x0, x1] = [cx - dx / 2, cx + dx / 2];
const [X0, X1] = xScale.range();
svg
.select('.brush')
.call(
brush.move,
x1 > X1 ? [X1 - dx, X1] : x0 < X0 ? [X0, X0 + dx] : [x0, x1]
);
},
[brush, xScale]
);
const brushended = useCallback(
({sourceEvent, selection}) => {
if (!sourceEvent || !selection) return;
const domain = selection
.map(xScale.invert)
.map((date) => formatISO(date, {representation: 'date'}));
const svg = select(chartRef.current);
svg
.select('.brush')
.call(
brush.move,
domain.map((date) => xScale(parseIndiaDate(date)))
)
.call((g) => g.select('.overlay').attr('cursor', 'pointer'));
},
[brush, xScale]
);
useEffect(() => {
if (!brush) return;
brush.on('start brush', brushed).on('end', brushended);
const svg = select(chartRef.current);
svg
.select('.brush')
.call(brush)
.call((g) =>
g
.select('.overlay')
.attr('cursor', 'pointer')
.datum({type: 'selection'})
.on('mousedown touchstart', beforebrushstarted)
);
}, [brush, brushed, brushended, beforebrushstarted]);
useEffect(() => {
if (!brush) return;
const svg = select(chartRef.current);
svg.select('.brush').call(brush.move, defaultSelection);
}, [brush, defaultSelection]);
const handleWheel = (event) => {
if (event.deltaX) {
setBrushSelectionEnd(
max([
endDateMin,
dates[
Math.max(
0,
Math.min(
dates.length - 1,
dates.indexOf(currentBrushSelection[1]) +
Math.sign(event.deltaX) * brushWheelDelta
)
)
],
])
);
}
};
return (
<div className="Timeseries">
<div
className={classnames('svg-parent is-brush fadeInUp')}
ref={wrapperRef}
onWheel={handleWheel}
style={{animationDelay: `${animationIndex * 250}ms`}}
>
<svg ref={chartRef} preserveAspectRatio="xMidYMid meet">
<defs>
<clipPath id="clipPath">
<rect
x={0}
y={`${margin.top}`}
width={width}
height={`${Math.max(0, height - margin.bottom)}`}
/>
</clipPath>
<mask id="mask">
<rect
x={0}
y={`${margin.top}`}
width={width}
height={`${Math.max(0, height - margin.bottom)}`}
fill="hsl(0, 0%, 40%)"
/>
<use href="#selection" fill="white" />
</mask>
</defs>
<g className="brush" clipPath="url(#clipPath)">
<g mask="url(#mask)">
<rect className="overlay" />
<g className="trend-areas" />
<rect className="selection" id="selection" />
</g>
</g>
<g className="x-axis" />
</svg>
</div>
</div>
);
}
Example #9
Source File: DateSlider.js From covid19 with MIT License | 4 votes |
render() {
const {
date,
lang,
startDate,
endDate,
handleDateChange,
handleTempDateChange,
fullMap,
fullPlot,
plotDates
} = this.props
let min = parseDate(startDate)
const max = parseDate(endDate)
min = new Date(min.getTime() + 1000 * 60 * (max.getTimezoneOffset() - min.getTimezoneOffset()))
const numberOfDays = (max - min) / (1000 * 3600 * 24)
const dateTicksInterval = Math.round(numberOfDays / (!fullMap ? 10 : 15))
const dateTicks = scaleTime()
.domain([ min, max ])
.ticks(
// hack to fix unwanted behavior (https://github.com/d3/d3/issues/2240)
timeDay.filter(function(d) {
return timeDay.count(0, d) % dateTicksInterval === 0
})
)
.map((d) => +d)
let values = !fullPlot ? [ date ] : plotDates
values = values.map((x) => {
let d = parseDate(x)
d = new Date(d.getTime() + 1000 * 60 * (max.getTimezoneOffset() - d.getTimezoneOffset()))
return +d
})
return (
<Slider
className="date-slider"
mode={1}
step={1000 * 60 * 60 * 24}
domain={[ +min, +max ]}
onChange={(time) => {
if (!fullPlot) handleDateChange(isoDate(time[0], endDate).slice(0, 10))
}}
onUpdate={handleTempDateChange}
values={values}
>
<Rail>
{({ getRailProps }) => (
<Fragment>
<div className="date-slider-rail-outer" {...getRailProps()} />
<div className="date-slider-rail-inner" />
</Fragment>
)}
</Rail>
<Handles>
{({ handles, getHandleProps }) => (
<div>
{handles.map((handle, index) => (
<Fragment key={`handle-${index}`}>
<div
className="date-slider-handle-outer"
style={{
left: `${handle.percent}%`
}}
{...getHandleProps(handle.id)}
/>
<div
role="slider"
className="date-slider-handle-inner"
// eslint-disable-next-line
aria-valuemin={+min}
// eslint-disable-next-line
aria-valuemax={+max}
aria-valuenow={handle.value}
style={{
left: `${handle.percent}%`
}}
/>
</Fragment>
))}
</div>
)}
</Handles>
<Tracks left={!fullPlot} right={false}>
{({ tracks, getTrackProps }) => (
<div>
{tracks.map(({ id, source, target }) => (
<div
key={`track-${id}`}
className="date-slider-track"
style={{
left: `${source.percent}%`,
width: `${target.percent - source.percent}%`
}}
{...getTrackProps()}
/>
))}
</div>
)}
</Tracks>
<Ticks values={dateTicks}>
{({ ticks }) => (
<div>
{ticks.map((tick, index) => (
<div key={`tick-${index}`}>
<div
className="date-slider-tick"
style={{
left: `${tick.percent}%`
}}
/>
<div
className="date-slider-tick-text"
style={{
marginLeft: `${-(100 / ticks.length) / 2}%`,
width: `${100 / ticks.length}%`,
left: `${tick.percent}%`
}}
>
{formatTick(tick.value, lang)}
</div>
</div>
))}
</div>
)}
</Ticks>
</Slider>
)
}