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

[WIP] Component | Timeline: Enhancements and fixes #536

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5c3fc3d
Component | Timeline: Improving label positioning and clipping
rokotyan Feb 20, 2025
a10887e
Dev | Examples | Timeline: Add label alignment and X domain clip path…
rokotyan Feb 19, 2025
9586fd9
Component | Timeline: Refactoring
rokotyan Feb 20, 2025
c13ae06
Dev | Examples | Timeline: Refactoring
rokotyan Feb 20, 2025
04a620a
Component | Timeline: Support for line icons
rokotyan Feb 21, 2025
0b19cb4
Dev | Examples | Timeline: Line Icons
rokotyan Feb 21, 2025
6e6376e
Component | Timeline: Update row label grouping and formatting
rokotyan Feb 27, 2025
8bb8742
Dev | Examples | Timeline | Label Alignment: Adding label custom style
rokotyan Feb 28, 2025
e469c69
Component | Timeline: Support for arrows
rokotyan Mar 3, 2025
1b50ddc
Dev | Examples | Timeline: Add timeline arrows example
rokotyan Mar 3, 2025
4e38d1d
Component | Timeline: Add configurable animation positions for line t…
rokotyan Mar 4, 2025
797ba71
Dev | Examples | Timeline: Add animation example, update tooltip, and…
rokotyan Mar 4, 2025
ac473da
Component | Timeline | Fixes: Update icon data binding, ordinal scale…
rokotyan Mar 4, 2025
da0570f
Component | Timeline: Implementing bleed for line icons
rokotyan Mar 11, 2025
52307d7
Dev | Examples | Timeline | Icons: Updating to test bleed calculation
rokotyan Mar 11, 2025
67a90cd
Dev | Examples | Timeline | Negative Lengths: Adding lineCap
rokotyan Mar 11, 2025
b03e7a0
Component | Timeline: Add hover style to timeline lines
rokotyan Mar 12, 2025
77ba131
Component | Timeline: Row icons
rokotyan Mar 12, 2025
ae41ab7
Dev | Examples | Timeline | Row Icons: Adding example with row icons
rokotyan Mar 12, 2025
f50baca
Component | Timeline: Add bleed calculation for small rounded lines
rokotyan Mar 13, 2025
8cea6cf
Dev | Examples | Timeline: Dedicated bleed example
rokotyan Mar 18, 2025
ab4aa52
Component | Timeline: Bendable arrows
rokotyan Mar 19, 2025
5062f2c
Component | Timeline | Refactor: Rename `lineLength` to `lineDuration`
rokotyan Mar 19, 2025
eac1ba7
Dev | Examples | Timeline | Arrows: Data transitions
rokotyan Mar 19, 2025
29c5afd
Component | Timeline: Misc tweaks
rokotyan Mar 19, 2025
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
2 changes: 1 addition & 1 deletion packages/dev/src/examples/xy-components/timeline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const transitionComponent: TransitionComponentProps<TimeDataRecord[]> = {
},
component: (props) => (
<VisXYContainer data={props.data}>
<VisTimeline x={d => d.timestamp} duration={props.duration}/>
<VisTimeline x={d => d.timestamp} duration={props.duration ?? 1000}/>
</VisXYContainer>
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useEffect } from 'react'
import { VisXYContainer, VisTimeline } from '@unovis/react'

import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'
import { TextAlign } from '@unovis/ts'

export const title = 'Animation Tweaking'
export const subTitle = 'Control enter/exit position'

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const [shouldRenderData, setShouldRenderData] = React.useState(true)
const intervalIdRef = React.useRef<NodeJS.Timeout | null>(null)
const data = Array(10).fill(0).map((_, i) => ({
timestamp: Date.now() + i * 1000 * 60 * 60 * 24,
length: 1000 * 60 * 60 * 24,
id: i.toString(),
type: `Row ${i}`,
lineWidth: 5 + Math.random() * 15,
}))

type Datum = typeof data[number]

useEffect(() => {
clearInterval(intervalIdRef.current!)
intervalIdRef.current = setInterval(() => {
setShouldRenderData(should => !should)
}, 2000)
}, [])

return (<>
<VisXYContainer<Datum>
data={shouldRenderData ? data : []}
height={300}
duration={1500}
>
<VisTimeline
id={(d) => d.id}
lineRow={(d: Datum) => d.type as string}
x={(d: Datum) => d.timestamp}
rowHeight={undefined}
lineWidth={(d) => d.lineWidth}
lineCap
showEmptySegments
showRowLabels
rowLabelTextAlign={TextAlign.Left}
duration={props.duration}
animationLineEnterPosition={[undefined, -110]}
animationLineExitPosition={[1000, undefined]}
/>
</VisXYContainer>
</>
)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useMemo, useState, useEffect } from 'react'
import { VisXYContainer, VisTimeline, VisAxis } from '@unovis/react'

