Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Homework2/csemartin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# More about the Framework


This is a template in React and TypeScript. React has a steeper learning curve than Vue.js because it requires understanding JSX syntax and concepts like hooks and component lifecycle.

If you want to use React but not with TypeScript, just remove any type specifications from the `Example.tsx`, `Notes.tsx`, and `NotesWithReducer.tsx`. You can always refer to `VanillaJS-Template/example.js` for this migration.


## Files You Have to Care about

`package.json` is where we manage the libraries we installed. Besides this, most of the files you can ignore, but **the files under `./src/` are your concern**.

* `./src/main.tsx` is the root script file for React that instatinates our single page application.
* `./src/App.tsx` is the root file for all **development** needs and is also where we manage the layout and load in components.
* `./src/types.ts` is usually where we declare our customized types if you're planning to use it.
* `./src/stores/` is where we manage the stores if you're planning to use it. The store is responsible for global state management.
* `./src/components/` is where we create the components. You may have multiple components depends on your design.
* `Example.tsx` shows how to read `.csv` and `.json`, how component size is being watched, how a bar chart is created, and how the component updates if there are any changes.
* `Notes.tsx` shows the difference of **state** and **prop**, how to use MUI, and how a local state updates based on interaction.
* `NotesWithReducer.tsx` is equivalent to `Notes.tsx`, excepts it uses store called reducer.

## Libraries Installed in this Framework
* D3.js v7 for visualization
* [axios](https://axios-http.com/docs/intro) for API.
* [Material UI](https://mui.com/material-ui/getting-started/) for UI components.
* [lodash](https://lodash.com/) for utility functions in JavaScript.
10 changes: 10 additions & 0 deletions Homework2/csemartin/data/demo.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
category,value
a,21
b,42
c,43
d,5
e,26
f,7
l,10
s,18
x,85
41 changes: 41 additions & 0 deletions Homework2/csemartin/data/demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data":[
{
"value": 21,
"category": "a"
},
{
"value": 42,
"category": "b"
},
{
"value": 43,
"category": "c"
},
{
"value": 5,
"category": "d"
},
{
"value": 26,
"category": "e"
},
{
"value": 7,
"category": "f"
},
{
"value": 10,
"category": "l"
},
{
"value": 18,
"category": "s"
},
{
"value": 85,
"category": "x"
}
]

}
8,583 changes: 8,583 additions & 0 deletions Homework2/csemartin/data/spotify_data clean.csv

Large diffs are not rendered by default.

8,779 changes: 8,779 additions & 0 deletions Homework2/csemartin/data/track_data_final.csv

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions Homework2/csemartin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
34 changes: 34 additions & 0 deletions Homework2/csemartin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "react-template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.6",
"@types/d3": "^7.4.3",
"@types/lodash": "^4.17.21",
"axios": "^1.13.2",
"d3": "^7.9.0",
"lodash": "^4.17.21",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@types/material-ui": "^0.21.18",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"globals": "^17.0.0",
"typescript": "^5.9.3",
"vite": "^7.3.0"
}
}
67 changes: 67 additions & 0 deletions Homework2/csemartin/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Example from './components/Example'
import Notes from './components/Notes'
import { NotesWithReducer, CountProvider } from './components/NotesWithReducer';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { grey } from '@mui/material/colors';

// Add dashboard
import Dashboard from "./components/Dashboard";

// Adjust the color theme for material ui
const theme = createTheme({
palette: {
primary:{
main: grey[700],
},
secondary:{
main: grey[700],
}
},
})

// For how Grid works, refer to https://mui.com/material-ui/react-grid/

function Layout() {
return (
<Box id='main-container'>
<Stack spacing={1} sx={{ height: '100%' }}>
{/* Top row: Example component taking about 60% width */}
<Grid container spacing={1} sx={{ height: '60%' }}>
<Grid size={7}>
<Example />
</Grid>
{/* flexible spacer to take remaining space */}
<Grid size="grow" />
</Grid>
{/* Bottom row: Notes component taking full width */}
<Grid size={12} sx={{ height: '40%' }}>
<Notes msg={"This is a message sent from App.tsx as component prop"} />
{ /* Uncomment the following to see how state management works in React.
<CountProvider>
<NotesWithReducer msg={"This is a message sent from App.tsx as component prop"} />
</CountProvider>
*/ }
</Grid>
</Stack>
</Box>
)
}

/*
function App() {
return (
<ThemeProvider theme={theme}>
<Layout />
</ThemeProvider>
)
}
*/

function App() {
return <Dashboard />;
}

export default App
30 changes: 30 additions & 0 deletions Homework2/csemartin/src/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as d3 from "d3";

