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
16 changes: 14 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<body>
<div class="container main-content">
<div class="row p-1">
<div class="col-md-3 col-lg-3 col-xl-2 p-1">
<div class="col-md-3 col-xl-2 p-1">
<div class="border rounded p-2 mb-2">
<fieldset id="target">
<legend>Target</legend>
Expand Down Expand Up @@ -178,12 +178,24 @@
</ul>
<div class="tab-content border rounded-bottom border-top-0" id="visualization-content">
<div
class="tab-pane fade show active"
class="tab-pane fade"
id="radar-content"
role="tabpanel"
aria-labelledby="radar-content-tab"
>
<canvas id="radar"></canvas>
<div class="btn-group w-100 my-1" role="group">
<button id="relativeRadars" class="btn btn-outline-primary">Relative</button>
<button id="absoluteRadars" class="btn btn-outline-primary">Absolute</button>
</div>
<div id="relativeRadarsDiv" class="d-none">
<canvas id="relativeRadar"></canvas>
</div>
<div id="absoluteRadarsDiv" class="row d-none">

<div class="col-md-8 col-sm-12" id="bigRadar"></div>
<div class="col-md-4 col-sm-12" id="smallRadars"></div>
</div>
</div>
<div
class="tab-pane fade"
Expand Down
211 changes: 116 additions & 95 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import { Chart, ChartData, registerables } from "chart.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [tsc] <6133> reported by reviewdog 🐶
'ChartData' is declared but its value is never read.

