Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7bf33b6
feat: add labels module and integrate into PackedGraph
StempunkDev Feb 10, 2026
dac231f
refactor: clean up label-related code and introduce raycasting utilities
StempunkDev Feb 10, 2026
c467f87
refactor: change exported functions to internal functions in label-ra…
StempunkDev Feb 10, 2026
94b638f
feat: integrate label generation into main flow and enhance label dat…
StempunkDev Feb 11, 2026
689fef0
feat: enhance label editing functionality and improve data model sync…
StempunkDev Feb 13, 2026
2379770
refactor: clean up code formatting and improve timing logic in label …
StempunkDev Feb 13, 2026
471e865
refactor: update import path for findClosestCell utility
StempunkDev Feb 13, 2026
ca6d01f
feat: synchronize label data model with burg name and position updates
StempunkDev Feb 16, 2026
3ab40ad
feat: migrate label data structure from SVG to data model and update …
StempunkDev Feb 17, 2026
6ab2c03
fix: prevent error on rendering removed or neutral states in stateLab…
StempunkDev Feb 17, 2026
861db87
refactor: encapsulate label generation functions within LabelsModule
StempunkDev Feb 17, 2026
0d56479
refactor: update import path for label-raycast and improve state labe…
StempunkDev Feb 17, 2026
c22e6eb
chore: update script version numbers to 1.113.0 in index.html
StempunkDev Feb 17, 2026
32e7049
refactor: replace currentLabelData with direct calls to getLabelData …
StempunkDev Feb 19, 2026
6d99d82
Fix spelling in label-raycast.ts [Copilot]
StempunkDev Feb 19, 2026
6fa3f78
refactor: improve code formatting and organization in labels and rend…
StempunkDev Feb 19, 2026
e174056
refactor: optimize label rendering by building HTML string for batch …
StempunkDev Feb 21, 2026
ba0ce8e
Merge branch 'master' into feature/split-label-view-data
StempunkDev Feb 24, 2026
fd32007
apply format
StempunkDev Feb 24, 2026
3927a76
chore: update version to 1.114.0 and adjust related script references…
StempunkDev Feb 27, 2026
fab495f
refactor: streamline label management by integrating label removal an…
StempunkDev Mar 4, 2026
25824fe
fix: regenerateStateLabels
StempunkDev Mar 4, 2026
5f35924
refactor: update label indexing to use current length for state and c…
StempunkDev Mar 8, 2026
967e889
simplify generateStateLabels method and removed removeStateLabel method
StempunkDev Mar 11, 2026
9b4add5
remove getByType out of lables.ts
StempunkDev Mar 27, 2026
baeab35
remove getStateLable function from labels.ts
StempunkDev Mar 27, 2026
5d90b66
remove getBurgLabel from labels.ts
StempunkDev Mar 27, 2026
470c42a
refactor: make label addition methods private and streamline data ass…
StempunkDev Apr 23, 2026
5c5f6c0
rename Labels.updateLabel to just Labels.update
StempunkDev Apr 23, 2026
924b537
rename Labels.removeLabel to Labels.remove
StempunkDev Apr 23, 2026
563e1c9
remove burg specific removal method
StempunkDev Apr 23, 2026
aa2d4d9
refactor: enhance label data structure and extraction methods for SVG…
StempunkDev May 1, 2026
e4a4c92
refactor: rename label interfaces for consistency and clarity
StempunkDev May 1, 2026
2bed4d5
remove redundant check in state label migration
StempunkDev May 1, 2026
c0dcad1
removed svg fallback from label editor
StempunkDev May 1, 2026
854cefe
remove unhelpful comment
StempunkDev May 1, 2026
6324eb6
quick win simplification
StempunkDev May 1, 2026
3e29f3e
cleanup comments
StempunkDev May 1, 2026
d84292e
add helpful description to getNextId
StempunkDev May 1, 2026
701448b
regenerate Labels on Erase Mode Exit
StempunkDev May 1, 2026
0b772be
remove useless comment
StempunkDev May 1, 2026
368eec0
add error logging for label update when label not found
StempunkDev May 2, 2026
0433669
implement custom label rendering and update label attributes for impr…
StempunkDev May 2, 2026
33b9599
Merge branch 'master' into feature/split-label-view-data
StempunkDev May 2, 2026
d4c7fb1
bump version
StempunkDev May 2, 2026
8b98578
format files
StempunkDev May 2, 2026
fd0bd4d
apply lint
StempunkDev May 2, 2026
27e35d4
improve burgLabels
StempunkDev May 5, 2026
3664ac8
Fix leaking paths when redrawing custom labels
StempunkDev May 5, 2026
3aa69bf
improvements to migrating, burg removal, stale paths and label object…
StempunkDev May 5, 2026
de3c861
remove left over console print
StempunkDev May 5, 2026
7cc7a69
fix: id properly incements when updating older maps
StempunkDev May 5, 2026
5ce7498
fix: custom label transforms are now properly migrated
StempunkDev May 5, 2026
e80c859
fix: burg change name is now consistant with the index
StempunkDev May 5, 2026
9eb45be
unified id checking in burg-editor.js and apply transform reset on re…
StempunkDev May 5, 2026
ec742de
fix: burg label id issue by false assumption
StempunkDev May 5, 2026
6808bb9
fix burg related label bugs
StempunkDev May 6, 2026
d66d6d2
add group rendering to custom labels and update burg label rendering
StempunkDev May 6, 2026
d1a356b
fix state label draw call by expecting state id now
StempunkDev May 6, 2026
192a609
apply formatter
StempunkDev May 6, 2026
74c86b0
add timeing to custom label drawing
StempunkDev May 6, 2026
84c4ff6
rename drawBurgFunction to pass linter, can be renamed when migrated
StempunkDev May 6, 2026
e350027
better null check for updated values in lables-editor
StempunkDev May 6, 2026
b791da5
experimental getNextId
StempunkDev May 7, 2026
50955d8
remove label text elements from DOM when layer is off
StempunkDev May 7, 2026
2108b9a
custom label adding now uses Labels module
StempunkDev May 8, 2026
18231da
format + lint pass
StempunkDev May 8, 2026
919a766
set text befor check in draw-state-labels
StempunkDev May 8, 2026
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
2 changes: 2 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,8 @@ async function generate(options) {
Provinces.generate();
Provinces.getPoles();

Labels.generate();
Comment thread
StempunkDev marked this conversation as resolved.

Comment thread
StempunkDev marked this conversation as resolved.
Rivers.specify();
Lakes.defineNames();

Expand Down
1 change: 1 addition & 0 deletions src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ import "./routes-generator";
import "./states-generator";
import "./zones-generator";
import "./religions-generator";
import "./labels";
import "./provinces-generator";
224 changes: 224 additions & 0 deletions src/modules/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
declare global {
var Labels: LabelsModule;
}

export interface StateLabelData {
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
i: number;
type: "state";
stateId: number;
text: string;
fontSize?: number;
Comment thread
StempunkDev marked this conversation as resolved.
}

export interface BurgLabelData {
i: number;
type: "burg";
burgId: number;
group: string;
text: string;
x: number;
y: number;
dx: number;
dy: number;
}

export interface CustomLabelData {
i: number;
type: "custom";
group: string;
text: string;
pathPoints: [number, number][];
startOffset?: number;
fontSize?: number;
letterSpacing?: number;
transform?: string;
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
}

export type LabelData = StateLabelData | BurgLabelData | CustomLabelData;

class LabelsModule {
private getNextId(): number {
const labels = pack.labels;
if (labels.length === 0) return 0;
Comment thread
StempunkDev marked this conversation as resolved.
Outdated

const existingIds = labels.map((l) => l.i).sort((a, b) => a - b);
for (let id = 0; id < existingIds[existingIds.length - 1]; id++) {
if (!existingIds.includes(id)) return id;
}
return existingIds[existingIds.length - 1] + 1;
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
}

generate() : void {
this.clear();
generateStateLabels();
generateBurgLabels();
}

getAll(): LabelData[] {
return pack.labels;
}

get(id: number): LabelData | undefined {
return pack.labels.find((l) => l.i === id);
}

getByType(type: LabelData["type"]): LabelData[] {
Comment thread
Azgaar marked this conversation as resolved.
Outdated
return pack.labels.filter((l) => l.type === type);
}

getByGroup(group: string): LabelData[] {
return pack.labels.filter(
(l) => (l.type === "burg" || l.type === "custom") && l.group === group,
);
}

getStateLabel(stateId: number): StateLabelData | undefined {
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
return pack.labels.find(
(l) => l.type === "state" && l.stateId === stateId,
) as StateLabelData | undefined;
}

getBurgLabel(burgId: number): BurgLabelData | undefined {
return pack.labels.find(
(l) => l.type === "burg" && l.burgId === burgId,
) as BurgLabelData | undefined;
}

addStateLabel(
data: Omit<StateLabelData, "i" | "type">,
): StateLabelData {
const label: StateLabelData = { i: this.getNextId(), type: "state", ...data };
pack.labels.push(label);
return label;
}

addBurgLabel(data: Omit<BurgLabelData, "i" | "type">): BurgLabelData {
const label: BurgLabelData = { i: this.getNextId(), type: "burg", ...data };
pack.labels.push(label);
return label;
}

addCustomLabel(
data: Omit<CustomLabelData, "i" | "type">,
): CustomLabelData {
const label: CustomLabelData = { i: this.getNextId(), type: "custom", ...data };
pack.labels.push(label);
return label;
}

updateLabel(id: number, updates: Partial<LabelData>): void {
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
const label = pack.labels.find((l) => l.i === id);
if (!label) return;
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
Object.assign(label, updates, { i: label.i, type: label.type });
}

removeLabel(id: number): void {
const index = pack.labels.findIndex((l) => l.i === id);
if (index !== -1) pack.labels.splice(index, 1);
}

removeByType(type: LabelData["type"]): void {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can we have only 1 remove method? There is too much noise...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have tried to reduce the amount of remove methods. I would like to keep these three methods since the other two removes offer shrinking of the labels array. Another way would be to offer a remove method with a function as parameter that would make two. One for single Item removal and one for group removal

pack.labels = pack.labels.filter((l) => l.type !== type);
}

removeByGroup(group: string): void {
pack.labels = pack.labels.filter(
(l) =>
!((l.type === "burg" || l.type === "custom") && l.group === group),
);
}

removeStateLabel(stateId: number): void {
const index = pack.labels.findIndex(
(l) => l.type === "state" && l.stateId === stateId,
);
if (index !== -1) pack.labels.splice(index, 1);
}

removeBurgLabel(burgId: number): void {
const index = pack.labels.findIndex(
(l) => l.type === "burg" && l.burgId === burgId,
);
if (index !== -1) pack.labels.splice(index, 1);
}

clear(): void {
pack.labels = [];
}
}

/**
* Generate state labels data entries for each state.
* Only stores essential label data; raycast path calculation happens during rendering.
* @param list - Optional array of stateIds to regenerate only those
*/
export function generateStateLabels(list?: number[]): void {
if (!TIME) console.time("generateStateLabels");
else TIME && console.time("generateStateLabels");

Comment thread
StempunkDev marked this conversation as resolved.
Outdated
const { states } = pack;
const labelsModule = window.Labels;

// Remove existing state labels that need regeneration
if (list) {
list.forEach((stateId) => labelsModule.removeStateLabel(stateId));
} else {
labelsModule.removeByType("state");
}

// Generate new label entries
for (const state of states) {
if (!state.i || state.removed || state.lock) continue;
if (list && !list.includes(state.i)) continue;

labelsModule.addStateLabel({
stateId: state.i,
text: state.name!,
fontSize: 100,
});
}

if (!TIME) console.timeEnd("generateStateLabels");
else TIME && console.timeEnd("generateStateLabels");
}

/**
* Generate burg labels data from burgs.
* Populates pack.labels with BurgLabelData for each burg.
*/
export function generateBurgLabels(): void {
if (!TIME) console.time("generateBurgLabels");
else TIME && console.time("generateBurgLabels");

const labelsModule = window.Labels;

// Remove existing burg labels
labelsModule.removeByType("burg");

// Generate new labels for all active burgs
for (const burg of pack.burgs) {
if (!burg.i || burg.removed) continue;

const group = burg.group || "unmarked";

// Get label group offset attributes if they exist (will be set during rendering)
// For now, use defaults - these will be updated during rendering phase
const dx = 0;
const dy = 0;

labelsModule.addBurgLabel({
burgId: burg.i,
group,
text: burg.name!,
x: burg.x,
y: burg.y,
dx,
dy,
});
}

if (!TIME) console.timeEnd("generateBurgLabels");
else TIME && console.timeEnd("generateBurgLabels");
}

window.Labels = new LabelsModule();
89 changes: 64 additions & 25 deletions src/renderers/draw-burg-labels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Burg } from "../modules/burgs-generator";
import type { BurgLabelData } from "../modules/labels";