export const GENRES = [
"pop",
"rap",
"edm",
"country",
"soft pop",
"soundtrack",
"art pop",
"dark r&b",
"Other"
];

export const GENRE_COLORS: Record<string, string> = {
"pop": "#1f77b4", // blue
"rap": "#ff7f0e", // orange
"edm": "#2ca02c", // green
"country": "#9467bd", // purple (distinct from pop blue)
"soft pop": "#17becf", // cyan (lighter, but different hue)
"soundtrack": "#bcbd22", // olive/yellow-green
"art pop": "#e377c2", // pink/magenta
"dark r&b": "#d62728", // red (kept strong)
"Other": "#7f7f7f" // neutral gray
};

export const genreColorScale = d3
.scaleOrdinal<string, string>()
.domain(GENRES)
.range(GENRES.map(g => GENRE_COLORS[g]));
129 changes: 129 additions & 0 deletions Homework2/csemartin/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Dashboard.tsx
import { useEffect, useState } from "react";
import * as d3 from "d3";
import "./dashboard.css";
import ScatterTrend from "./figures/ScatterTrend";
import StreamGraph from "./figures/StreamGraph";
import BarChart from "./figures/BarChart";

export type Track = {
track_popularity: number;
artist_popularity: number;
artist_followers: number;
release_year: number;
genre: string;
danceability: number;
energy: number;
valence: number;
tempo: number;
duration_ms: number;
explicit: number;
};

function normalizeGenre(raw: string): string {
if (!raw) return "Other";
const g = raw.toLowerCase();

if (g.includes("pop")) return "pop";
if (g.includes("rap") || g.includes("hip")) return "rap";
if (g.includes("edm") || g.includes("electronic")) return "edm";
if (g.includes("country")) return "country";
if (g.includes("soundtrack")) return "soundtrack";
if (g.includes("r&b") || g.includes("rnb")) return "dark r&b";
if (g.includes("art")) return "art pop";
if (g.includes("soft")) return "soft pop";

return "Other";
}

export default function Dashboard() {
const [data, setData] = useState<Track[] | null>(null);

// Load data ONCE
useEffect(() => {
d3.csv("/data/track_data_final.csv").then(raw => {
const parsed: Track[] = raw.map(d => ({
track_popularity: +d.track_popularity!,
artist_popularity: +d.artist_popularity!,
artist_followers: +d.artist_followers!,
release_year: new Date(d.album_release_date!).getFullYear(),
genre: normalizeGenre(d.artist_genres),
danceability: +d.danceability!,
energy: +d.energy!,
valence: +d.valence!,
tempo: +d.tempo!,
duration_ms: +d.duration_ms!,
explicit: +d.explicit!
}));

setData(parsed);
});
}, []);

if (!data) {
return <div style={{ padding: "20px" }}>Loading data…</div>;
}

return (
<div className="page">
<h2 className="dashboard-title">
Genre, Popularity, and Temporal Structure in Contemporary Music
<span className="dashboard-title-sub">
How Genre and Artist Visibility Shape Track Popularity Over Time
</span>
</h2>

<div className="dashboard-subtitle">
<div className="figure-guide">
<strong>Figure guide.</strong>
<span>
<strong>(Top)</strong> Stream graph — year-by-year genre composition.
<em> Area thickness</em> encodes relative genre prevalence within each year (wiggle-offset for readability).
</span>
<span>
<br></br>
<strong>(Bottom-left)</strong> Scatter + trend lines — association between artist popularity and track popularity.
<em> Point size</em> = artist followers; <em>color</em> = genre; <em>lines</em> = per-genre least-squares fit.
</span>
<span>
<br></br>
<strong>(Bottom-right)</strong> Bar chart — baseline track popularity by genre.
<em> Bar height</em> = mean popularity; <em>error bar (variability)</em> = ±1 standard deviation.
</span>
</div>

<div className="figure-goal">
<br></br>
<strong>Objective:</strong> Does genre context and artist popularity jointly structure track popularity,
and whether genres differ in baseline popularity and variability.
</div>
</div>


<div className="dashboard">
<div className="top-row">
<div className="panel">
<h3>Stream Graph: Genre Composition Over Time</h3>
{/* Temporal aggregate view showing relative genre prevalence over release years */}
<StreamGraph data={data} />
</div>
</div>

<div className="bottom-row">
<div className="panel">
<h3>Scatter Plot: Track Popularity vs Artist Popularity</h3>
{/* Position encoding for quantitative comparison;
(color used for categorical genre separation) */}
<ScatterTrend data={data} />
</div>

<div className="panel">
<h3>Mean Track Popularity by Genre</h3>
{/* baseline differences in track popularity across genres, providing context for the genre */}
<BarChart data={data} />
</div>
</div>
</div>
</div>
);
}
Loading