Skip to content

Commit f36d6b0

Browse files
committed
Make site buildable
1 parent d463249 commit f36d6b0

File tree

6 files changed

+204
-47
lines changed

6 files changed

+204
-47
lines changed

.github/workflows/publish.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Simple workflow for deploying static content to GitHub Pages
2+
name: Deploy static content to Pages
3+
4+
on:
5+
# Runs on pushes targeting the default branch
6+
push:
7+
branches: ["master"]
8+
9+
# Allows you to run this workflow manually from the Actions tab
10+
workflow_dispatch:
11+
12+
# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
13+
permissions:
14+
contents: read
15+
pages: write
16+
id-token: write
17+
18+
# Allow one concurrent deployment
19+
concurrency:
20+
group: "pages"
21+
cancel-in-progress: true
22+
23+
jobs:
24+
# Single deploy job since we're just deploying
25+
deploy:
26+
environment:
27+
name: github-pages
28+
url: ${{ steps.deployment.outputs.page_url }}
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
- name: Set up Node
34+
uses: actions/setup-node@v4
35+
with:
36+
node-version: lts/*
37+
cache: "npm"
38+
- name: Install dependencies
39+
run: npm ci
40+
- name: Build
41+
run: npm run build
42+
- name: Setup Pages
43+
uses: actions/configure-pages@v4
44+
- name: Upload artifact
45+
uses: actions/upload-pages-artifact@v3
46+
with:
47+
# Upload dist folder
48+
path: "./dist"
49+
- name: Deploy to GitHub Pages
50+
id: deployment
51+
uses: actions/deploy-pages@v4

index.html

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
<!doctype html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
8-
</head>
9-
<body>
10-
<div id="root"></div>
11-
<script type="module" src="/src/main.tsx"></script>
12-
</body>
13-
</html>
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Illustrating Trump's Tariffs</title>
8+
</head>
9+
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="/src/main.tsx"></script>
13+
</body>
14+
15+
</html>

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
},
1919
"devDependencies": {
2020
"@eslint/js": "^9.21.0",
21+
"@types/node": "^22.14.1",
2122
"@types/react": "^19.0.10",
2223
"@types/react-dom": "^19.0.4",
2324
"@vitejs/plugin-react": "^4.3.4",

src/App.tsx

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import { useEffect, useMemo, useRef, useState } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
22
import './App.css'
33
/* Imports */
4-
import { Button, Container, Graphics, HeatLegend, Root, color as am5color, p50 as am5p50, p100 as am5p100, percent as am5percent, Slider, Label } from "@amcharts/amcharts5/index";
4+
import { Button, Container, Graphics, HeatLegend, Root, color as am5color, p50 as am5p50, p100 as am5p100, percent as am5percent, Slider, Label, Circle } from "@amcharts/amcharts5/index";
55
import * as am5map from "@amcharts/amcharts5/map";
66
import am5geodataWorldLow from "@amcharts/amcharts5-geodata/worldLow";
77
// import am5geodataUSALow from "@amcharts/amcharts5-geodata/usaLow";
88
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
9-
import dayjs, { Dayjs } from 'dayjs';
9+
import dayjs from 'dayjs';
1010
import { DataItem, IComponentDataItem } from '@amcharts/amcharts5/.internal/core/render/Component';
11-
import { FLATTENED_TARIFF_DATA, FlattenedTariffDataEntry, TariffType } from './data';
11+
import { FLATTENED_TARIFF_DATA, FlattenedTariffDataEntry, TariffType, Valid2DigitCountryCodesWithoutUSA } from './data';
1212

1313

14-
interface EffectiveTariff extends IComponentDataItem, FlattenedTariffDataEntry {
15-
}
16-
1714
const dates = [...FLATTENED_TARIFF_DATA.keys()];
1815

