diff --git a/components/Contact.tsx b/components/Contact.tsx
index 9393388..54d5274 100644
--- a/components/Contact.tsx
+++ b/components/Contact.tsx
@@ -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';
@@ -25,7 +26,7 @@ const validationSchema = yup.object({
const Contact = () => {
return (
-
+
{
-
+
-
+
-
+
+
+
+
diff --git a/components/ProjectData.tsx b/components/ProjectData.tsx
index 9d533f6..345b8a1 100644
--- a/components/ProjectData.tsx
+++ b/components/ProjectData.tsx
@@ -8,6 +8,7 @@ export const ProjectData: Array = [
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/',
},
@@ -18,6 +19,7 @@ export const ProjectData: Array = [
description: 'Data Visualization looking at Vietnamese Cuisine',
href: '/lifeofplastic',
category: ['Design'],
+ image: '/public/images/thumb_lifeofplastic.png',
link: 'https://life-of-plastic.interactivethings.io/',
},
{
@@ -27,6 +29,7 @@ export const ProjectData: Array = [
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/',
},
{
@@ -36,6 +39,7 @@ export const ProjectData: Array = [
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/',
},
];
diff --git a/components/Projects.tsx b/components/Projects.tsx
index 4d97fd3..29050b9 100644
--- a/components/Projects.tsx
+++ b/components/Projects.tsx
@@ -19,6 +19,7 @@ export interface Project {
description: string;
href: string;
category: Array;
+ image: string;
github?: string;
link?: string;
}
diff --git a/components/d3/TimeLine.tsx b/components/d3/TimeLine.tsx
index d90f398..ce113d6 100644
--- a/components/d3/TimeLine.tsx
+++ b/components/d3/TimeLine.tsx
@@ -1,46 +1,201 @@
-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;
+ 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;
+ events: Array;
+ background: string;
+}
+
+const TimeLine: React.FC = ({
+ width,
+ height,
+ occupations,
+ events,
+ background,
+}) => {
const svgRef = useRef(null);
- const [data] = useState(timelineData);
+
+ const lookupInRange = (
+ corner: Date,
+ allValues: Array
+ ): 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(yScale);
+ const yAxis = axisLeft(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 (
@@ -48,34 +203,3 @@ const TimeLine = () => {
};
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'),
- },
-];
diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx
index d6aa7f5..4b47392 100644
--- a/components/layout/Navbar.tsx
+++ b/components/layout/Navbar.tsx
@@ -6,10 +6,10 @@ const Navbar = () => {
return (
{
padding: 14px 16px;
text-decoration: none;
color: #8b7a70;
- font-family: 'Josefin Slab', serif;
+ font-family: 'Josefin Sans', serif;
font-size: 1em;
}
a.title {
@@ -29,16 +29,75 @@ const Navbar = () => {
font-family: 'Raleway', sans-serif;
font-size: 1.5em;
}
+ /* The dropdown container */
+ .dropdown {
+ float: right;
+ overflow: hidden;
+ }
+
+ /* Dropdown button */
+ .dropdown .dropbtn {
+ color: #8b7a70;
+ font-family: 'Josefin Sans', serif;
+ font-size: 1em;
+ border: none;
+ outline: none;
+ padding: 14px 16px;
+ background-color: inherit;
+ margin: 0; /* Important for vertical align on mobile phones */
+ }
+
+ /* Dropdown content (hidden by default) */
+ .dropdown-content {
+ display: none;
+ position: absolute;
+ background-color: #f9f9f9;
+ min-width: 160px;
+ box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+ }
+
+ /* Links inside the dropdown */
+ .dropdown-content a {
+ float: none;
+ color: black;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+ text-align: left;
+ }
+
+ /* Add a grey background color to dropdown links on hover */
+ .dropdown-content a:hover {
+ background-color: #ddd;
+ }
+
+ /* Show the dropdown menu on hover */
+ .dropdown:hover .dropdown-content {
+ display: block;
+ }
`}
>
-
-
Lloyd Richards
-
-
About
-
Projects
-
Blog
-
CV
+
+
Lloyd Richards
+
Contact
+
CV
+
+
+
About
);
};
diff --git a/components/layout/StyledLayoutComponents.tsx b/components/layout/StyledLayoutComponents.tsx
index d5821bb..a330e34 100644
--- a/components/layout/StyledLayoutComponents.tsx
+++ b/components/layout/StyledLayoutComponents.tsx
@@ -1,5 +1,9 @@
import styled from '@emotion/styled';
+export const LinenPaper = '#f6f3f0';
+export const DarkLinenPaper = '#8B7A70';
+export const Unselected = "#E7DFD8"
+
export const FullWidthBackground = styled.div`
width: 100vw;
background: #f6f3f0;
@@ -24,6 +28,14 @@ export const H2 = styled.h2`
color: black;
font-family: 'Josefin Sans', serif;
font-size: 2em;
+ a {
+ text-decoration: none;
+ color: ${Unselected};
+ :hover {
+ text-decoration: underline;
+ color: ${DarkLinenPaper};
+ }
+ }
`;
export const H3 = styled.h2`
diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx
index 125a58a..d841568 100644
--- a/pages/blog/index.tsx
+++ b/pages/blog/index.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Grid, Card, Button, CardHeader } from '@material-ui/core';
-import {} from '../../components/layout/StyledLayoutComponents';
+import { H2 } from '../../components/layout/StyledLayoutComponents';
import { useRouter } from 'next/router';
import { BlogData } from '../../components/BlogData';
import Layout from '../../components/layout/Layout';
@@ -26,6 +26,7 @@ function Blog() {
return (
+ Blog
{posts.map((i) => (
diff --git a/pages/experiment/019.tsx b/pages/experiment/019.tsx
index c846473..8263d21 100644
--- a/pages/experiment/019.tsx
+++ b/pages/experiment/019.tsx
@@ -1,7 +1,9 @@
import React, { useRef, useEffect, useState } from 'react';
import Layout from '../../components/layout/Layout';
import * as d3 from 'd3';
-import TimeLine from '../../components/d3/TimeLine';
+import TimeLine, { Occupation, LifeEvent } from '../../components/d3/TimeLine';
+import { LinenPaper } from '../../components/layout/StyledLayoutComponents';
+import { Grid } from '@material-ui/core';
const Experiment019 = () => {
const svgRef = useRef(null);
@@ -70,9 +72,72 @@ const Experiment019 = () => {
as a list beside the timeline and then using D3, build the timeline and
draw lines that connect to the appropriate list item.
-
+
+
+
+
+
+
+ This feels really good. I've built a TimeLine.tsx
{' '}
+ component that can take in props for the data, including occupations
+ and events which it then renders into this horizontal timeline. The
+ graph scales to the size of the data and even orders the boxes and
+ colours them accoring to their overlap and props. So cool to see
+ this come together so nicely!
+
+
+
);
};
export default Experiment019;
+
+const eventSampleData: Array = [
+ { id: '001', title: 'Moved to Switzerland', date: new Date('2020-01-03') },
+];
+
+const occupationSampleData: Array = [
+ {
+ id: '001',
+ selected: true,
+ title: 'number 1',
+ category: 'Work',
+ tag: ['coder'],
+ start: new Date('2019-12-01'),
+ end: new Date('2020-03-23'),
+ },
+ {
+ id: '002',
+ selected: false,
+ title: 'number 2',
+ category: 'Education',
+ tag: ['coder'],
+ start: new Date('2018-06-02'),
+ end: new Date('2019-01-01'),
+ },
+ {
+ id: '003',
+ selected: true,
+ title: 'number 3',
+ category: 'Work',
+ tag: ['coder'],
+ start: new Date('2019-04-29'),
+ end: new Date('2019-12-31'),
+ },
+ {
+ id: '004',
+ selected: true,
+ title: 'number 4',
+ category: 'Volunteer',
+ tag: ['coder'],
+ start: new Date('2019-02-29'),
+ end: new Date('2019-06-31'),
+ },
+];
diff --git a/pages/index.tsx b/pages/index.tsx
index ae80eb0..531c433 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -49,7 +49,7 @@ const IndexPage: React.FC = () => (
-
+
About
@@ -84,12 +84,14 @@ const IndexPage: React.FC = () => (
-
-
Recent
+
-
+
CV
diff --git a/pages/portfolio/index.tsx b/pages/portfolio/index.tsx
new file mode 100644
index 0000000..e237494
--- /dev/null
+++ b/pages/portfolio/index.tsx
@@ -0,0 +1,153 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Grid,
+ Card,
+ CardContent,
+ CardActions,
+ Button,
+ ButtonGroup,
+} from '@material-ui/core';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import ExpandLessIcon from '@material-ui/icons/ExpandLess';
+import {
+ H3,
+ Description,
+ H2,
+} from '../../components/layout/StyledLayoutComponents';
+import { ProjectData } from '../../components/ProjectData';
+import Layout from '../../components/layout/Layout';
+
+export interface Project {
+ id: string;
+ date: Date;
+ title: string;
+ description: string;
+ href: string;
+ category: Array
;
+ github?: string;
+ link?: string;
+}
+
+interface Category {
+ Design: boolean;
+ Garden: boolean;
+ Code: boolean;
+ Other: boolean;
+}
+
+const Projects = () => {
+ const [currentProjects, setCurrentProjects] = useState>(
+ ProjectData.sort((a, b) => +b.date - +a.date)
+ );
+ const [categories, setCategories] = useState({
+ Design: true,
+ Garden: true,
+ Code: true,
+ Other: true,
+ });
+ const [expanded, setExpanded] = useState(false);
+ useEffect(() => {
+ setCurrentProjects(
+ ProjectData.filter((i) => i.category.some((r) => categories[r] === true))
+ );
+ }, [categories]);
+ return (
+
+ Portfolio
+
+
+ setCategories({
+ Design: true,
+ Garden: true,
+ Code: true,
+ Other: true,
+ })
+ }
+ style={{
+ color:
+ categories.Design && categories.Code && categories.Garden
+ ? '#8B7A70'
+ : '#F0ECE8',
+ }}
+ >
+ All
+
+
+ setCategories({ ...categories, Design: !categories.Design })
+ }
+ style={{
+ color: categories.Design ? '#8B7A70' : '#F0ECE8',
+ }}
+ >
+ Design
+
+
+ setCategories({ ...categories, Code: !categories.Code })
+ }
+ style={{
+ color: categories.Code ? '#8B7A70' : '#F0ECE8',
+ }}
+ >
+ Code
+
+
+ setCategories({ ...categories, Garden: !categories.Garden })
+ }
+ style={{
+ color: categories.Garden ? '#8B7A70' : '#F0ECE8',
+ }}
+ >
+ Gardens
+
+
+
+ {currentProjects.map((i) => (
+
+
+
+ {i.title}
+ {i.description}
+
+
+ {i.github ? (
+
+ GitHub
+
+ ) : null}
+
+ Learn More
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default Projects;
diff --git a/public/images/thumb_lifeofplastic.png b/public/images/thumb_lifeofplastic.png
new file mode 100644
index 0000000..508392e
Binary files /dev/null and b/public/images/thumb_lifeofplastic.png differ