Skip to content

Commit

Permalink
feat: Timeline Graph Component
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydrichards committed Jul 24, 2020
1 parent 4f4da57 commit 16f1afd
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 73 deletions.
28 changes: 24 additions & 4 deletions components/Contact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Intro } from './layout/StyledLayoutComponents';
import LinkedInIcon from '@material-ui/icons/LinkedIn';
import GitHubIcon from '@material-ui/icons/GitHub';
import InstagramIcon from '@material-ui/icons/Instagram';
import TwitterIcon from '@material-ui/icons/Twitter';
import SendIcon from '@material-ui/icons/Send';
import { Formik, Form } from 'formik';
import { MyTextField } from './formik/TextField';
Expand All @@ -25,7 +26,7 @@ const validationSchema = yup.object({

const Contact = () => {
return (
<Grid container spacing={2}>
<Grid id='Contact' container spacing={2}>
<Grid item xs={12} md={6}>
<Card
elevation={0}
Expand All @@ -46,13 +47,32 @@ const Contact = () => {
</Intro>
</CardContent>
<CardActions style={{ justifyContent: 'center' }}>
<IconButton aria-label='linkedIn'>
<IconButton
target='_blank'
href='https://www.linkedin.com/in/lloyddrichards/'
aria-label='linkedIn'
>
<LinkedInIcon />
</IconButton>
<IconButton aria-label='Instagram'>
<IconButton
target='_blank'
href='https://www.instagram.com/lloydrichardsdesign/'
aria-label='Instagram'
>
<InstagramIcon />
</IconButton>
<IconButton aria-label='Github'>
<IconButton
target='_blank'
href='https://twitter.com/LRichardsDesign'
aria-label='Twitter'
>
<TwitterIcon />
</IconButton>
<IconButton
target='_blank'
href='https://github.com/lloydrichards'
aria-label='Github'
>
<GitHubIcon />
</IconButton>
</CardActions>
Expand Down
4 changes: 4 additions & 0 deletions components/ProjectData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const ProjectData: Array<Project> = [
description: 'Data Visualization looking at plastic recycling',
href: '/lifeofplastic',
category: ['Code', 'Design'],
image: '/public/images/thumb_lifeofplastic.png',
github: 'https://github.com/interactivethings/LifeofPlastic',
link: 'https://life-of-plastic.interactivethings.io/',
},
Expand All @@ -18,6 +19,7 @@ export const ProjectData: Array<Project> = [
description: 'Data Visualization looking at Vietnamese Cuisine',
href: '/lifeofplastic',
category: ['Design'],
image: '/public/images/thumb_lifeofplastic.png',
link: 'https://life-of-plastic.interactivethings.io/',
},
{
Expand All @@ -27,6 +29,7 @@ export const ProjectData: Array<Project> = [
description: 'Cateloging the presentations from the Visualized Conference',
href: '/lifeofplastic',
category: ['Design', 'Code'],
image: '/public/images/thumb_lifeofplastic.png',
link: 'https://life-of-plastic.interactivethings.io/',
},
{
Expand All @@ -36,6 +39,7 @@ export const ProjectData: Array<Project> = [
description: 'Cateloging the presentations from the Visualized Conference',
href: '/lifeofplastic',
category: ['Design', 'Code'],
image: '/public/images/thumb_lifeofplastic.png',
link: 'https://life-of-plastic.interactivethings.io/',
},
];
1 change: 1 addition & 0 deletions components/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Project {
description: string;
href: string;
category: Array<keyof Category>;
image: string;
github?: string;
link?: string;
}
Expand Down
224 changes: 174 additions & 50 deletions components/d3/TimeLine.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,205 @@
import React, { useRef, useState, useEffect } from 'react';
import { select, scaleTime, axisLeft } from 'd3';
import React, { useRef, useEffect } from 'react';
import { select, scaleTime, axisLeft, timeYear, timeFormat, min } from 'd3';
import { DarkLinenPaper } from '../layout/StyledLayoutComponents';

export interface Occupation {
id: string;
selected: boolean;
title: string;
category: keyof Category;
tag: Array<keyof Tag>;
start: Date;
end: Date;
}

export interface LifeEvent {
id: string;
title: string;
date: Date;
}

interface Category {
Education: boolean;
Work: boolean;
Volunteer: boolean;
}

interface Tag {
architect: boolean;
landscaper: boolean;
baker: boolean;
coder: boolean;
farmer: boolean;
manager: boolean;
}
const dim = {
width: 400,
height: 800,
marginLeft: 100,
background: '#f6f3f0',
};

const TimeLine = () => {
interface Props {
width: number;
height: number;
occupations: Array<Occupation>;
events: Array<LifeEvent>;
background: string;
}

const TimeLine: React.FC<Props> = ({
width,
height,
occupations,
events,
background,
}) => {
const svgRef = useRef(null);
const [data] = useState(timelineData);

const lookupInRange = (
corner: Date,
allValues: Array<Occupation>
): boolean => {
let result: boolean = false;
allValues.forEach((i) => {
if (corner > i.start && corner < i.end) {
result = true;
return true;
}
});
return result;
};

const categoryColor = (category: keyof Category) => {
switch (category) {
case 'Work':
return '#CBE0F2';
case 'Education':
return '#EECEC9';
case 'Volunteer':
return '#F0E2CE';
}
};

const orderInRange = (
value: Occupation,
backArg: any,
midArg: any,
frontArg: any
): boolean | number | string => {
if (
lookupInRange(value.end, occupations) &&
lookupInRange(value.start, occupations)
) {
return backArg;
} else if (lookupInRange(value.start, occupations)) {
return midArg;
} else {
return frontArg;
}
};

useEffect(() => {
const svg = select(svgRef.current);

const yScale = scaleTime()
.domain([new Date('2018-01-01'), new Date('2020-07-22')])
.range([dim.height, 0]);
.domain([
min(occupations, (d) => d.start) || new Date('1988-04-18'),
new Date(),
])
.range([height, 0]);

const yAxis = axisLeft<any>(yScale);
const yAxis = axisLeft<any>(yScale)
.ticks(timeYear, 1)
.tickFormat(timeFormat('%Y'));

const Axis = svg.append('g');
Axis.style('transform', `translateX(${dim.marginLeft}px)`).call(yAxis);

const Boxes = svg.append('g');
Boxes.selectAll('rect')
.data(data)
.join('rect')
.attr('x', 100)
const BackBoxes = svg
.append('g')
.selectAll('rect')
.data(occupations.filter((d) => orderInRange(d, true, false, false)));
BackBoxes.join('rect')
.transition()
.attr('x', 115)
.attr('y', (value) => yScale(+value.end))
.attr('width', 35)
.attr('height', (value) => yScale(+value.start) - yScale(+value.end))
.attr('fill', (value) =>
value.selected ? categoryColor(value.category) : 'none'
)
.attr('stroke', (value) =>
value.selected ? background : categoryColor(value.category)
)
.attr('stroke-width', '2px');

const MidBoxes = svg
.append('g')
.selectAll('rect')
.data(occupations.filter((d) => orderInRange(d, false, true, false)));
MidBoxes.join('rect')
.transition()
.attr('x', 110)
.attr('y', (value) => yScale(+value.end))
.attr('width', 30)
.attr('height', (value) => yScale(+value.start) - yScale(+value.end))
.attr('fill', (value) =>
value.selected ? categoryColor(value.category) : 'none'
)
.attr('stroke', (value) =>
value.selected ? background : categoryColor(value.category)
)
.attr('stroke-width', '2px');

const FrontBoxes = svg
.append('g')
.selectAll('rect')
.data(occupations.filter((d) => orderInRange(d, false, false, true)));
FrontBoxes.join('rect')
.transition()
.attr('x', 105)
.attr('y', (value) => yScale(+value.end))
.attr('width', (_, i) => i * 10 + 25)
.attr('width', 25)
.attr('height', (value) => yScale(+value.start) - yScale(+value.end))
.attr('fill', 'teal')
.attr('stroke', 'lightgrey');
}, [data]);
.attr('fill', (value) =>
value.selected ? categoryColor(value.category) : 'none'
)
.attr('stroke', (value) =>
value.selected ? background : categoryColor(value.category)
)
.attr('stroke-width', '2px');

const LifeEvents = svg.append('g').selectAll('line').data(events);
LifeEvents.join('line')
.transition()
.attr('x1', 25)
.attr('x2', width - 25)
.attr('y1', (value) => yScale(value.date))
.attr('y2', (value) => yScale(value.date))
.attr('stroke', 'black')
.attr('stroke-dasharray', '5,10,5');

LifeEvents.join('text')
.transition()
.attr('x', 5)
.attr('y', (value) => yScale(value.date) - 5)
.text((value) => value.title)
.attr('font-family', 'Josefin Sans, serif')
.attr('font-size', '0.6em')
.attr('fill', DarkLinenPaper);
}, [occupations]);

return (
<div>
<svg
style={{ background: 'lightgrey', overflow: 'visible' }}
width={dim.width}
height={dim.height}
style={{ background, overflow: 'visible' }}
width={width}
height={height}
ref={svgRef}
/>
</div>
);
};

export default TimeLine;

const timelineData = [
{
id: '001',
title: 'number 1',
category: 'Code',
start: new Date('2020-01-01'),
end: new Date('2020-03-23'),
},
{
id: '002',
title: 'number 2',
category: 'Design',
start: new Date('2018-06-02'),
end: new Date('2019-01-01'),
},
{
id: '003',
title: 'number 3',
category: 'Other',
start: new Date('2019-04-29'),
end: new Date('2019-12-31'),
},
{
id: '004',
title: 'number 4',
category: 'Other',
start: new Date('2019-02-29'),
end: new Date('2019-5-31'),
},
];
Loading

0 comments on commit 16f1afd

Please sign in to comment.