Skip to content

Commit

Permalink
Created sankey chart
Browse files Browse the repository at this point in the history
  • Loading branch information
andresnowak committed Dec 20, 2024
1 parent 010a1b0 commit 0230170
Show file tree
Hide file tree
Showing 5 changed files with 737 additions and 113 deletions.
237 changes: 130 additions & 107 deletions app/bubble_chart.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,130 @@
import { useEffect, useRef, useState } from "react";
import * as d3 from "d3";

const BubbleChart = () => {
const svgRef = useRef(null);
const [year, setYear] = useState(2000);

// Sample data
const data = [
{ name: "Company A", year: 2000, value: 10, x: 100, y: 150 },
{ name: "Company B", year: 2001, value: 20, x: 200, y: 200 },
{ name: "Company C", year: 2002, value: 15, x: 300, y: 100 },
// Add more data here
];

useEffect(() => {
// Set up the chart dimensions
const width = 800;
const height = 600;

// Set up the SVG container
const svg = d3
.select(svgRef.current)
.attr("width", width)
.attr("height", height);

// Set up the size scale for the bubbles
const sizeScale = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.value)])
.range([5, 50]); // Bubble size range

// Set up the x and y scales for positioning the bubbles
const xScale = d3.scaleLinear().domain([0, 500]).range([0, width]);
const yScale = d3.scaleLinear().domain([0, 500]).range([0, height]);

// Function to update the bubbles based on the selected year
const updateBubbles = (year) => {
const filteredData = data.filter((d) => d.year <= year);

// Bind data to circles
const bubbles = svg
.selectAll(".bubble")
.data(filteredData, (d) => d.name);

// Remove exiting bubbles
bubbles.exit().remove();

// Create new bubbles (enter phase)
const newBubbles = bubbles
.enter()
.append("circle")
.attr("class", "bubble")
.attr("r", (d) => sizeScale(d.value))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.style("fill", "steelblue")
.style("opacity", 0.7);

// Add labels inside the bubbles
newBubbles
.append("text")
.attr("class", "label")
.attr("x", (d) => xScale(d.x))
.attr("y", (d) => yScale(d.y))
.attr("dy", 5)
.text((d) => d.name);

// Transition for updated bubbles
bubbles
.merge(newBubbles)
.transition()
.duration(500)
.attr("r", (d) => sizeScale(d.value))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.style("fill", "steelblue")
.style("opacity", 0.7);
};

// Initial call to render bubbles for the selected year
updateBubbles(year);

// Cleanup on unmount
return () => {
svg.selectAll("*").remove();
};
}, [year]); // Re-render when the year changes

return (
<div>
<input
type="range"
min="2000"
max="2020"
step="1"
value={year}
onChange={(e) => setYear(Number(e.target.value))}
className="slider"
/>
<span>Year: {year}</span>
<svg ref={svgRef}></svg>
</div>
);
};

export { BubbleChart };
"use client";

import dynamic from "next/dynamic";
const Plot = dynamic(() => import("react-plotly.js"), { ssr: false });
import Papa from "papaparse";
import { useEffect, useState } from "react";

function BubbleChart({ datapath, colors }) {
const [bubbleData, setBubbleData] = useState([]);
const [years, setYears] = useState([]);
const [columns, setColumns] = useState([]);

useEffect(() => {
fetch(datapath)
.then((res) => res.text())
.then((csv) => {
Papa.parse(csv, {
complete: (result) => {
const header = result.data[0].map((c) => c.trim());
const rows = result.data.slice(1).map((r) => r.map((c) => c.trim()));
const uniqueYears = [...new Set(rows.map((r) => r[0]))];
const traces = uniqueYears.map((year) => {
const yearData = rows.filter((row) => row[0] === year);
return {
name: year,
x: yearData.map((d) => parseFloat(d[1])),
y: yearData.map((d) => parseFloat(d[2])),
text: yearData.map((d) => d[3]),
mode: "markers",
marker: {
size: yearData.map((d) => parseFloat(d[4])),
color: colors,
sizemode: "area",
},
};
});
setColumns(header);
setYears(uniqueYears);
setBubbleData(traces);
},
});
})
.catch((err) => console.error("CSV load error:", err));
}, [datapath, colors]);

// Each frame corresponds to one year
const frames = bubbleData.map((trace) => ({
name: trace.name,
data: [trace],
}));

// Create slider steps for each year
const sliderSteps = bubbleData.map((trace, i) => ({
label: trace.name,
method: "animate",
args: [
[trace.name],
{
mode: "immediate",
transition: { duration: 500 },
frame: { duration: 500, redraw: false },
},
],
}));

const layout = {
title: "Animated Bubble Chart",
xaxis: { title: columns[1] },
yaxis: { title: columns[2] },
showlegend: false,
updatemenus: [
{
type: "buttons",
x: 0.1,
y: 1.15,
xanchor: "left",
yanchor: "top",
showactive: false,
buttons: [
{
label: "Play",
method: "animate",
args: [
null,
{
fromcurrent: true,
frame: { duration: 500, redraw: false },
transition: { duration: 500 },
},
],
},
{
label: "Pause",
method: "animate",
args: [
[null],
{
mode: "immediate",
frame: { duration: 0, redraw: false },
},
],
},
],
},
],
sliders: [
{
active: 0,
steps: sliderSteps,
x: 0.1,
y: 0,
len: 0.9,
},
],
};

return (
<div className="w-full max-w-2xl">
<Plot
data={[bubbleData[0]]}
layout={layout}
frames={frames}
config={{ scrollZoom: false }}
style={{ width: "100%", height: "600px" }}
/>
</div>
);
}