declare global {
var drawBurgLabels: () => void;
Expand All @@ -15,31 +16,42 @@ const burgLabelsRenderer = (): void => {
TIME && console.time("drawBurgLabels");
createLabelGroups();

for (const { name } of options.burgs.groups as BurgGroup[]) {
const burgsInGroup = pack.burgs.filter(
(b) => b.group === name && !b.removed,
);
if (!burgsInGroup.length) continue;
// Get all burg labels grouped by group name
const burgLabelsByGroup = new Map<string, BurgLabelData[]>();
for (const label of Labels.getByType("burg").map((l) => l as BurgLabelData)) {
if (!burgLabelsByGroup.has(label.group)) {
burgLabelsByGroup.set(label.group, []);
}
burgLabelsByGroup.get(label.group)!.push(label);
}

const labelGroup = burgLabels.select<SVGGElement>(`#${name}`);
// Render each group and update label offsets from SVG attributes
for (const [groupName, labels] of burgLabelsByGroup) {
const labelGroup = burgLabels.select<SVGGElement>(`#${groupName}`);
if (labelGroup.empty()) continue;

const dx = labelGroup.attr("data-dx") || 0;
const dy = labelGroup.attr("data-dy") || 0;

labelGroup
.selectAll("text")
.data(burgsInGroup)
.enter()
.append("text")
.attr("text-rendering", "optimizeSpeed")
.attr("id", (d) => `burgLabel${d.i}`)
.attr("data-id", (d) => d.i!)
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
.attr("dx", `${dx}em`)
.attr("dy", `${dy}em`)
.text((d) => d.name!);
const dxAttr = labelGroup.attr("data-dx");
const dyAttr = labelGroup.attr("data-dy");
const dx = dxAttr ? parseFloat(dxAttr) : 0;
Comment thread
StempunkDev marked this conversation as resolved.
const dy = dyAttr ? parseFloat(dyAttr) : 0;

for (const labelData of labels) {
// Update label data with SVG group offsets
if (labelData.dx !== dx || labelData.dy !== dy) {
Labels.updateLabel(labelData.i, { dx, dy });
}

labelGroup
.append("text")
.attr("text-rendering", "optimizeSpeed")
.attr("id", `burgLabel${labelData.burgId}`)
.attr("data-id", labelData.burgId)
.attr("x", labelData.x)
.attr("y", labelData.y)
.attr("dx", `${dx}em`)
.attr("dy", `${dy}em`)
.text(labelData.text);
}
}

TIME && console.timeEnd("drawBurgLabels");
Expand All @@ -48,14 +60,40 @@ const burgLabelsRenderer = (): void => {
const drawBurgLabelRenderer = (burg: Burg): void => {
const labelGroup = burgLabels.select<SVGGElement>(`#${burg.group}`);
if (labelGroup.empty()) {
drawBurgLabels();
burgLabelsRenderer();
return; // redraw all labels if group is missing
}

const dx = labelGroup.attr("data-dx") || 0;
const dy = labelGroup.attr("data-dy") || 0;
const dxAttr = labelGroup.attr("data-dx");
const dyAttr = labelGroup.attr("data-dy");
const dx = dxAttr ? parseFloat(dxAttr) : 0;
const dy = dyAttr ? parseFloat(dyAttr) : 0;

removeBurgLabelRenderer(burg.i!);

// Add/update label in data layer
const existingLabel = Labels.getBurgLabel(burg.i!);
if (existingLabel) {
Labels.updateLabel(existingLabel.i, {
text: burg.name!,
x: burg.x,
y: burg.y,
dx,
dy,
});
} else {
Labels.addBurgLabel({
burgId: burg.i!,
group: burg.group || "unmarked",
text: burg.name!,
x: burg.x,
y: burg.y,
dx,
dy,
});
}

// Render to SVG
labelGroup
.append("text")
.attr("text-rendering", "optimizeSpeed")
Expand All @@ -71,6 +109,7 @@ const drawBurgLabelRenderer = (burg: Burg): void => {
const removeBurgLabelRenderer = (burgId: number): void => {
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
const existingLabel = document.getElementById(`burgLabel${burgId}`);
if (existingLabel) existingLabel.remove();
Labels.removeBurgLabel(burgId);
Comment thread
StempunkDev marked this conversation as resolved.
Outdated
};

function createLabelGroups(): void {
Expand Down
Loading
Loading