1916
function formatLargeMoney(value: number) {
@@ -37,34 +34,40 @@ function formatLargeMoney(value: number) {
3734
return `$${valueTo100ths} ${specifier}`;
3835
}
3936

37+
type AmChartsData = ({ id: Valid2DigitCountryCodesWithoutUSA, approxValueFormatted?: string } & Partial<Pick<FlattenedTariffDataEntry, Exclude<keyof FlattenedTariffDataEntry, "id" | "date" | "type">>>) & IComponentDataItem;
4038

41-
function AmChartsMap({
42-
field = "percentValue", type = "announced"
43-
}: {
44-
field: Exclude<keyof FlattenedTariffDataEntry, "id" | "date" | "type">,
45-
type: TariffType
46-
}) {
47-
const [dateIndex, setDateIndex] = useState<number>(0);
48-
const [chart, setChart] = useState<am5map.MapChart | null>(null);
39+
function AmChartsMap() {
40+
const [field, setField] = useState<Exclude<keyof FlattenedTariffDataEntry, "id" | "date" | "type">>("percentValue");
41+
const [type, setType] = useState<TariffType>("announced");
42+
const [dateIndex, setDateIndex] = useState<number>(-1);
4943
const [polygonSeries, setPolygonSeries] = useState<am5map.MapPolygonSeries | null>(null);
5044
const dateIndexRef = useRef(dateIndex); // Need to pass this value into a callback in the use effect
45+
const oldData = useRef<AmChartsData[]>([]);
5146

5247
useEffect(() => {
5348
if (polygonSeries) {
54-
const mappedData = FLATTENED_TARIFF_DATA.get(dates[dateIndex])!.filter((d) => d.type === type).map(d => {
49+
const mappedData: AmChartsData[] = dateIndex === -1 ? [] : FLATTENED_TARIFF_DATA.get(dates[dateIndex])!.filter((d) => d.type === type).map(d => {
5550
return {
5651
id: d.id,
5752
[field]: d[field],
5853
approxValueFormatted: field === "approximateValueOfImportsImpacted" ? formatLargeMoney(d.approximateValueOfImportsImpacted) : undefined,
5954
};
6055
});
61-
console.log(mappedData);
6256
polygonSeries.data.setAll(mappedData);
63-
if (chart) {
64-
polygonSeries.mapPolygons.values.forEach(polygon => polygon.appear(1000));
65-
}
57+
const diffItems = mappedData.filter((d) => {
58+
const oldItem = oldData.current.find((old) => old.id === d.id);
59+
if (!oldItem) {
60+
return true;
61+
}
62+
const isDifferent = oldItem[field] !== d[field];
63+
return isDifferent;
64+
}).map(d => d.id);
65+
oldData.current = mappedData;
66+
polygonSeries.mapPolygons.values
67+
.filter(polygon => polygon.dataItem && diffItems.includes((polygon.dataItem as DataItem<AmChartsData>).get("id")))
68+
.forEach(polygon => polygon.appear(1000));
6669
}
67-
}, [polygonSeries, chart, dateIndex, field, type]);
70+
}, [polygonSeries, dateIndex, field, type]);
6871

6972
useEffect(() => {
7073
if (polygonSeries) {
@@ -94,7 +97,6 @@ function AmChartsMap({
9497
layout: root.horizontalLayout
9598
}));
9699

97-
setChart(chart);
98100
// Create polygon series
99101
const polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
100102
// geoJSON: am5geodataUSALow,
@@ -133,7 +135,7 @@ function AmChartsMap({
133135
}));
134136

135137
polygonSeries.mapPolygons.template.events.on("pointerover", function (ev) {
136-
heatLegend.showValue(Number((ev.target.dataItem as DataItem<EffectiveTariff>).get(field)));
138+
heatLegend.showValue(Number((ev.target.dataItem as DataItem<AmChartsData>).get(field)));
137139
});
138140

139141
heatLegend.startLabel.setAll({
@@ -150,13 +152,14 @@ function AmChartsMap({
150152
polygonSeries.events.on("datavalidated", function () {
151153
// console.log({ low: polygonSeries.getPrivate("valueLow"), high: polygonSeries.getPrivate("valueHigh") });
152154
// const low = polygonSeries.getPrivate("valueLow")!;
153-
const high = polygonSeries.getPrivate("valueHigh")!;
155+
const high = polygonSeries.getPrivate("valueHigh") ?? 0;
154156
// heatLegend.set("startValue", polygonSeries.getPrivate("valueLow"));
155157
// heatLegend.set("endValue", Math.max(polygonSeries.getPrivate("valueHigh") ?? 0, 100));
156158
if (field !== "percentValue") {
159+
const endValue = Math.max(high, 1_000_000);
157160
heatLegend.setAll({
158-
endValue: Math.max(high, 100),
159-
endText: formatLargeMoney(high),
161+
endValue,
162+
endText: formatLargeMoney(endValue),
160163
});
161164
}
162165
})
@@ -173,8 +176,8 @@ function AmChartsMap({
173176
y: am5p100,
174177
centerX: am5p50,
175178
centerY: am5p100,
176-
x: am5p50,
177-
width: am5percent(90),
179+
x: am5percent(50),
180+
width: am5percent(135),
178181
layout: root.horizontalLayout,
179182
paddingBottom: 10
180183
}));
@@ -189,7 +192,6 @@ function AmChartsMap({
189192
}));
190193

191194
const slider = container.children.push(Slider.new(root, {
192-
//width: am5.percent(80),
193195
orientation: "horizontal",
194196
start: 0,
195197
centerY: am5p50
@@ -219,7 +221,7 @@ function AmChartsMap({
219221

220222
slider.events.on("rangechanged", function () {
221223
const lastDateMillis = dayjs(dates[dates.length - 1]).valueOf();
222-
const firstDateMillis = dayjs(dates[0]).valueOf();
224+
const firstDateMillis = dayjs("2025-01-20").valueOf(); // Inauguration date
223225
// var year = firstYear + Math.round(slider.get("start", 0) * (lastYear - firstYear));
224226
const nextDateMillis = firstDateMillis + Math.round(slider.get("start", 0) * (lastDateMillis - firstDateMillis));
225227
const nextDate = dayjs(nextDateMillis);
@@ -229,10 +231,86 @@ function AmChartsMap({
229231
if (potentialNextDateIndex === -1) {
230232
setDateIndex(dates.length - 1);
231233
} else {
232-
setDateIndex(potentialNextDateIndex === 0 ? 0 : (potentialNextDateIndex - 1));
234+
setDateIndex(potentialNextDateIndex - 1);
235+
}
236+
});
237+
238+
const typeContainer = chart.children.push(Container.new(root, {
239+
layout: root.horizontalLayout,
240+
x: am5percent(85),
241+
centerX: am5p100,
242+
y: am5percent(100),
243+
dy: -40
244+
}));
245+
246+
// Add labels and controls
247+
typeContainer.children.push(Label.new(root, {
248+
centerY: am5p50,
249+
text: "Announced"
250+
}));
251+
252+
const typeSwitchButton = typeContainer.children.push(Button.new(root, {
253+
themeTags: ["switch"],
254+
centerY: am5p50,
255+
icon: Circle.new(root, {
256+
themeTags: ["icon"]
257+
}),
258+
active: type === "effective",
259+
}));
260+
261+
typeSwitchButton.on("active", function () {
262+
if (!typeSwitchButton.get("active")) {
263+
setType("announced");
264+
} else {
265+
setType("effective");
233266
}
234267
});
235268

269+
typeContainer.children.push(
270+
Label.new(root, {
271+
centerY: am5p50,
272+
text: "Effective"
273+
})
274+
);
275+
276+
const fieldContainer = chart.children.push(Container.new(root, {
277+
layout: root.horizontalLayout,
278+
x: am5percent(15),
279+
centerX: am5p100,
280+
y: am5percent(100),
281+
dy: -40
282+
}));
283+
284+
// Add labels and controls
285+
fieldContainer.children.push(Label.new(root, {
286+
centerY: am5p50,
287+
text: "Percent"
288+
}));
289+
290+
const fieldSwitchButton = fieldContainer.children.push(Button.new(root, {
291+
themeTags: ["switch"],
292+
centerY: am5p50,
293+
icon: Circle.new(root, {
294+
themeTags: ["icon"]
295+
}),
296+
active: field === "approximateValueOfImportsImpacted",
297+
}));
298+
299+
fieldSwitchButton.on("active", function () {
300+
if (!fieldSwitchButton.get("active")) {
301+
setField("percentValue")
302+
} else {
303+
setField("approximateValueOfImportsImpacted")
304+
}
305+
});
306+
307+
fieldContainer.children.push(
308+
Label.new(root, {
309+
centerY: am5p50,
310+
text: "Approximate Value of Imports"
311+
})
312+
);
313+
236314
return () => {
237315
root.dispose();
238316
}
@@ -244,8 +322,8 @@ function AmChartsMap({
244322

245323
function App() {
246324
return (
247-
<AmChartsMap field='approximateValueOfImportsImpacted' type='announced' />
325+
<AmChartsMap />
248326
)
249327
}
250328

251-
export default App
329+
export default App;

vite.config.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { defineConfig } from 'vite'
2-
import react from '@vitejs/plugin-react'
1+
import { defineConfig } from "vite";
2+
import react from "@vitejs/plugin-react";
3+
import process from "process";
34

45
// https://vite.dev/config/
56
export default defineConfig({
67
plugins: [react()],
7-
})
8+
// If in Github Actions, set the base to the repository name
9+
base: process.env.GITHUB_ACTIONS
10+
? process.env.GITHUB_REPOSITORY
11+
? `/${process.env.GITHUB_REPOSITORY.split("/")[1]}/`
12+
: "/info-design-tariffs/"
13+
: undefined,
14+
});

0 commit comments

Comments
 (0)