Add subject, label, annotation

This commit is contained in:
Jared He 2022-08-17 22:45:06 -04:00
parent 3f5439cb7d
commit 42638cbdb5
6 changed files with 320 additions and 20 deletions

View File

@ -0,0 +1,21 @@
.timebox {
margin: 0;
}
.timebox:hover {
background-color: blueviolet;
}
.timeboxHover {
background-color: blue;
}
.textbox {
background-color: maroon;
padding: 5px;
}
.textboxText {
overflow-y: scroll;
word-break: break-all;
}

View File

@ -1,4 +1,14 @@
import React from "react";
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";
interface TimelineData {
time: string;
@ -7,21 +17,149 @@ interface TimelineData {
interface TimelineProps {
data: TimelineData[];
/** Distance between consecutive nodes on timeline, in pixels */
/** 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. */
nodeGap?: number;
/** Whether the time is transformed to uppercase */
isTimeUppercase?: boolean;
/** 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;
className?: string;
}
export default function Timeline({
data,
nodeGap = 40,
width,
height,
padding = 20,
timelinePosition = width / 2,
nodeGap = data.length > 1
? (height - 2 * padding) / (data.length - 1)
: height - 2 * padding,
isTimeUppercase = true,
timeXOffset = -30,
timeWidth = 100,
textXOffset = 30,
textWidth = 100,
textHeight = 100,
className,
}: TimelineProps) {
return <div></div>;
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>
);
}
function TimelineSection() {
return <div></div>;
interface TimelineAnnotationProps {
positionData: {
x: number;
y: number;
time: string;
text: string;
};
timeXOffset: number;
timeWidth: number;
textXOffset: number;
textWidth: number;
textHeight: number;
}
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>
);
}

View File

@ -67,20 +67,20 @@ export const moreMockCategoricalData = [
export const mockTimelineData = [
{
time: "Fall 2018",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
time: "Problem 1",
text: "Overflow-y scroll text not supported by svg",
},
{
time: "Winter 2019",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim venia",
time: "Problem 2",
text: "Hover effects don't seem to work with <foreignObject>",
},
{
time: "Spring 2019",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in volupta",
time: "Problem 3",
text: "Need to add a max-height to these boxes",
},
{
time: "Fall 2020",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
time: "Problem 4",
text: "Styling",
},
{
time: "Winter 2020",
@ -88,6 +88,6 @@ export const mockTimelineData = [
},
{
time: "Spring 2020",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in repre",
text: "Lorem ipsum doldolor in repre",
},
];

134
package-lock.json generated
View File

@ -5,9 +5,9 @@
"requires": true,
"packages": {
"": {
"name": "cs-2022-class-profile",
"version": "0.1.0",
"dependencies": {
"@visx/annotation": "^2.12.2",
"@visx/axis": "^2.10.0",
"@visx/event": "^2.6.0",
"@visx/grid": "^2.10.0",
@ -886,6 +886,63 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@visx/annotation": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-2.12.2.tgz",
"integrity": "sha512-NhIexNL2QJKc5escOpCe5apNdqBUqmQzGLqc40L7GslYuS3KgxtMa4tdpI+WCct8b/EK3fyQmN9oqG0H5HTY9A==",
"dependencies": {
"@types/react": "*",
"@visx/drag": "2.10.0",
"@visx/group": "2.10.0",
"@visx/point": "2.6.0",
"@visx/shape": "2.12.2",
"@visx/text": "2.12.2",
"classnames": "^2.3.1",
"prop-types": "^15.5.10",
"react-use-measure": "^2.0.4"
},
"peerDependencies": {
"react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/annotation/node_modules/@visx/shape": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.12.2.tgz",
"integrity": "sha512-4gN0fyHWYXiJ+Ck8VAazXX0i8TOnLJvOc5jZBnaJDVxgnSIfCjJn0+Nsy96l9Dy/bCMTh4DBYUBv9k+YICBUOA==",
"dependencies": {
"@types/d3-path": "^1.0.8",
"@types/d3-shape": "^1.3.1",
"@types/lodash": "^4.14.172",
"@types/react": "*",
"@visx/curve": "2.1.0",
"@visx/group": "2.10.0",
"@visx/scale": "2.2.2",
"classnames": "^2.3.1",
"d3-path": "^1.0.5",
"d3-shape": "^1.2.0",
"lodash": "^4.17.21",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/annotation/node_modules/@visx/text": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-2.12.2.tgz",
"integrity": "sha512-Sv9YEolggfv2Nf6+l28ESG3VXVR1+s4u/Cz17QpgOxygcbOM8LfLtriWtBsBMKdMbYKeUpoUro0clx55TUwzew==",
"dependencies": {
"@types/lodash": "^4.14.172",
"@types/react": "*",
"classnames": "^2.3.1",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",
"reduce-css-calc": "^1.3.0"
},
"peerDependencies": {
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/axis": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.10.0.tgz",
@ -927,6 +984,20 @@
"d3-shape": "^1.0.6"
}
},
"node_modules/@visx/drag": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/drag/-/drag-2.10.0.tgz",
"integrity": "sha512-1G7ABfue8/Jn7tHxEPsDK+84Jbgej3Cqgi8FHaV15XlDRlaWs/fDNz4ECdJUGvhXuXLYCpaWFzhD1HaSEDJL1g==",
"dependencies": {
"@types/react": "*",
"@visx/event": "2.6.0",
"@visx/point": "2.6.0",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/event": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/event/-/event-2.6.0.tgz",
@ -4975,6 +5046,56 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"@visx/annotation": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-2.12.2.tgz",
"integrity": "sha512-NhIexNL2QJKc5escOpCe5apNdqBUqmQzGLqc40L7GslYuS3KgxtMa4tdpI+WCct8b/EK3fyQmN9oqG0H5HTY9A==",
"requires": {
"@types/react": "*",
"@visx/drag": "2.10.0",
"@visx/group": "2.10.0",
"@visx/point": "2.6.0",
"@visx/shape": "2.12.2",
"@visx/text": "2.12.2",
"classnames": "^2.3.1",
"prop-types": "^15.5.10",
"react-use-measure": "^2.0.4"
},
"dependencies": {
"@visx/shape": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.12.2.tgz",
"integrity": "sha512-4gN0fyHWYXiJ+Ck8VAazXX0i8TOnLJvOc5jZBnaJDVxgnSIfCjJn0+Nsy96l9Dy/bCMTh4DBYUBv9k+YICBUOA==",
"requires": {
"@types/d3-path": "^1.0.8",
"@types/d3-shape": "^1.3.1",
"@types/lodash": "^4.14.172",
"@types/react": "*",
"@visx/curve": "2.1.0",
"@visx/group": "2.10.0",
"@visx/scale": "2.2.2",
"classnames": "^2.3.1",
"d3-path": "^1.0.5",
"d3-shape": "^1.2.0",
"lodash": "^4.17.21",
"prop-types": "^15.5.10"
}
},
"@visx/text": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-2.12.2.tgz",
"integrity": "sha512-Sv9YEolggfv2Nf6+l28ESG3VXVR1+s4u/Cz17QpgOxygcbOM8LfLtriWtBsBMKdMbYKeUpoUro0clx55TUwzew==",
"requires": {
"@types/lodash": "^4.14.172",
"@types/react": "*",
"classnames": "^2.3.1",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",
"reduce-css-calc": "^1.3.0"
}
}
}
},
"@visx/axis": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.10.0.tgz",
@ -5009,6 +5130,17 @@
"d3-shape": "^1.0.6"
}
},
"@visx/drag": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/drag/-/drag-2.10.0.tgz",
"integrity": "sha512-1G7ABfue8/Jn7tHxEPsDK+84Jbgej3Cqgi8FHaV15XlDRlaWs/fDNz4ECdJUGvhXuXLYCpaWFzhD1HaSEDJL1g==",
"requires": {
"@types/react": "*",
"@visx/event": "2.6.0",
"@visx/point": "2.6.0",
"prop-types": "^15.5.10"
}
},
"@visx/event": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/event/-/event-2.6.0.tgz",

View File

@ -15,15 +15,16 @@
"lint:fix": "eslint \"{pages,components}/**/*.{js,ts,tsx,jsx}\" --quiet --fix"
},
"dependencies": {
"@visx/annotation": "^2.12.2",
"@visx/axis": "^2.10.0",
"@visx/event": "^2.6.0",
"@visx/grid": "^2.10.0",
"@visx/group": "^2.10.0",
"@visx/scale": "^2.2.2",
"@visx/shape": "^2.10.0",
"@visx/text": "^2.10.0",
"@visx/tooltip": "^2.10.0",
"@visx/wordcloud": "^2.10.0",
"@visx/text": "^2.10.0",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0"

View File

@ -1,8 +1,13 @@
import Timeline from "@/components/Timeline";
import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph";
import { mockCategoricalData, moreMockCategoricalData } from "data/mocks";
import {
mockCategoricalData,
moreMockCategoricalData,
mockTimelineData,
} from "data/mocks";
import React from "react";
import Timeline from "@/components/Timeline";
import { ColorPalette } from "../components/ColorPalette";
import { WordCloud } from "../components/WordCloud";
@ -60,7 +65,10 @@ export default function Home() {
value: word.value,
}))}
/>
<Timeline data={mockTimelineData}/>
<h2>
<code>{"<Timeline />"}</code>
</h2>
<Timeline data={mockTimelineData} width={400} height={1200} />
</div>
);
}