import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'
import { Arrangement, TextAlign, TimelineArrow } from '@unovis/ts'

// Icons
import icon from './icon.svg?raw'

export const title = 'Timeline Arrows'
export const subTitle = 'Between the lines'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const generateData = (length: number) => Array(length).fill(0).map((_, i) => ({
timestamp: Date.now() + i * 1000 * 60 * 60 * 24 + Math.random() * 1000 * 60 * 60 * 24,
length: 1000 * 60 * 60 * 24,
id: i.toString(),
type: `Row ${i}`,
lineWidth: 5 + Math.random() * 15,
}))

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const lineIconSize = 25
const [data, setData] = useState(() => generateData(15))

useEffect(() => {
const interval = setInterval(() => { setData(generateData(12)) }, 6000)
return () => clearInterval(interval)
}, [])

type Datum = typeof data[number]

const arrows = data.map((d, i) => {
if (i === data.length - 1) return undefined

return {
id: `arrow-${i}`,
xSource: d.timestamp + d.length,
xSourceOffsetPx: 12,
xTargetOffsetPx: 0,
lineSourceId: d.id,
lineTargetId: data[i + 1].id,
lineSourceMarginPx: (lineIconSize - d.lineWidth) / 2,
lineTargetMarginPx: 2,
}
}).filter(Boolean) as TimelineArrow[]

const svgDefs = useMemo(() => `${icon}`, [])
return (<>
<VisXYContainer<Datum>
data={data}
height={400}
svgDefs={svgDefs}
>
<VisTimeline
id={(d) => d.id}
lineRow={(d: Datum) => d.type as string}
x={(d: Datum) => d.timestamp}
rowHeight={40}
lineWidth={(d) => d.lineWidth}
lineCap
showEmptySegments
showRowLabels
rowLabelTextAlign={TextAlign.Left}
duration={props.duration}
lineEndIcon={'#circle_check_filled'}
lineEndIconSize={lineIconSize}
lineStartIconColor={'#fff'}
lineEndIconColor={'rgb(38, 86, 201)'}
lineEndIconArrangement={Arrangement.Outside}
arrows={arrows}
/>
<VisAxis
type='x'
numTicks={3}
tickFormat={(x: number) => new Date(x).toDateString()}
duration={props.duration}
/>
</VisXYContainer>
</>
)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useMemo } from 'react'
import { VisXYContainer, VisTimeline, VisAxis, VisTooltip, VisCrosshair } from '@unovis/react'
import { Arrangement } from '@unovis/ts'

import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'

// Icons
import icon from './icon.svg?raw'

export const title = 'Timeline: Bleed'
export const subTitle = 'Automatic bleed calculation'

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const data = useMemo(() => [
{ timestamp: 0, duration: 20, row: 'Long Row' },
{ timestamp: 0, duration: 0, row: 'Empty Row 1' },
{ timestamp: 10, duration: 0, row: 'Empty Row 1' },
{ timestamp: 15, duration: 0, row: 'Empty Row 1' },
{ timestamp: 20, duration: 0, row: 'Empty Row 1' },
{ timestamp: 18, duration: 0, row: 'Empty Row 2' },
{ timestamp: 24, duration: 0, row: 'Empty Row 2' },
{ timestamp: 27, duration: 0, row: 'Empty Row 2' },
], [])