export { BubbleChart };
45 changes: 39 additions & 6 deletions app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { NavBar } from "./nav.js";
import { Footer, LandingPage } from "./home_layout.js";
import { IframeChart } from "./iframe_charts.js";
import { BubbleChart } from "./bubble_chart.js";
import { SankeyChart } from "./sankey_chart.js";

function SubTitleText({ title, text }) {
return (
Expand Down Expand Up @@ -64,8 +65,7 @@ export default function Home() {
<main className="min-h-screen flex flex-col items-center">
<div className="flex flex-col space-y-2 p-4 max-w-4xl">
<h2 className="text-3xl font-bold mb-2">
Welcome to the Ultimate Sports Analysis Hub on
YouTube!
Welcome to the Ultimate Sports Analysis Hub on YouTube!
</h2>
<p className="text-lg">
Sports have an unparalleled ability to unite billions of
Expand All @@ -81,14 +81,14 @@ export default function Home() {
You’re in the right place!
</p>
</div>
<SubTitleText
title="YouTube: The Digital Arena for Sports Fans"
text="In today's digital landscape, YouTube stands as the premier platform where sports content flourishes. With millions of sports-related videos uploaded each year and billions of views every month, YouTube mirrors the global fervor for sports like no other medium. From electrifying game highlights and in-depth analyses to live streams and passionate fan reactions, the diversity and volume of sports content on YouTube are staggering.
<SubTitleText
title="YouTube: The Digital Arena for Sports Fans"
text="In today's digital landscape, YouTube stands as the premier platform where sports content flourishes. With millions of sports-related videos uploaded each year and billions of views every month, YouTube mirrors the global fervor for sports like no other medium. From electrifying game highlights and in-depth analyses to live streams and passionate fan reactions, the diversity and volume of sports content on YouTube are staggering.
Major sporting events trigger a significant surge in YouTube activity. For instance, during the FIFA World Cup, sports channels see a massive influx of views and uploads, reflecting global excitement and engagement. Similarly, the Olympics and NBA Finals generate waves of content that capture every thrilling moment, behind-the-scenes action, and fan interaction. This digital amplification not only enhances the real-time experience of these events but also extends their reach, allowing fans from all corners of the world to participate in the excitement.
As sports content on YouTube continues to grow, it becomes a vital indicator of global sports trends and fan interests. Below, our first plot showcases the delta views of various sports channels over the years, illustrating the evolving patterns of viewer engagement."
></SubTitleText>
></SubTitleText>
<ChartComponent loading={<LoadingSpinner />} />
<SubTitleText
title="Create Next App"
Expand Down Expand Up @@ -220,6 +220,39 @@ export default function Home() {
/>
)}
</VariableChooserComponent>
<VariableChooserComponent
Title="User comment flow chart for soccer before and after World Cup 2018"
variables={[
{
datapath:
"data/sankey_plot/sankey_diagram_.json",
name: "Sankey soccer",
},
]}
>
{(variable) => (
<SankeyChart
dataPath={variable}
loading={<LoadingSpinner />}
/>
)}
</VariableChooserComponent>
<VariableChooserComponent
Title="Line plot of delta view"
variables={[
{
datapath: "bubble_data.csv",
name: "Bubble Chart",
},
]}
>
{(variable) => (
<BubbleChart
datapath={variable}
loading={<LoadingSpinner />}
/>
)}
</VariableChooserComponent>
</main>
<Footer />
</>
Expand Down
45 changes: 45 additions & 0 deletions app/sankey_chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

// Import Plotly if you're using modules
import dynamic from "next/dynamic";

import { useState, useEffect } from "react";

// Dynamically import Plotly with no SSR
const Plot = dynamic(() => import("react-plotly.js"), {
ssr: false,
loading: () => <div>Loading Plot...</div>,
});

const SankeyChart = ({ dataPath }) => {
const [data, setData] = useState(null);

useEffect(() => {
console.log("SankeyChart component mounted with dataPath:", dataPath);
fetch(dataPath)
.then((response) => response.json())
.then((jsonData) => {
setData(jsonData);
})
.catch((error) => {
console.error("Error loading Sankey data:", error);
});
}, [dataPath]);

if (!data) {
return <div>Loading...</div>;
}

return (
<div className="sankey-chart">
<Plot
data={data.data}
layout={data.layout}
config={data.config || {}} // Optional configuration
frames={data.frames || []} // Optional animation frames
/>
</div>
);
};

export { SankeyChart };
10 changes: 10 additions & 0 deletions public/data/bubble_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
word, 2000, 2001, 2002, 2003
hello, 1, 2, 3, 4
world, 5, 6, 7, 8
foo, 9, 10, 11, 12
bar, 13, 14, 15, 16
baz, 17, 18, 19, 20
qux, 21, 22, 23, 24
quux, 25, 26, 27, 28
corge, 29, 30, 31, 32

Loading

0 comments on commit 0230170

Please sign in to comment.