From b586f5910919d42a9ebb8fcddf237d22ac538dd7 Mon Sep 17 00:00:00 2001 From: NilsUeter Date: Sat, 5 Apr 2025 17:59:28 +0200 Subject: [PATCH] Add a short summary and two charts --- package-lock.json | 30 ++++ package.json | 2 + src/10th/Roster.jsx | 30 ++-- src/10th/ShortSummaryTable.jsx | 248 +++++++++++++++++++++++++++++++++ src/App.jsx | 2 + 5 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 src/10th/ShortSummaryTable.jsx diff --git a/package-lock.json b/package-lock.json index 6f56374..2745f4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "fancyscribe", "version": "0.0.0", "dependencies": { + "chart.js": "^4.4.8", "jszip": "^3.10.1", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0" }, "devDependencies": { @@ -742,6 +744,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1058,6 +1066,18 @@ } ] }, + "node_modules/chart.js": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2063,6 +2083,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", diff --git a/package.json b/package.json index b06c14f..0ae56a1 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "preview": "vite preview" }, "dependencies": { + "chart.js": "^4.4.8", "jszip": "^3.10.1", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0" }, "devDependencies": { diff --git a/src/10th/Roster.jsx b/src/10th/Roster.jsx index 850e5cc..c011ae4 100644 --- a/src/10th/Roster.jsx +++ b/src/10th/Roster.jsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect, Fragment } from "react"; +import React, { useRef, useState, useEffect, Fragment } from "react"; import factionBackground from "../assets/factionBackground.png"; import keywordsBackground from "../assets/keywordsBackground.png"; import cardBackground from "../assets/cardBackground.png"; @@ -13,8 +13,14 @@ import { Weapons, hasDifferentProfiles } from "./Weapons"; import { useIndexedDB } from "../helpers/useIndexedDB"; // New hook for IndexedDB import { ImgEditor } from "./ImgEditor"; import { trySettingLocalStorage } from "../helpers/useLocalStorage"; +import { ShortSummaryTable } from "./ShortSummaryTable"; -export const Roster = ({ roster, onePerPage, colorUserChoice }) => { +export const Roster = ({ + roster, + onePerPage, + colorUserChoice, + primaryColor, +}) => { if (!roster) { return null; } @@ -26,7 +32,7 @@ export const Roster = ({ roster, onePerPage, colorUserChoice }) => { }} >
{ marginBottom: "12px", }} > - {name} [{cost.commandPoints} CP, {cost.points} pts] + {name} + {cost.points} pts
+ {forces.map((force, index) => ( - + + + + ))} ); diff --git a/src/10th/ShortSummaryTable.jsx b/src/10th/ShortSummaryTable.jsx new file mode 100644 index 0000000..e2ab6fe --- /dev/null +++ b/src/10th/ShortSummaryTable.jsx @@ -0,0 +1,248 @@ +import React from "react"; +import { Bar } from "react-chartjs-2"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Tooltip, + Legend, + Title, +} from "chart.js"; +import cardBackground from "../assets/cardBackground.png"; + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Tooltip, + Legend, + Title, +); +export const ShortSummaryTable = ({ force, primaryColor }) => { + const { units, factionRules, rules, catalog } = force; + + const sortedUnits = units.sort((a, b) => { + // sort by points cost desc first, then by name + if (a.cost.points < b.cost.points) return 1; + if (a.cost.points > b.cost.points) return -1; + return a.name.localeCompare(b.name); + }); + + // Prepare data for the bar chart, group by toughness and sum points cost + const groupedByToughness = units.reduce((acc, unit) => { + const toughness = unit.modelStats?.[0]?.toughness || 0; + if (!acc[toughness]) { + acc[toughness] = { totalPoints: 0, unitNames: [] }; + } + acc[toughness].totalPoints += unit.cost.points; + if (!acc[toughness].unitNames.includes(unit.name)) { + acc[toughness].unitNames.push(unit.name); + } + return acc; + }, {}); + + const groupedChartDataToughness = Object.entries(groupedByToughness) + .map(([toughness, { totalPoints, unitNames }]) => ({ + toughness: parseInt(toughness, 10), + totalPoints, + unitNames, + })) + .sort((a, b) => a.toughness - b.toughness); + + // Prepare data for the bar chart, group by movement and sum points cost + const groupedByMovement = units.reduce((acc, unit) => { + const movement = unit.modelStats?.[0]?.move || 0; + if (!acc[movement]) { + acc[movement] = { totalPoints: 0, unitNames: [] }; + } + acc[movement].totalPoints += unit.cost.points; + if (!acc[movement].unitNames.includes(unit.name)) { + acc[movement].unitNames.push(unit.name); + } + return acc; + }, {}); + + const groupedChartDataMovement = Object.entries(groupedByMovement) + .map(([movement, { totalPoints, unitNames }]) => ({ + movement: parseInt(movement, 10), + totalPoints, + unitNames, + })) + .sort((a, b) => a.movement - b.movement); + + return ( +
+
+
+
+
+ Unit Name +
+
+ Cost (pts) +
+
+
+
+ {sortedUnits.map((unit, index) => { + let { name, cost, models } = unit; + const count = models?.[0]?.count || 1; + return ( +
+
+ {count > 1 ? count + "x " : ""} + {name} +
+
+ {cost.points} pts +
+
+ ); + })} +
+
+ +
+
+ data.movement + '"', + ), + datasets: [ + { + label: "Total Cost (pts)", + data: groupedChartDataMovement.map( + (data) => data.totalPoints, + ), + backgroundColor: primaryColor, + }, + ], + }} + options={{ + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + }, + }, + plugins: { + title: { + display: true, + text: "MOVEMENT OF YOUR UNITS", + font: { + size: 14, + weight: "bold", + family: "Noto Sans", + }, + color: "#000", + align: "start", + }, + legend: { + display: false, + }, + tooltip: { + displayColors: false, + callbacks: { + title: function (context) { + return ""; + }, + label: function (context) { + const movement = context.label; + const unitNames = groupedChartDataMovement.find( + (data) => data.movement === parseInt(movement, 10), + )?.unitNames; + return unitNames; + }, + }, + }, + }, + }} + /> +
+ +
+ data.toughness), + datasets: [ + { + label: "Total Cost (pts)", + data: groupedChartDataToughness.map( + (data) => data.totalPoints, + ), + backgroundColor: primaryColor, + }, + ], + }} + options={{ + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + }, + }, + plugins: { + title: { + display: true, + text: "TOUGHNESS OF YOUR UNITS", + font: { + size: 14, + weight: "bold", + family: "Noto Sans", + }, + color: "#000", + align: "start", + }, + legend: { + display: false, + }, + tooltip: { + displayColors: false, + callbacks: { + title: function (context) { + return ""; + }, + label: function (context) { + const toughness = context.label; + const unitNames = groupedChartDataToughness.find( + (data) => data.toughness === parseInt(toughness, 10), + )?.unitNames; + return unitNames; + }, + }, + }, + }, + }} + /> +
+
+
+ ); +}; diff --git a/src/App.jsx b/src/App.jsx index d4c1925..d83bb43 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -214,6 +214,7 @@ function App() { return (
)}