Add subject, label, annotation

pull/35/head
Jared He 8 months ago
parent 3f5439cb7d
commit 42638cbdb5
  1. 21
      components/Timeline.module.css
  2. 150
      components/Timeline.tsx
  3. 18
      data/mocks.ts
  4. 134
      package-lock.json
  5. 3
      package.json
  6. 14
      pages/playground.tsx

@ -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;
}

@ -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>
);
}
interface TimelineAnnotationProps {
positionData: {
x: number;
y: number;
time: string;
text: string;
};
timeXOffset: number;
timeWidth: number;
textXOffset: number;
textWidth: number;
textHeight: number;
}
function TimelineSection() {
return <div></div>;
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>
);
}

@ -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

@ -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",

@ -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"

@ -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>
);
}

Loading…
Cancel
Save