import ALL_WEAPONS, { weaponByName, weaponById } from "./all_weapons";
import { MetricLabel } from "./metrics";
import { labelGroup, MetricLabel, Unit } from "./metrics";
import {
generateMetrics,
unitGroupStats,
Expand All @@ -9,8 +10,8 @@ import {
} from "./stats";
import "./style.scss";
import { Target } from "./target";
import { borderDash, weaponColor, weaponDash, metricColor } from "./ui";
import { shuffle } from "./util";
import { weaponColor, weaponDash, metricColor, createRadarChart, chartData, createBarChart, getViewport } from "./ui";
import { shuffle, subsets } from "./util";
import { Weapon, WeaponType } from "./weapon";

Chart.defaults.font.family = "'Lato', sans-serif";
Expand All @@ -22,16 +23,22 @@ let horsebackDamageMultiplier = 1.0;

let stats: WeaponStats = generateMetrics(ALL_WEAPONS, 1, 1, Target.VANGUARD_ARCHER);
let unitStats: UnitStats = unitGroupStats(stats);
let absoluteRadarsEnabled = false;
let selectedRadar = Unit.DAMAGE;

let selectedTab = "radar-content-tab";

const selectedWeapons: Set<Weapon> = new Set<Weapon>();
const selectedCategories: Set<MetricLabel> = new Set<MetricLabel>();
const searchResults: Set<Weapon> = new Set<Weapon>();


const bigRadarDiv = document.querySelector<HTMLDivElement>("#bigRadar")!
const smallRadarsDiv = document.querySelector<HTMLDivElement>("#smallRadars")!
const weaponSearchResults = document.querySelector<HTMLDivElement>("#weaponSearchResults")!
const displayedWeapons = document.querySelector<HTMLFieldSetElement>("#displayedWeapons")!;


function toId(str: string) {
return str
.replaceAll(" ", "_")
Expand All @@ -43,95 +50,15 @@ function toId(str: string) {
// Normalization will only occur for stat types that have a unit present in the provided normalizationStats.
// This allows for selective normalization, like for bar charts where we wan't mostly raw data, except for
// "speed" (or other inverse metrics) which only make sense as a normalized value
function chartData(
dataset: WeaponStats,
categories: Set<MetricLabel>,
normalizationStats: UnitStats,
setBgColor: boolean
): ChartData {
let sortedCategories = Array.from(categories);
sortedCategories.sort((a,b) => {
return Object.values(MetricLabel).indexOf(a) - Object.values(MetricLabel).indexOf(b);
});

return {
labels: [...sortedCategories],
datasets: [...selectedWeapons].map((w) => {
return {
label: w.name,
data: [...sortedCategories].map((c) => {
const metric = dataset.get(w.name)!.get(c)!;
let value = metric.value.result;
const maybeUnitStats = normalizationStats.get(c);
if (maybeUnitStats) {
const unitMin = maybeUnitStats!.min;
const unitMax = maybeUnitStats!.max;

// Normalize
return (value - unitMin) / (unitMax - unitMin);
}
return value;
}),
backgroundColor: setBgColor ? weaponColor(w, 0.6) : weaponColor(w, 0.1),
borderColor: weaponColor(w, 0.6),
borderDash: borderDash(w),
};
}),
};
}

const radar: Chart = new Chart(
document.getElementById("radar") as HTMLCanvasElement,
{
type: "radar",
options: {
animation: false,
plugins: {
legend: {
display: false,
position: "bottom",
},
},
responsive: true,
maintainAspectRatio: true,
scales: {
radial: {
min: 0,
max: 1,
ticks: {
display: false,
maxTicksLimit: 2,
},
},
},
},
data: chartData(stats, selectedCategories, unitStats, false),
}
);
const relativeRadar: Chart = createRadarChart(
document.querySelector<HTMLCanvasElement>("#relativeRadar")!,
chartData(stats, Array.from(selectedWeapons), selectedCategories, unitStats, false, null),
{min: 0, max: 1}
)

const bars = new Array<Chart>();

function createBarChart(element: HTMLCanvasElement, category: MetricLabel) {
const barUnitStats: UnitStats = new Map();
if(category.includes("Speed")) {
barUnitStats.set(category, unitStats.get(category)!);
}

return new Chart(element as HTMLCanvasElement, {
type: "bar",
options: {
animation: false,
plugins: {
legend: {
display: false,
},
},
responsive: true,
maintainAspectRatio: false,
},
data: chartData(stats, new Set([category]), barUnitStats, true),
});
}
const absoluteRadars = new Array<Chart>();

function redrawBars() {
const barsElem = document.getElementById("bars")!;
Expand All @@ -149,11 +76,11 @@ function redrawBars() {
const elem = document.createElement("canvas");
outer.appendChild(elem);
barsElem.appendChild(outer);
bars.push(createBarChart(elem, c));
bars.push(createBarChart(elem, Array.from(selectedWeapons), c, stats, unitStats));
});
}

function redrawTable(dataset: WeaponStats, unitStats: UnitStats) {
function redrawTable() {
let sortedCategories = Array.from(selectedCategories);
sortedCategories.sort((a,b) => {
return Object.values(MetricLabel).indexOf(a) - Object.values(MetricLabel).indexOf(b);
Expand Down Expand Up @@ -201,7 +128,7 @@ function redrawTable(dataset: WeaponStats, unitStats: UnitStats) {
table.appendChild(head);

selectedWeapons.forEach(weapon => {
let weaponData = dataset.get(weapon.name)!;
let weaponData = stats.get(weapon.name)!;

let row = document.createElement("tr");

Expand Down Expand Up @@ -267,7 +194,8 @@ function redraw() {
radar.update();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [tsc] <2304> reported by reviewdog 🐶
Cannot find name 'radar'.


redrawBars();
redrawTable(stats, unitStats);
redrawTable();
redrawRadars();

// Update content of location string so we can share
const params = new URLSearchParams();
Expand All @@ -279,6 +207,84 @@ function redraw() {
window.history.replaceState(null, "", `?${params.toString()}`);
}

function redrawRadars() {
if(absoluteRadarsEnabled) {
absoluteRadarsButton.classList.add('active');
relativeRadarsButton.classList.remove('active');
relativeRadarsDiv.classList.add('d-none');
absoluteRadarsDiv.classList.remove('d-none');
} else {
relativeRadarsButton.classList.add('active');
absoluteRadarsButton.classList.remove('active');
absoluteRadarsDiv.classList.add('d-none');
relativeRadarsDiv.classList.remove('d-none');
}

relativeRadar.data = chartData(stats, Array.from(selectedWeapons), selectedCategories, unitStats, false, null);
relativeRadar.update();

let categoryGroups = subsets(selectedCategories, labelGroup)
let cateoryGroupsKeys = Array.from(categoryGroups.keys());
absoluteRadars.forEach(r => {
r.clear();
r.destroy();
});
absoluteRadars.splice(0, absoluteRadars.length);
if(!cateoryGroupsKeys.includes(selectedRadar)) {
selectedRadar = cateoryGroupsKeys[0];
}

bigRadarDiv.innerHTML = "";
smallRadarsDiv.innerHTML = "";

cateoryGroupsKeys.forEach(unit => {
let parent = unit === selectedRadar ? bigRadarDiv : smallRadarsDiv

let label =
unit === Unit.SPEED ? "Speed*" :
unit === Unit.DAMAGE ? "Damage" :
unit === Unit.RANGE ? "Range" :
"Unknown"

let newLabel = document.createElement("div");
newLabel.classList.add("category-group");
newLabel.classList.add("text-center");
newLabel.innerHTML = label;
parent.append(newLabel);

if(categoryGroups.get(unit)!.size < 3) {
let newMessage = document.createElement("div");
newMessage.innerHTML = "Select 3 or more categories of this type for this chart to display.";
newMessage.classList.add("text-center");
newMessage.classList.add("my-5");
parent.append(newMessage);
} else {
let newCanvasDiv = document.createElement("div");
newCanvasDiv.classList.add("col-xs-6")
newCanvasDiv.classList.add("col-md-12")
let newCanvas = document.createElement("canvas");

let viewport = getViewport();
if(["xl", "lg", "md"].includes(viewport)) {
newCanvas.onclick = () => { selectedRadar = unit; redrawRadars(); }
}
newCanvasDiv.appendChild(newCanvas);
parent.appendChild(newCanvasDiv);

absoluteRadars.push(
createRadarChart(
newCanvas,
chartData(stats, Array.from(selectedWeapons), categoryGroups.get(unit)!, new Map(), false, x => x.split(' - ')[1]),
{ min: 0, max: unitStats.get(unit)!.max }
)
);
}
});

absoluteRadars.forEach(r => r.draw());

}

function addWeaponDiv(weapon: Weapon) {
const div = document.createElement("div");
div.id = weapon.name;
Expand Down Expand Up @@ -443,14 +449,29 @@ document.getElementById("random")!.onclick = random;
document.getElementById("all")!.onclick = all;
document.getElementById("reset")!.onclick = reset;

let absoluteRadarsButton = document.querySelector<HTMLButtonElement>("#absoluteRadars")!,
relativeRadarsButton = document.querySelector<HTMLButtonElement>("#relativeRadars")!,
absoluteRadarsDiv = document.querySelector<HTMLButtonElement>("#absoluteRadarsDiv")!,
relativeRadarsDiv = document.querySelector<HTMLButtonElement>("#relativeRadarsDiv")!;

absoluteRadarsButton.onclick = () => {
absoluteRadarsEnabled = true;
redrawRadars();
}

relativeRadarsButton.onclick = () => {
absoluteRadarsEnabled = false;
redrawRadars();
}

// Link up Share button
document.getElementById("share")!.onclick = () => {
navigator.clipboard.writeText(window.location.toString());
alert("Copied to clipboard!");
};

let numberOfTargetsInput = document.querySelector<HTMLInputElement>("#numberOfTargets")!;
let numberOfTargetsOutput = document.getElementById("numberOfTargetsOutput")!;
let numberOfTargetsInput = document.querySelector<HTMLInputElement>("#numberOfTargets")!,
numberOfTargetsOutput = document.getElementById("numberOfTargetsOutput")!;

numberOfTargetsInput.oninput = () => {
numberOfTargetsOutput.innerHTML = numberOfTargetsInput.value
Expand Down
22 changes: 18 additions & 4 deletions src/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export enum Unit {
INDEX = "Index",
SPEED = "Milliseconds",
RANGE = "Jeoffreys",
DAMAGE = "Hitpoints"
DAMAGE = "Hitpoints",
UNCATEGORIZED = "???"
}

export enum MetricLabel {
Expand Down Expand Up @@ -76,6 +77,19 @@ export enum MetricLabel {
POLEHAMMER_INDEX = "Index - Polehammer"
}

export function labelGroup(label: MetricLabel): Unit {
if(label.startsWith("Damage") || label.startsWith("Thrown Damage"))
return Unit.DAMAGE;

if(label.startsWith("Range"))
return Unit.RANGE;

if(label.startsWith("Speed"))
return Unit.SPEED;

return Unit.UNCATEGORIZED;
}

export function unitGroup(path: MetricPath) {
if (path.includes(".damage")) {
return Unit.DAMAGE;
Expand All @@ -84,7 +98,7 @@ export function unitGroup(path: MetricPath) {
} else if (path.includes(".range") || path.includes(".altRange")) {
return Unit.RANGE;
}
throw `Invalid path: ${path}`;
return Unit.UNCATEGORIZED
}

export const DAMAGE_METRICS = Object.values(MetricPath).filter(
Expand Down Expand Up @@ -178,7 +192,7 @@ export class InverseMetric extends Metric {
calculate(weapon: Weapon): MetricResult {
let rawResult = extractNumber(weapon, this.path);
return {
result: 1/rawResult,
result: (1 / rawResult) * 1000,
rawResult: rawResult
}
}
Expand All @@ -204,7 +218,7 @@ export class AggregateInverseMetric extends Metric {
);

return {
result: 1 / rawResult,
result: (1 / rawResult) * 1000,
rawResult: rawResult,
}
}
Expand Down
Loading