-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Component | Line: Support interpolated dashed line for missing values
- Loading branch information
Showing
9 changed files
with
281 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
packages/dev/src/examples/xy-components/line/multi-patchy-line/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import React, { useCallback, useEffect, useState } from 'react' | ||
import { VisAxis, VisBulletLegend, VisBulletLegendSelectors, VisCrosshair, VisLine, VisScatter, VisTooltip, VisXYContainer } from '@unovis/react' | ||
import { BulletLegendItemInterface, BulletShape, NumericAccessor, colors, CurveType } from '@unovis/ts' | ||
|
||
import { ExampleViewerDurationProps } from '@src/components/ExampleViewer' | ||
|
||
export const title = 'Interpolated Multi-Line Chart' | ||
export const subTitle = 'With interactive bullet legend' | ||
|
||
const n = undefined | ||
const layers: Record<string, (number | undefined | null)[]> = { | ||
y0: [3, 5, 2, 5, n, 3, 4, 5, 4, 2, 5, 2, 4, 2, n, 5], | ||
y1: [2, 1, n, 2, 2, n, 1, 3, 2, n, 1, 4, 6, 4, 3, 2], | ||
y2: [5, 6, 7, n, 5, 7, 8, 7, 9, 6, n, 5, n, n, 9, 7], | ||
y3: [9, n, n, 8, n, n, 5, 6, 5, 5, 4, 3, 2, 1, 2, 0], | ||
} | ||
|
||
type Datum = Record<keyof typeof layers, number> & { x: number } | ||
|
||
const keys = Object.keys(layers) as (keyof Datum)[] | ||
const data = Array.from({ length: layers.y0.length }, (_, i) => ({ | ||
x: i, | ||
...(keys.reduce((o, k) => ({ ...o, [k]: layers[k][i] }), {})), | ||
})) | ||
|
||
export const component = (props: ExampleViewerDurationProps): JSX.Element => { | ||
const x: NumericAccessor<Datum> = d => d.x | ||
const [y, setY] = useState<NumericAccessor<Datum>[]>() | ||
const [color, setColor] = useState<string[]>([]) | ||
|
||
const [legendItems, setLegendItems] = useState( | ||
keys.map((name, i) => ({ name, inactive: false, color: colors[i], cursor: 'pointer' })) | ||
) | ||
|
||
useEffect(() => { | ||
const updated = legendItems.reduce((obj, item) => { | ||
if (!item.inactive) obj.colors.push(item.color) | ||
obj.ys.push(d => (item.inactive ? null : d[item.name])) | ||
return obj | ||
}, { colors: new Array<string>(), ys: new Array<NumericAccessor<Datum>>() }) | ||
setY(updated.ys) | ||
setColor(updated.colors) | ||
}, [legendItems]) | ||
|
||
const updateItems = useCallback((_: BulletLegendItemInterface, index: number) => { | ||
const newItems = [...legendItems] | ||
newItems[index].inactive = !newItems[index].inactive | ||
setLegendItems(newItems) | ||
}, [legendItems]) | ||
|
||
const tooltipTemplate = useCallback((d: Datum): string => legendItems.map(item => ` | ||
<div style="font-size:12px;${item.inactive ? 'text-decoration:line-through;opacity:0.7;color:#ccc">' : '">'} | ||
<span style="color:${item.color};font-weight:${item.inactive ? '400' : '800'};">${item.name}</span>: ${d[item.name] ?? '-'} | ||
</div>` | ||
).join(''), [legendItems]) | ||
|
||
return ( | ||
<div style={{ margin: 50 }}> | ||
<style>{` | ||
.square-legend .${VisBulletLegendSelectors.item} { | ||
--vis-legend-item-spacing: 10px; | ||
padding: 2px 4px; | ||
} | ||
.line-legend .${VisBulletLegendSelectors.bullet} { width: 16px !important; } | ||
.line-legend .${VisBulletLegendSelectors.bullet} path { stroke-dasharray: 5 3; } | ||
`}</style> | ||
<div style={{ display: 'flex', width: 'max-content', padding: '10px 10px 0px 35px' }}> | ||
<VisBulletLegend className='square-legend' items={legendItems} bulletShape={BulletShape.Square} onLegendItemClick={updateItems}/> | ||
<VisBulletLegend className='line-legend' items={[{ name: 'No data', color: '#5558', shape: 'line' }]} /> | ||
</div> | ||
<VisXYContainer yDomain={[0, 10]} data={data} height={400} duration={props.duration}> | ||
<VisLine lineWidth={2} curveType={CurveType.Linear} x={x} y={y} color={color} interpolateMissingData/> | ||
<VisScatter size={2} x={x} y={y} color={color}/> | ||
<VisCrosshair template={tooltipTemplate} color={color}/> | ||
<VisTooltip/> | ||
<VisAxis type='x' tickFormat={(d: number) => `0${(Math.floor(d / 6)) + 1}:${d % 6}0pm`}/> | ||
<VisAxis type='y'/> | ||
</VisXYContainer> | ||
</div> | ||
) | ||
} |
85 changes: 85 additions & 0 deletions
85
packages/dev/src/examples/xy-components/line/patchy-line/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import React, { useState } from 'react' | ||
import { VisXYContainer, VisLine, VisAxis, VisScatter, VisCrosshair, VisTooltip, VisAnnotations } from '@unovis/react' | ||
import { CurveType } from '@unovis/ts' | ||
|
||
import { ExampleViewerDurationProps } from '@src/components/ExampleViewer' | ||
|
||
import s from './style.module.css' | ||
|
||
export const title = 'Patchy Line Chart' | ||
export const subTitle = 'Various test cases' | ||
|
||
type TestCase = { | ||
title: string; | ||
data: (number | undefined | null)[]; | ||
} | ||
|
||
const testCases: TestCase[] = [ | ||
{ title: 'Gaps in middle', data: [3, 1, undefined, 7, undefined, 1, 1, undefined, 0.5, 4] }, | ||
{ title: 'Longer gaps', data: [2, 3, undefined, undefined, undefined, 12, 10, undefined, undefined, 2] }, | ||
{ title: 'Gaps at ends', data: [7, undefined, 9, 10, 7, 4, 5, 2, undefined, 10] }, | ||
{ title: 'Gaps at true ends', data: [undefined, 2, 10, 4, 5, 2, 6, 2, 3, undefined] }, | ||
{ title: 'Gaps surrounding single point', data: [5, 3, 6, undefined, 2, undefined, 10, 8, 9, 5] }, | ||
{ title: 'All undefined', data: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined] }, | ||
{ title: 'Single point', data: [undefined, undefined, undefined, undefined, 10, undefined] }, | ||
{ title: 'Missing every other point', data: [3, undefined, 12, undefined, 7, undefined, 5, undefined, 12] }, | ||
{ title: 'Includes undefined and null values', data: [3, 5, undefined, 6, 7, null, 9, 10, undefined, 4] }, | ||
|
||
] | ||
|
||
export const component = (props: ExampleViewerDurationProps): JSX.Element => { | ||
type Datum = Record<string, number> | ||
const combined = Array.from({ length: 10 }, (_, i) => ({ | ||
x: i, | ||
...(testCases.reduce((obj, d, j) => ({ | ||
...obj, | ||
[`y${j}`]: d.data[i], | ||
}), {})), | ||
})) | ||
const x = (d: Datum): number => d.x | ||
const getY = (i: number) => (d: Datum) => d[`y${i}`] | ||
|
||
const fallbacks = [undefined, 0, 5, 10] | ||
const [fallbackValue, setFallbackValue] = useState(fallbacks[0]) | ||
const [interpolation, setInterpolation] = useState(true) | ||
const [showScatter, setShowScatter] = useState(true) | ||
|
||
return ( | ||
<div className={s.patchyLineExample}> | ||
<div className={s.inputs}> | ||
<label> | ||
Fallback value: | ||
<select onChange={e => setFallbackValue(fallbacks[Number(e.target.value)])}> | ||
{fallbacks.map((o, i) => <option value={i}>{String(o)}</option>)} | ||
</select> | ||
</label> | ||
<label> | ||
Interpolate:<input type='checkbox' checked={interpolation} onChange={e => setInterpolation(e.target.checked)}/> | ||
</label> | ||
<label> | ||
Show Scatter: <input type='checkbox' checked={showScatter} onChange={e => setShowScatter(e.target.checked)}/> | ||
</label> | ||
</div> | ||
<div className={s.singleLines}> | ||
{testCases.map((val, i) => ( | ||
<VisXYContainer<Datum> data={combined} key={i} xDomain={[-0.2, 9.2]} yDomain={[0, 15]} height={200} width='100%'> | ||
<VisAnnotations items={[{ content: val.title, x: '50%', y: 0, textAlign: 'center' }]}/> | ||
<VisLine | ||
curveType={CurveType.Linear} | ||
duration={props.duration} | ||
fallbackValue={fallbackValue} | ||
interpolateMissingData={interpolation} | ||
x={x} | ||
y={getY(i)} | ||
/> | ||
{showScatter && <VisScatter excludeFromDomainCalculation size={2} x={x} y={d => getY(i)(d) ?? undefined}/>} | ||
<VisCrosshair template={(d: Datum) => `${d.x}, ${getY(i)(d)}`} color='var(--vis-color0)' strokeWidth='1px'/> | ||
<VisTooltip/> | ||
<VisAxis type='x'/> | ||
<VisAxis type='y' domainLine={false}/> | ||
</VisXYContainer> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
packages/dev/src/examples/xy-components/line/patchy-line/style.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.patchyLineExample { | ||
width: 100%; | ||
} | ||
|
||
.inputs { | ||
font-family: 'Courier New', Courier, monospace; | ||
font-size: smaller; | ||
margin-bottom: 12px; | ||
} | ||
|
||
.inputs > label { | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.singleLines { | ||
display: grid; | ||
width: 100%; | ||
grid-template-columns: repeat(3, 1fr); | ||
column-gap: 10px; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
/** Data type for Line Generator: [x, y, defined] */ | ||
export type LineDatum = { x: number; y: number; value: number | null | undefined; defined: boolean } | ||
export type LineData = { values: LineDatum[]; defined: boolean; visible: boolean } | ||
export type LineData = { values: LineDatum[]; gaps: LineDatum[]; defined: boolean; visible: boolean } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters