2022-08-17 22:45:06 -04:00
|
|
|
import {
|
|
|
|
Connector,
|
|
|
|
CircleSubject,
|
|
|
|
Annotation,
|
|
|
|
HtmlLabel,
|
|
|
|
} from "@visx/annotation";
|
|
|
|
import { Group } from "@visx/group";
|
|
|
|
import { LinePath } from "@visx/shape";
|
|
|
|
import React, { useState } from "react";
|
|
|
|
|
|
|
|
import styles from "./Timeline.module.css";
|
2022-07-27 20:43:23 -04:00
|
|
|
|
2022-08-09 18:38:28 -04:00
|
|
|
interface TimelineData {
|
|
|
|
time: string;
|
|
|
|
text: string;
|
|
|
|
}
|
2022-07-27 20:43:23 -04:00
|
|
|
|
|
|
|
interface TimelineProps {
|
2022-08-09 18:38:28 -04:00
|
|
|
data: TimelineData[];
|
2022-08-17 22:45:06 -04:00
|
|
|
/** Width of the entire timeline, in pixels. */
|
|
|
|
width: number;
|
|
|
|
/** Height of the entire timeline, in pixels. */
|
|
|
|
height: number;
|
|
|
|
/** Padding top and bottom, in pixels. */
|
|
|
|
padding: number;
|
|
|
|
/** Location of timeline width-wise, in pixels. Default is width / 2. */
|
|
|
|
timelinePosition?: number;
|
|
|
|
/** Distance between consecutive nodes on timeline, in pixels. Default is evenly spaced. padding is not calculated if user specifies this value. */
|
2022-08-09 18:38:28 -04:00
|
|
|
nodeGap?: number;
|
|
|
|
/** Whether the time is transformed to uppercase */
|
|
|
|
isTimeUppercase?: boolean;
|
2022-08-17 22:45:06 -04:00
|
|
|
/** Offset between the line and the time label, in pixels (negative for left offset, positive for right offset). */
|
|
|
|
timeXOffset?: number;
|
|
|
|
/** Width of time label, in pixels. */
|
|
|
|
timeWidth?: number;
|
|
|
|
/** Offset between the line and the text label, in pixels (negative for left offset, positive for right offset). */
|
|
|
|
textXOffset?: number;
|
|
|
|
/** Width of text label, in pixels. */
|
|
|
|
textWidth?: number;
|
|
|
|
/** Height of text label, in pixels. */
|
|
|
|
textHeight?: number;
|
2022-08-09 18:38:28 -04:00
|
|
|
className?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function Timeline({
|
|
|
|
data,
|
2022-08-17 22:45:06 -04:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
padding = 20,
|
|
|
|
timelinePosition = width / 2,
|
|
|
|
nodeGap = data.length > 1
|
|
|
|
? (height - 2 * padding) / (data.length - 1)
|
|
|
|
: height - 2 * padding,
|
2022-08-09 18:38:28 -04:00
|
|
|
isTimeUppercase = true,
|
2022-08-17 22:45:06 -04:00
|
|
|
timeXOffset = -30,
|
|
|
|
timeWidth = 100,
|
|
|
|
textXOffset = 30,
|
|
|
|
textWidth = 100,
|
|
|
|
textHeight = 100,
|
|
|
|
className,
|
2022-08-09 18:38:28 -04:00
|
|
|
}: TimelineProps) {
|
2022-08-17 22:45:06 -04:00
|
|
|
const dataWithPositions = data.map((data, i) => ({
|
|
|
|
x: timelinePosition,
|
|
|
|
y: i * nodeGap + padding,
|
|
|
|
time: isTimeUppercase ? data.time.toUpperCase() : data.time,
|
|
|
|
text: data.text,
|
|
|
|
}));
|
|
|
|
|
|
|
|
return (
|
|
|
|
<svg className={className} width={width} height={height}>
|
|
|
|
<LinePath
|
|
|
|
data={dataWithPositions.map((data) => [data.x, data.y])}
|
|
|
|
stroke="#fff"
|
|
|
|
strokeWidth={10}
|
|
|
|
/>
|
|
|
|
{dataWithPositions.map((data, i) => (
|
|
|
|
<TimelineAnnotation
|
|
|
|
positionData={data}
|
|
|
|
timeXOffset={timeXOffset}
|
|
|
|
timeWidth={timeWidth}
|
|
|
|
textXOffset={textXOffset}
|
|
|
|
textWidth={textWidth}
|
|
|
|
textHeight={textHeight}
|
|
|
|
key={`${data.time}${i}`}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</svg>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TimelineAnnotationProps {
|
|
|
|
positionData: {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
time: string;
|
|
|
|
text: string;
|
|
|
|
};
|
|
|
|
timeXOffset: number;
|
|
|
|
timeWidth: number;
|
|
|
|
textXOffset: number;
|
|
|
|
textWidth: number;
|
|
|
|
textHeight: number;
|
2022-07-27 20:43:23 -04:00
|
|
|
}
|
|
|
|
|
2022-08-17 22:45:06 -04:00
|
|
|
function TimelineAnnotation({
|
|
|
|
positionData,
|
|
|
|
timeXOffset,
|
|
|
|
timeWidth,
|
|
|
|
textXOffset,
|
|
|
|
textWidth,
|
|
|
|
textHeight,
|
|
|
|
}: TimelineAnnotationProps) {
|
|
|
|
const [hover, setHover] = useState(false);
|
|
|
|
|
|
|
|
function handleMouseEnter() {
|
|
|
|
setHover(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleMouseLeave() {
|
|
|
|
setHover(false);
|
|
|
|
}
|
|
|
|
console.log(hover);
|
|
|
|
return (
|
|
|
|
<Group>
|
|
|
|
<Annotation x={positionData.x} y={positionData.y} dx={timeXOffset} dy={0}>
|
|
|
|
<Connector />
|
|
|
|
<HtmlLabel showAnchorLine={false} verticalAnchor="start">
|
|
|
|
<h1
|
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
className={`${styles.timebox} ${hover ? styles.timeboxHover : ""}`}
|
|
|
|
style={{ width: timeWidth }}
|
|
|
|
>
|
|
|
|
{positionData.time}
|
|
|
|
</h1>
|
|
|
|
</HtmlLabel>
|
|
|
|
</Annotation>
|
|
|
|
<Annotation x={positionData.x} y={positionData.y} dx={textXOffset} dy={0}>
|
|
|
|
<Connector />
|
|
|
|
<CircleSubject
|
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
radius={10}
|
|
|
|
fill="#000"
|
|
|
|
/>
|
|
|
|
<CircleSubject radius={4} fill="red" />
|
|
|
|
<HtmlLabel showAnchorLine={false} verticalAnchor="start">
|
|
|
|
<div
|
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
className={styles.textbox}
|
|
|
|
style={{ width: textWidth, height: textHeight }}
|
|
|
|
>
|
|
|
|
<p
|
|
|
|
className={styles.textboxText}
|
|
|
|
style={{ maxHeight: textHeight - 10 }}
|
|
|
|
>
|
|
|
|
{positionData.text}
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</HtmlLabel>
|
|
|
|
</Annotation>
|
|
|
|
</Group>
|
|
|
|
);
|
2022-07-27 20:43:23 -04:00
|
|
|
}
|