git repos / oastat_static_web_generator

commit 8262ec61

sago007 · 2025-12-03 20:39
8262ec615e412990aed1fbcb9782646da5df47bd patch · browse files
parent b6933188e790a368553ee1cd8c72dd096bc445d8

Move javascript code into js files

Changed files

A static_content/js/score-chart.js
A static_content/js/weapon-pie-chart.js
M templates/game.tpl before
M templates/map.tpl before
diff --git a/static_content/js/score-chart.js b/static_content/js/score-chart.js new file mode 100644 index 0000000..5efbaa2 --- /dev/null +++ b/static_content/js/score-chart.js
@@ -0,0 +1,120 @@
+/**
+ * Score Progression Chart
+ * Renders a D3.js line chart showing player scores over time
+ *
+ * @param {string} dataUrl - URL to the JSON file containing score data
+ */
+function renderScoreChart(dataUrl) {
+ // Load score progression data from JSON file
+ d3.json(dataUrl)
+ .then(function(scoreData) {
+ console.log("Loaded score data:", scoreData);
+
+ if (!scoreData || scoreData.length === 0) {
+ console.log("No score data available");
+ return;
+ }
+
+ // Set up dimensions
+ const margin = {top: 20, right: 120, bottom: 50, left: 60};
+ const width = 800 - margin.left - margin.right;
+ const height = 400 - margin.top - margin.bottom;
+
+ // Create SVG
+ const svg = d3.select("#score-chart")
+ .append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform", `translate(${margin.left},${margin.top})`);
+
+ // Find max values for scales
+ const maxSecond = d3.max(scoreData, d => d3.max(d.data, p => p.second));
+ const maxScore = d3.max(scoreData, d => d3.max(d.data, p => p.score));
+
+ // Create scales
+ const xScale = d3.scaleLinear()
+ .domain([0, maxSecond])
+ .range([0, width]);
+
+ const yScale = d3.scaleLinear()
+ .domain([0, maxScore])
+ .range([height, 0]);
+
+ // Create axes
+ const xAxis = d3.axisBottom(xScale);
+ const yAxis = d3.axisLeft(yScale);
+
+ // Add axes to SVG
+ svg.append("g")
+ .attr("transform", `translate(0,${height})`)
+ .call(xAxis)
+ .append("text")
+ .attr("x", width / 2)
+ .attr("y", 35)
+ .attr("fill", "#000")
+ .style("text-anchor", "middle")
+ .text("Time (seconds)");
+
+ svg.append("g")
+ .call(yAxis)
+ .append("text")
+ .attr("transform", "rotate(-90)")
+ .attr("y", -45)
+ .attr("x", -height / 2)
+ .attr("fill", "#000")
+ .style("text-anchor", "middle")
+ .text("Score");
+
+ // Color scale
+ const color = d3.scaleOrdinal()
+ .domain(scoreData.map(d => d.player))
+ .range(d3.schemeCategory10);
+
+ // Line generator
+ const line = d3.line()
+ .x(d => xScale(d.second))
+ .y(d => yScale(d.score));
+
+ // Draw lines for each player
+ scoreData.forEach(playerData => {
+ svg.append("path")
+ .datum(playerData.data)
+ .attr("fill", "none")
+ .attr("stroke", color(playerData.player))
+ .attr("stroke-width", 2)
+ .attr("d", line);
+
+ // Add dots at data points
+ svg.selectAll(`.dot-${playerData.playerid}`)
+ .data(playerData.data)
+ .enter()
+ .append("circle")
+ .attr("class", `dot-${playerData.playerid}`)
+ .attr("cx", d => xScale(d.second))
+ .attr("cy", d => yScale(d.score))
+ .attr("r", 3)
+ .attr("fill", color(playerData.player))
+ .append("title")
+ .text(d => `${playerData.player}: ${d.score} at ${d.second}s`);
+ });
+
+ // Create legend
+ const legend = d3.select("#score-legend");
+ scoreData.forEach(playerData => {
+ const item = legend.append("div")
+ .attr("class", "legend-item");
+
+ item.append("div")
+ .attr("class", "legend-color")
+ .style("background-color", color(playerData.player));
+
+ const finalScore = playerData.data[playerData.data.length - 1].score;
+ item.append("span")
+ .text(`${playerData.player} (${finalScore})`);
+ });
+ })
+ .catch(function(error) {
+ console.error("Error loading score data:", error);
+ });
+}
diff --git a/static_content/js/weapon-pie-chart.js b/static_content/js/weapon-pie-chart.js new file mode 100644 index 0000000..64c7598 --- /dev/null +++ b/static_content/js/weapon-pie-chart.js
@@ -0,0 +1,82 @@
+/**
+ * Weapon Kills Pie Chart
+ * Renders a D3.js pie chart showing weapon kill distribution
+ *
+ * @param {Array} weaponData - Array of objects with {name: string, value: number}
+ */
+function renderWeaponPieChart(weaponData) {
+ // Set up dimensions
+ const width = 500;
+ const height = 400;
+ const radius = Math.min(width, height) / 2 - 40;
+
+ // Color scale
+ const color = d3.scaleOrdinal()
+ .domain(weaponData.map(d => d.name))
+ .range(d3.schemeCategory10);
+
+ // Create SVG
+ const svg = d3.select("#weapon-pie-chart")
+ .append("svg")
+ .attr("width", width)
+ .attr("height", height)
+ .append("g")
+ .attr("transform", `translate(${width/2},${height/2})`);
+
+ // Create pie layout
+ const pie = d3.pie()
+ .value(d => d.value)
+ .sort(null);
+
+ // Create arc generator
+ const arc = d3.arc()
+ .innerRadius(0)
+ .outerRadius(radius);
+
+ const labelArc = d3.arc()
+ .innerRadius(radius * 0.6)
+ .outerRadius(radius * 0.6);
+
+ // Create pie slices
+ const arcs = svg.selectAll(".arc")
+ .data(pie(weaponData))
+ .enter()
+ .append("g")
+ .attr("class", "arc");
+
+ arcs.append("path")
+ .attr("class", "pie-slice")
+ .attr("d", arc)
+ .attr("fill", d => color(d.data.name))
+ .append("title")
+ .text(d => `${d.data.name}: ${d.data.value} kills`);
+
+ // Add labels for larger slices
+ arcs.append("text")
+ .attr("class", "pie-label")
+ .attr("transform", d => `translate(${labelArc.centroid(d)})`)
+ .attr("text-anchor", "middle")
+ .style("display", d => {
+ // Only show label if slice is large enough
+ const percent = (d.endAngle - d.startAngle) / (2 * Math.PI) * 100;
+ return percent > 5 ? "block" : "none";
+ })
+ .text(d => {
+ const percent = ((d.endAngle - d.startAngle) / (2 * Math.PI) * 100).toFixed(1);
+ return `${percent}%`;
+ });
+
+ // Create legend
+ const legend = d3.select("#weapon-legend");
+ weaponData.forEach(d => {
+ const item = legend.append("div")
+ .attr("class", "legend-item");
+
+ item.append("div")
+ .attr("class", "legend-color")
+ .style("background-color", color(d.name));
+
+ item.append("span")
+ .text(`${d.name} (${d.value})`);
+ });
+}
diff --git a/templates/game.tpl b/templates/game.tpl index 5ff6b00..5f024c4 100644 --- a/templates/game.tpl +++ b/templates/game.tpl
@@ -34,6 +34,7 @@
<link rel="stylesheet" href="../static/css/oastat.css" type="text/css">
<link rel="stylesheet" href="../static/css/oastat_d3.css" type="text/css">
<script src="https://d3js.org/d3.v7.min.js"></script>
+ <script src="../static/js/score-chart.js"></script>
<title>OpenArena Stats - Game {{GAME_NUMBER}}</title>
</head>
<body>
@@ -63,118 +64,8 @@
</table>
<script>
- // Load score progression data from JSON file
- d3.json("{{GAME_NUMBER}}.json")
- .then(function(scoreData) {
- console.log("Loaded score data:", scoreData);
-
- if (!scoreData || scoreData.length === 0) {
- console.log("No score data available");
- return;
- }
-
- // Set up dimensions
- const margin = {top: 20, right: 120, bottom: 50, left: 60};
- const width = 800 - margin.left - margin.right;
- const height = 400 - margin.top - margin.bottom;
-
- // Create SVG
- const svg = d3.select("#score-chart")
- .append("svg")
- .attr("width", width + margin.left + margin.right)
- .attr("height", height + margin.top + margin.bottom)
- .append("g")
- .attr("transform", `translate(${margin.left},${margin.top})`);
-
- // Find max values for scales
- const maxSecond = d3.max(scoreData, d => d3.max(d.data, p => p.second));
- const maxScore = d3.max(scoreData, d => d3.max(d.data, p => p.score));
-
- // Create scales
- const xScale = d3.scaleLinear()
- .domain([0, maxSecond])
- .range([0, width]);
-
- const yScale = d3.scaleLinear()
- .domain([0, maxScore])
- .range([height, 0]);
-
- // Create axes
- const xAxis = d3.axisBottom(xScale);
- const yAxis = d3.axisLeft(yScale);
-
- // Add axes to SVG
- svg.append("g")
- .attr("transform", `translate(0,${height})`)
- .call(xAxis)
- .append("text")
- .attr("x", width / 2)
- .attr("y", 35)
- .attr("fill", "#000")
- .style("text-anchor", "middle")
- .text("Time (seconds)");
-
- svg.append("g")
- .call(yAxis)
- .append("text")
- .attr("transform", "rotate(-90)")
- .attr("y", -45)
- .attr("x", -height / 2)
- .attr("fill", "#000")
- .style("text-anchor", "middle")
- .text("Score");
-
- // Color scale
- const color = d3.scaleOrdinal()
- .domain(scoreData.map(d => d.player))
- .range(d3.schemeCategory10);
-
- // Line generator
- const line = d3.line()
- .x(d => xScale(d.second))
- .y(d => yScale(d.score));
-
- // Draw lines for each player
- scoreData.forEach(playerData => {
- svg.append("path")
- .datum(playerData.data)
- .attr("fill", "none")
- .attr("stroke", color(playerData.player))
- .attr("stroke-width", 2)
- .attr("d", line);
-
- // Add dots at data points
- svg.selectAll(`.dot-${playerData.playerid}`)
- .data(playerData.data)
- .enter()
- .append("circle")
- .attr("class", `dot-${playerData.playerid}`)
- .attr("cx", d => xScale(d.second))
- .attr("cy", d => yScale(d.score))
- .attr("r", 3)
- .attr("fill", color(playerData.player))
- .append("title")
- .text(d => `${playerData.player}: ${d.score} at ${d.second}s`);
- });
-
- // Create legend
- const legend = d3.select("#score-legend");
- scoreData.forEach(playerData => {
- const item = legend.append("div")
- .attr("class", "legend-item");
-
- item.append("div")
- .attr("class", "legend-color")
- .style("background-color", color(playerData.player));
-
- const finalScore = playerData.data[playerData.data.length - 1].score;
- item.append("span")
- .text(`${playerData.player} (${finalScore})`);
- });
- })
- .catch(function(error) {
- console.error("Error loading score data:", error);
- });
+ // Render score chart with game data
+ renderScoreChart("{{GAME_NUMBER}}.json");
</script>
</div>
</body>
diff --git a/templates/map.tpl b/templates/map.tpl index 7a42df9..8b76ba1 100644 --- a/templates/map.tpl +++ b/templates/map.tpl
@@ -32,6 +32,7 @@
<link rel="stylesheet" href="../static/css/oastat.css" type="text/css">
<link rel="stylesheet" href="../static/css/oastat_d3.css" type="text/css">
<script src="https://d3js.org/d3.v7.min.js"></script>
+ <script src="../static/js/weapon-pie-chart.js"></script>
<title>OpenArena Stats - Map {{MAP_NAME}}</title>
</head>
<body>
@@ -69,81 +70,9 @@
{name: "{{WEAPON_NAME}}", value: {{KILL_COUNT}}},
{{/WEAPON_KILLS}}
];
-
- // Set up dimensions
- const width = 500;
- const height = 400;
- const radius = Math.min(width, height) / 2 - 40;
-
- // Color scale
- const color = d3.scaleOrdinal()
- .domain(weaponData.map(d => d.name))
- .range(d3.schemeCategory10);
-
- // Create SVG
- const svg = d3.select("#weapon-pie-chart")
- .append("svg")
- .attr("width", width)
- .attr("height", height)
- .append("g")
- .attr("transform", `translate(${width/2},${height/2})`);
-
- // Create pie layout
- const pie = d3.pie()
- .value(d => d.value)
- .sort(null);
-
- // Create arc generator
- const arc = d3.arc()
- .innerRadius(0)
- .outerRadius(radius);
-
- const labelArc = d3.arc()
- .innerRadius(radius * 0.6)
- .outerRadius(radius * 0.6);
-
- // Create pie slices
- const arcs = svg.selectAll(".arc")
- .data(pie(weaponData))
- .enter()
- .append("g")
- .attr("class", "arc");
-
- arcs.append("path")
- .attr("class", "pie-slice")
- .attr("d", arc)
- .attr("fill", d => color(d.data.name))
- .append("title")
- .text(d => `${d.data.name}: ${d.data.value} kills`);
-
- // Add labels for larger slices
- arcs.append("text")
- .attr("class", "pie-label")
- .attr("transform", d => `translate(${labelArc.centroid(d)})`)
- .attr("text-anchor", "middle")
- .style("display", d => {
- // Only show label if slice is large enough
- const percent = (d.endAngle - d.startAngle) / (2 * Math.PI) * 100;
- return percent > 5 ? "block" : "none";
- })
- .text(d => {
- const percent = ((d.endAngle - d.startAngle) / (2 * Math.PI) * 100).toFixed(1);
- return `${percent}%`;
- });
-
- // Create legend
- const legend = d3.select("#weapon-legend");
- weaponData.forEach(d => {
- const item = legend.append("div")
- .attr("class", "legend-item");
-
- item.append("div")
- .attr("class", "legend-color")
- .style("background-color", color(d.name));
-
- item.append("span")
- .text(`${d.name} (${d.value})`);
- });
+
+ // Render weapon pie chart with data
+ renderWeaponPieChart(weaponData);
</script>
<h2>Most Recent Matches</h2>