Get the graph rendering as expected

This commit is contained in:
Oliver-Akins 2025-04-23 00:16:24 -06:00
parent 3796687a72
commit 5e9b91b199
5 changed files with 98 additions and 24 deletions

View file

@ -1,5 +1,7 @@
import { Chart } from "chart.js";
import { filePath } from "../consts.mjs"; import { filePath } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs"; import { Logger } from "../utils/Logger.mjs";
import { smallToLarge } from "../utils/sorters/smallToLarge.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -19,7 +21,7 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
}, },
position: { position: {
width: 475, width: 475,
height: 315, height: 400,
}, },
actions: {}, actions: {},
}; };
@ -55,6 +57,11 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
for (const input of elements) { for (const input of elements) {
input.addEventListener(`change`, this.#bindListener.bind(this)); input.addEventListener(`change`, this.#bindListener.bind(this));
}; };
if (options.parts.includes(`graph`)) {
const canvas = this.element.querySelector(`canvas`);
new Chart( canvas, this._graphData );
};
}; };
async _preparePartContext(partId) { async _preparePartContext(partId) {
@ -126,22 +133,76 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
_graphData = {}; _graphData = {};
async #prepareGraphContext(_ctx) { async #prepareGraphContext(_ctx) {
const datasets = CONFIG.StatsDatabase.getRows( const userData = CONFIG.StatsDatabase.getRows(
`${this._selectedTable}/${this._selectedSubtable}`, `${this._selectedTable}/${this._selectedSubtable}`,
this._selectedUsers, this._selectedUsers,
); );
Logger.log(datasets); const data = {};
const allBuckets = new Set();
const buckets = {}; /*
for (const row of datasets[game.user.id] ?? []) { When we're displaying data for a table within the Dice namespace, we want to
buckets[row.value] ??= 0; include all values that any user might not have rolled, just for completeness
buckets[row.value] += 1; since we do know exactly what labels to display, this might eventually be
replaced with a per-table configuration setting to determine what values are
populated within the graph and nothing else / prevent non-accepted values
from being added to the table in the first place.
*/
if (this._selectedTable === `Dice`) {
const size = Number.parseInt(this._selectedSubtable.slice(1));
for (let i = 1; i <= size; i++) {
allBuckets.add(i);
};
}; };
const sorted = Object.entries(buckets).sort(([v1], [v2]) => Math.sign(v1 - v2)); for (const user of this._selectedUsers) {
const buckets = {};
for (const row of userData[user] ?? []) {
buckets[row.value] ??= 0;
buckets[row.value] += 1;
allBuckets.add(row.value);
};
data[user] = buckets;
}
Logger.log(sorted); const sortedBucketNames = Array.from(allBuckets).sort(smallToLarge);
const datasets = {};
for (const bucket of sortedBucketNames) {
for (const user of this._selectedUsers) {
datasets[user] ??= [];
datasets[user].push(data[user][bucket]);
};
};
this._graphData = {
type: `bar`,
options: {
scales: {
y: {
stacked: true,
},
x: {
stacked: true,
},
},
},
data: {
labels: sortedBucketNames,
datasets: Object.entries(datasets).map(([userID, values]) => {
const user = game.users.get(userID);
return {
label: user.name,
data: values,
borderColor: user.color.css,
backgroundColor: user.color.toRGBA(0.5),
borderWidth: 2,
borderRadius: 4,
borderSkipped: false,
};
}),
},
};
}; };
/** /**
@ -160,17 +221,5 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
Logger.log(`updating ${binding} value to ${target.value}`); Logger.log(`updating ${binding} value to ${target.value}`);
this[binding] = target.value; this[binding] = target.value;
this.render(); this.render();
// this.#updatePartContainingElement(target);
};
/**
* @param { HTMLElement } element
*/
#updatePartContainingElement(element) {
const partRoot = element.closest(`[data-application-part]`);
if (!partRoot) { return };
const data = partRoot.dataset;
const partId = data.applicationPart;
this.render({ parts: [partId] });
}; };
}; };

View file

@ -52,13 +52,17 @@ export class MemoryDatabase {
if (!subtable) { if (!subtable) {
continue; continue;
} }
const size = Number.parseInt(subtable.slice(1)); const size = Number.parseInt(subtable.slice(1));
const rows = []; const rows = [];
for (let i = 1; i <= size; i++) { for (let i = 1; i <= size; i++) {
const count = getNormalDistributionHeight(i, size / 2, size); const count = getNormalDistributionHeight(i, size / 2, size);
console.table({ count, i }); const temp = new Array(count)
const temp = new Array(count).fill(null).map(() => generateRow(i)); .fill(null)
.map(() => generateRow(
game.user.id == user ? i : Math.ceil(Math.random() * size),
));
rows.push(...temp); rows.push(...temp);
}; };

View file

@ -0,0 +1,19 @@
/**
*
* @param {string | number} a
* @param {string | number} b
*/
export function smallToLarge(a, b) {
const aInt = Number.parseInt(a);
const bInt = Number.parseInt(b);
const aIsInvalid = Number.isNaN(aInt);
const bIsInvalid = Number.isNaN(bInt);
if (aIsInvalid && bIsInvalid) {
return a > b;
} else if (aIsInvalid || bIsInvalid) {
return aIsInvalid ? -1 : 1;
};
return Math.sign(aInt - bInt);
};

View file

@ -17,5 +17,7 @@
[data-application-part="graph"] { [data-application-part="graph"] {
flex-grow: 1; flex-grow: 1;
justify-items: center;
position: relative;
} }
} }

View file

@ -1,3 +1,3 @@
<div> <div>
Graph Canvas <canvas></canvas>
</div> </div>