return (<>
<VisXYContainer data={data} height={200} svgDefs={icon}>
<VisTimeline<typeof data[number]>
lineRow={(d) => d.row as string}
lineDuration={(d) => d.duration}
x={(d) => d.timestamp}
rowHeight={50}
lineWidth={undefined}
showEmptySegments={true}
// lineEndIcon={'#circle_check_filled'}
lineStartIcon={'#circle_check_filled'}
// lineEndIconArrangement={Arrangement.Outside}
lineStartIconArrangement={Arrangement.Outside}
showRowLabels
duration={props.duration}
lineCap={true}
/>
<VisAxis type='x' numTicks={3} tickFormat={(x: number) => (new Date(x).getTime()).toString()} duration={props.duration}/>
<VisTooltip/>
</VisXYContainer>
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import React from 'react'
import React, { useMemo } from 'react'
import { VisXYContainer, VisTimeline, VisAxis, VisTooltip, VisCrosshair } from '@unovis/react'

import { TimeDataRecord, generateTimeSeries } from '@src/utils/data'
import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'

export const title = 'Timeline: Negative Lengths'
export const subTitle = 'Generated Data'
export const title = 'Timeline: Empty Segments'
export const subTitle = 'Small and Negative Lengths'

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const [showEmptySegments, toggleEmptySegments] = React.useState(true)
const data = generateTimeSeries(10).map((d, i) => ({
const [lineCap, setLineCap] = React.useState(false)
const data = useMemo(() => generateTimeSeries(50).map((d, i) => ({
...d,
length: [d.length, 0, -d.length][i % 3],
type: ['Positive', 'Zero', 'Negative'][i % 3],
}))
})), [])

return (<>
<label>Show empty segments: <input type='checkbox' checked={showEmptySegments} onChange={e => toggleEmptySegments(e.target.checked)}/></label>
<div><input type='checkbox' checked={showEmptySegments} onChange={e => toggleEmptySegments(e.target.checked)}/><label>Show empty segments</label></div>
<div><input type='checkbox' checked={lineCap} onChange={e => setLineCap(e.target.checked)}/><label>Rounded corners</label></div>
<VisXYContainer<TimeDataRecord> data={data} height={200}>
<VisTimeline x={(d: TimeDataRecord) => d.timestamp} rowHeight={50} showEmptySegments={showEmptySegments} showLabels duration={props.duration}/>
<VisTimeline
lineRow={(d: TimeDataRecord) => d.type as string}
x={(d: TimeDataRecord) => d.timestamp}
rowHeight={50}
showEmptySegments={showEmptySegments}
showRowLabels
duration={props.duration}
lineCap={lineCap}
/>
<VisAxis type='x' numTicks={3} tickFormat={(x: number) => new Date(x).toDateString()} duration={props.duration}/>
<VisCrosshair template={(d: TimeDataRecord) => `${Intl.DateTimeFormat().format(d.timestamp)}: ${Math.round(d.length / Math.pow(10, 7))}m`}/>
<VisTooltip/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react'
import { VisXYContainer, VisTimeline, VisAxis } from '@unovis/react'

import { TimeDataRecord, generateTimeSeries } from '@src/utils/data'
import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'
import { TextAlign } from '@unovis/ts'

export const title = 'Label Alignment & Style'
export const subTitle = 'X Domain, auto line width'

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const data = generateTimeSeries(25, 20, 10).map((d, i) => ({
...d,
type: `Row ${i}`,
}))

const xDomain = [data[0].timestamp + 100000, data[data.length - 1].timestamp - 10000] as [number, number]
return (<>
<VisXYContainer<TimeDataRecord>
data={data}
height={300}
xDomain={xDomain}
margin={{
left: 10,
right: 10,
}}
>
<VisTimeline
lineRow={(d: TimeDataRecord) => d.type as string}
x={(d: TimeDataRecord) => d.timestamp}
rowHeight={20}
lineWidth={undefined}
lineCap
showRowLabels
rowLabelTextAlign={TextAlign.Left}
duration={props.duration}
rowLabelStyle={rowLabel => rowLabel.label === 'Row 24'
? ({ fill: 'rgb(237, 116, 128)', cursor: 'pointer', 'text-decoration': 'underline', transform: 'translateX(5px)' })
: undefined
}
/>
<VisAxis
type='x'
numTicks={3}
tickFormat={(x: number) => new Date(x).toDateString()}
duration={props.duration}
/>
</VisXYContainer>
</>
)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useMemo } from 'react'
import { VisXYContainer, VisTimeline, VisAxis } from '@unovis/react'

import { generateTimeSeries } from '@src/utils/data'
import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index'
import { Arrangement, TextAlign } from '@unovis/ts'

// Icons
import icon from './icon.svg?raw'

export const title = 'Line Icons'
export const subTitle = 'Start / End icons'

export const component = (props: ExampleViewerDurationProps): JSX.Element => {
const data = generateTimeSeries(25, 20, 10).map((d, i) => ({
...d,
type: `Row ${i}`,
lineWidth: 5 + Math.random() * 15,
}))
type Datum = typeof data[number]

const svgDefs = useMemo(() => `${icon}`, [])
return (<>
<VisXYContainer<Datum>
data={data}
height={300}
svgDefs={svgDefs}
>
<VisTimeline
lineRow={(d: Datum) => d.type as string}
x={(d: Datum) => d.timestamp}
rowHeight={20}
lineWidth={(d) => d.lineWidth}
lineCap
rowLabelTextAlign={TextAlign.Left}
duration={props.duration}
lineStartIcon={'#circle_pending_filled'}
lineEndIcon={'#circle_check_filled'}
lineEndIconSize={25}
lineStartIconColor={'rgb(38, 86, 201)'}
lineEndIconColor={'rgb(38, 86, 201)'}
lineEndIconArrangement={Arrangement.Outside}
lineStartIconArrangement={Arrangement.Outside}
showRowLabels
/>
<VisAxis
type='x'
numTicks={3}
tickFormat={(x: number) => new Date(x).toDateString()}
duration={props.duration}
/>
</VisXYContainer>
</>
)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading