Skip to content

Commit

Permalink
repro the economist
Browse files Browse the repository at this point in the history
  • Loading branch information
holtzy committed Oct 24, 2024
1 parent a4d244a commit ec282cf
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 13 deletions.
26 changes: 13 additions & 13 deletions pages/course/scales/other-scale-types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import TitleAndDescription from '@/component/TitleAndDescription';
import { LayoutCourse } from '@/component/LayoutCourse';
import { lessonList } from '@/util/lessonList';
import Link from 'next/link';
import { CircleScaleExercise } from '@/component/interactiveTeaching/CircleScaleExercise';
import { CodeBlock } from '@/component/UI/CodeBlock';
import { ExerciseAccordion } from '@/component/ExerciseAccordion';
import {
Expand All @@ -13,11 +12,10 @@ import {
import { scaleBand } from 'd3';
import { Caption } from '@/component/UI/Caption';
import { Sidenote } from '@/component/SideNote';
import { CodeSandbox } from '@/component/CodeSandbox';

const previousURL = '/course/scales/linear-scale';
const currentURL = '/course/scales/other-scale-types';
const nextURL = '/course/axis/introduction';
const nextURL = '/course/scales/project';
const seoDescription = '';

export default function Home() {
Expand Down Expand Up @@ -238,19 +236,19 @@ colorScale("b") // --> green
localStorageId={currentLesson.link}
exercises={[
{
title: <span>Control bar size with a scale</span>,
title: <span>First barplot!</span>,
content: <ExerciseDoubleSandbox exercise={exercices[0]} />,
},
{
title: <span>Three Bars!</span>,
title: <span>Tiny bars?</span>,
content: <ExerciseDoubleSandbox exercise={exercices[1]} />,
},
{
title: <span>Reverse direction</span>,
title: <span>Use colors for groups</span>,
content: <ExerciseDoubleSandbox exercise={exercices[2]} />,
},
{
title: <span>Mirror barplot</span>,
title: <span>Switch to vertical version</span>,
content: <ExerciseDoubleSandbox exercise={exercices[3]} />,
},
]}
Expand All @@ -262,16 +260,18 @@ colorScale("b") // --> green
-
-
- */}
<h2>Wouch! 😅</h2>
<p>That was challenging!</p>

<h2>It's taking shape! 🎉</h2>
<p>
Scales are a core concept in D3.js data visualization, which is why we
needed so many exercises!
You've now mastered two fundamental concepts of dataviz with React and
D3: <b>SVG</b> and <b>Scales</b>. This means{' '}
<b>we're ready to build actual graphs</b>!
</p>
<p>
You've mastered the most important part, but we're not done with scales
just yet. Let's explore a few other useful scale types!
In the next lesson, you'll dive into a hands-on exercise where we
recreate a real-world chart using everything you've learned so far.
</p>
<p>Let's do it! 🙇</p>
</LayoutCourse>
);
}
Expand Down
95 changes: 95 additions & 0 deletions pages/course/scales/project.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import TitleAndDescription from '@/component/TitleAndDescription';
import { LayoutCourse } from '@/component/LayoutCourse';
import { lessonList } from '@/util/lessonList';
import Link from 'next/link';
import { CircleScaleExercise } from '@/component/interactiveTeaching/CircleScaleExercise';
import { CodeBlock } from '@/component/UI/CodeBlock';
import { ExerciseAccordion } from '@/component/ExerciseAccordion';
import {
Exercise,
ExerciseDoubleSandbox,
} from '@/component/ExerciseDoubleSandbox';
import { scaleBand } from 'd3';
import { Caption } from '@/component/UI/Caption';
import { Sidenote } from '@/component/SideNote';
import { CodeSandbox } from '@/component/CodeSandbox';
import { ChartOrSandbox } from '@/component/ChartOrSandbox';
import { BarplotTheEconomistDemo } from '@/viz/BarplotTheEconomist/BarplotTheEconomistDemo';

const previousURL = '/course/scales/other-scale-types';
const currentURL = '/course/scales/project';
const nextURL = '/course/axis/introduction';
const seoDescription = '';

export default function Home() {
const currentLesson = lessonList.find((l) => l.link === currentURL);

if (!currentLesson) {
return null;
}

return (
<LayoutCourse
title={currentLesson.name}
seoDescription={seoDescription}
nextTocItem={lessonList.find((l) => l.link === nextURL)}
previousTocItem={lessonList.find((l) => l.link === previousURL)}
>
<TitleAndDescription
title={currentLesson.name}
lessonStatus={currentLesson.status}
readTime={currentLesson.readTime}
selectedLesson={currentLesson}
description={
<>
<p>
We've built a solid foundation in <b>D3</b>, <b>SVG</b>, and{' '}
<b>scales</b>! Now, let's put that knowledge to the test by
recreating a barplot inspired by
<b>The Economist</b>.
</p>
</>
}
/>
{/* -
-
-
-
-
-
- */}
<h2>What we're trying to do:</h2>

<h2>The data</h2>

<ChartOrSandbox
vizName={'BarplotTheEconomist'}
VizComponent={BarplotTheEconomistDemo}
height={550}
maxWidth={700}
caption="Reproduction of a barplot made by the Economist"
/>

{/* -
-
-
-
-
-
- */}

<h2>It's taking shape! 🎉</h2>
<p>
You've now mastered two fundamental concepts of dataviz with React and
D3: <b>SVG</b> and <b>Scales</b>. This means{' '}
<b>we're ready to build actual graphs</b>!
</p>
<p>
In the next lesson, you'll dive into a hands-on exercise where we
recreate a real-world chart using everything you've learned so far.
</p>
<p>Let's do it! 🙇</p>
</LayoutCourse>
);
}
15 changes: 15 additions & 0 deletions util/lessonList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ export const lessonList: Lesson[] = [
),
readTime: 4,
link: '/course/scales/other-scale-types',
status: 'free',
moduleId: 'scales',
},
{
name: 'Project',
description: (
<>
<p>
Time to apply what we know about scales and SVG to reproduce a barplot
from the Economist!
</p>
</>
),
readTime: 4,
link: '/course/scales/project',
status: 'wip',
moduleId: 'scales',
},
Expand Down
126 changes: 126 additions & 0 deletions viz/BarplotTheEconomist/Barplot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as d3 from 'd3';

const BAR_PADDING = 0.4;

type BarplotProps = {
width: number;
height: number;
data: { name: string; count: number }[];
};

export const Barplot = ({ width, height, data }: BarplotProps) => {
const groups = data.map((d) => d.name).reverse();
const yScale = d3
.scaleBand()
.domain(groups)
.range([0, height])
.paddingInner(BAR_PADDING)
.paddingOuter(0.1);

// X axis
const xScale = d3.scaleLinear().domain([0, 55]).range([0, width]);

// Build the shapes
const allRects = data.map((d, i) => {
const y = yScale(d.name);

if (y === undefined) {
return null;
}

return (
<g key={i}>
<rect
x={0}
y={yScale(d.name)}
width={xScale(d.count)}
height={yScale.bandwidth()}
opacity={1}
stroke="#076fa2"
fill="#076fa2"
/>
{d.count > 7 ? (
<text
x={xScale(0) + 7}
y={y + yScale.bandwidth() / 2}
textAnchor="start"
alignmentBaseline="central"
fontSize={14}
fill="white"
fillOpacity={0.9}
>
{d.name}
</text>
) : (
<text
x={xScale(d.count) + 7}
y={y + yScale.bandwidth() / 2}
textAnchor="start"
alignmentBaseline="central"
fontSize={14}
fill="#076fa2"
>
{d.name}
</text>
)}
</g>
);
});

const grid = xScale
.ticks(10)
.slice(1)
.map((count, i) => (
<g key={i}>
<line
x1={xScale(count)}
x2={xScale(count)}
y1={0}
y2={height}
stroke="#808080"
opacity={0.2}
/>
<text
x={xScale(count)}
y={-10}
textAnchor="middle"
alignmentBaseline="central"
fontSize={12}
fill="#808080"
opacity={1}
>
{count}
</text>
</g>
));

return (
<div>
<svg width={width} height={height} style={{ overflow: 'visible' }}>
{grid}
{allRects}
<g>
<line
x1={xScale(0)}
x2={xScale(0)}
y1={0}
y2={height}
stroke="black"
opacity={0.8}
/>
<text
x={xScale(0)}
y={-10}
textAnchor="middle"
alignmentBaseline="central"
fontSize={12}
fill="#808080"
opacity={1}
>
{0}
</text>
</g>
</svg>
</div>
);
};
45 changes: 45 additions & 0 deletions viz/BarplotTheEconomist/BarplotTheEconomistDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { data } from './data';
import { Barplot } from './Barplot';

const HEADER_HEIGHT = 100;
const FOOTER_HEIGHT = 90;

export const BarplotTheEconomistDemo = ({ width = 700, height = 400 }) => {
return (
<div>
<div style={{ height: HEADER_HEIGHT }}>
<div
style={{
marginTop: 14,
width: '100%',
height: 1,
backgroundColor: '#e5011c',
}}
/>
<div style={{ width: 36, height: 9, backgroundColor: '#e5011c' }} />
<span style={{ fontSize: 20 }}>
<b>Escape artists</b>
</span>
<br />
<span style={{ fontSize: 16 }}>
Number of laboratory-acquired infections, 1970-2021
</span>
</div>

<Barplot
data={data}
width={width}
height={height - FOOTER_HEIGHT - HEADER_HEIGHT}
/>

<div style={{ height: FOOTER_HEIGHT, fontSize: 12, color: 'grey' }}>
<span>
Sources: Laboratory-Acquired Infection Database; American Biological
Safety Association
</span>
<br />
<span>The Economist</span>
</div>
</div>
);
};
38 changes: 38 additions & 0 deletions viz/BarplotTheEconomist/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const data = [
{
"count": 6,
"name": "Hantavirus",
},
{
"count": 7,
"name": "Tularemia",
},
{
"count": 7,
"name": "Dengue",
},
{
"count": 9,
"name": "Ebola",
},
{
"count": 11,
"name": "E. coli",
},
{
"count": 15,
"name": "Tuberculosis",
},
{
"count": 17,
"name": "Salmonella",
},
{
"count": 18,
"name": "Vaccinia",
},
{
"count": 54,
"name": "Brucella",
}
]
Loading

0 comments on commit ec282cf

Please sign in to comment.