diff --git a/frontend/src/components/ShopFloorMap.vue b/frontend/src/components/ShopFloorMap.vue
index 6857d3d..635e3cc 100644
--- a/frontend/src/components/ShopFloorMap.vue
+++ b/frontend/src/components/ShopFloorMap.vue
@@ -38,7 +38,7 @@
:key="t.machinetypeid"
class="legend-item"
>
-
+
{{ t.machinetype }}
@@ -53,7 +53,7 @@
:key="subtypeId"
class="legend-item"
>
-
+
{{ subtypeNames[subtypeId] || `Type ${subtypeId}` }}
@@ -64,7 +64,7 @@
:key="assetType"
class="legend-item"
>
-
+
{{ assetTypeLabels[assetType] || assetType }}
@@ -240,6 +240,13 @@ const visibleAssetTypes = computed(() => {
return result
})
+// Legend swatches wear the same ring as the markers they stand for, so the key
+// reads as a key. Without this the dot took a surface-colored border and a light
+// color looked ringed in white next to its black-ringed marker.
+function legendDotStyle(color) {
+ return { background: color, borderColor: markerRingColor(props.theme) }
+}
+
// Get visible subtypes when a type is selected
const visibleSubtypes = computed(() => {
if (!props.selectedAssetType) return {}
@@ -477,9 +484,9 @@ function renderMarkers() {
const marker = L.circleMarker([leafletY, leafletX], {
radius: 6,
fillColor: color,
- // Ring picked from the fill, not fixed white - a white ring on a light
- // marker vanished against the light (white) blueprint.
- color: markerRingColor(color),
+ // Ring keyed on the theme's surface: dark on the white blueprint, light on
+ // the dark one. A fixed white ring vanished against the light blueprint.
+ color: markerRingColor(props.theme),
weight: 2,
fillOpacity: 1,
renderer: canvasRenderer
@@ -630,6 +637,8 @@ watch(() => props.machines, (newVal, oldVal) => {
watch(() => props.theme, (newTheme) => {
if (imageOverlay && map) {
imageOverlay.setUrl(blueprintUrlFor(newTheme))
+ // Marker rings are keyed on the surface, so they have to be redrawn too.
+ renderMarkers()
}
})
diff --git a/frontend/src/utils/mapColors.js b/frontend/src/utils/mapColors.js
index 1913bd3..501fefe 100644
--- a/frontend/src/utils/mapColors.js
+++ b/frontend/src/utils/mapColors.js
@@ -53,19 +53,17 @@ export function getSubtypeId(asset) {
return null
}
-// Ring color for a marker of the given fill. A fixed white ring disappears on
-// the white blueprint, so pick the ring by the FILL's lightness instead: a light
-// fill gets a dark ring, a dark fill a light one. The marker then keeps a hard
-// edge on either surface, and it also rescues a washed-out color a user picked
-// by hand for a subtype - which the palette below cannot control.
-export function markerRingColor(fill) {
- const hex = (fill || '').replace('#', '')
- if (hex.length !== 6) return '#ffffff'
- const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.slice(i, i + 2), 16) / 255)
- // Relative luminance (WCAG), same formula the palette validator uses.
- const channel = c => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)
- const luminance = 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
- return luminance > 0.35 ? '#1b1b1b' : '#ffffff'
+// Ring color for a marker, keyed on the SURFACE it sits on, not on its fill.
+// The ring's job is to separate the marker from the background, so every marker
+// in a theme wears the same one - keying it off the fill instead singled out the
+// light colors with a black ring while their neighbours kept white ones.
+//
+// A ring close to its own fill is never a problem: a fill that resembles the
+// ring is by definition far from the background, which is what carries the
+// separation. So a dark ring on white and a light ring on the dark card work for
+// every fill, including a washed-out color a user picked by hand for a subtype.
+export function markerRingColor(theme) {
+ return theme === 'light' ? '#1b1b1b' : '#ffffff'
}
// Resolve the marker color for an asset the same way ShopFloorMap does: when a
diff --git a/frontend/src/utils/mapColors.spec.js b/frontend/src/utils/mapColors.spec.js
index c96e629..e035668 100644
--- a/frontend/src/utils/mapColors.spec.js
+++ b/frontend/src/utils/mapColors.spec.js
@@ -58,20 +58,21 @@ describe('marker legibility', () => {
expect(contrast(color, DARK_CARD)).toBeGreaterThanOrEqual(3)
})
- it('rings a light fill dark and a dark fill light', () => {
- expect(markerRingColor('#FFEB3B')).toBe('#1b1b1b')
- expect(markerRingColor('#1565c0')).toBe('#ffffff')
+ it('rings by surface, not by fill', () => {
+ // Uniform within a theme: every marker on the white blueprint wears the
+ // dark ring, every marker on the dark one wears the light ring. Keying it
+ // off the fill singled out the light colors with a black ring.
+ expect(markerRingColor('light')).toBe('#1b1b1b')
+ expect(markerRingColor('dark')).toBe('#ffffff')
})
- it('gives every ring 3:1 against its own fill', () => {
- for (const color of palette) {
- expect(contrast(color, markerRingColor(color))).toBeGreaterThanOrEqual(3)
- }
+ it('gives each ring 3:1 against the surface it sits on', () => {
+ expect(contrast(markerRingColor('light'), WHITE)).toBeGreaterThanOrEqual(3)
+ expect(contrast(markerRingColor('dark'), DARK_CARD)).toBeGreaterThanOrEqual(3)
})
- it('falls back to a light ring for a malformed color', () => {
- expect(markerRingColor('')).toBe('#ffffff')
- expect(markerRingColor('nonsense')).toBe('#ffffff')
+ it('defaults to the light ring when the theme is missing', () => {
+ expect(markerRingColor(undefined)).toBe('#ffffff')
})
})
diff --git a/frontend/src/utils/mapPdf.js b/frontend/src/utils/mapPdf.js
index 175befe..eeb6641 100644
--- a/frontend/src/utils/mapPdf.js
+++ b/frontend/src/utils/mapPdf.js
@@ -97,8 +97,8 @@ export async function exportMapPdf(opts) {
if (lx + w > pageW - margin) { lx = margin; ly += lineH }
const [r, g, b] = hexToRgb(entry.color)
doc.setFillColor(r, g, b)
- // Outline the swatch against the white page, same rule as the markers.
- doc.setDrawColor(markerRingColor(entry.color)); doc.setLineWidth(0.4)
+ // The page is always white, so the ring is the light-surface one throughout.
+ doc.setDrawColor(markerRingColor('light')); doc.setLineWidth(0.4)
doc.rect(lx, ly - swatch + 1, swatch, swatch, 'FD')
doc.setTextColor('#333333')
doc.text(label, lx + swatch + 4, ly)
@@ -131,8 +131,8 @@ export async function exportMapPdf(opts) {
const fill = resolveMarkerColor(a, { selectedType, subtypeColors })
const [r, g, b] = hexToRgb(fill)
doc.setFillColor(r, g, b)
- // The PDF prints on white, where a white ring is invisible - ring by fill.
- doc.setDrawColor(markerRingColor(fill)); doc.setLineWidth(0.6)
+ // Prints on white, where a white ring is invisible - use the dark ring.
+ doc.setDrawColor(markerRingColor('light')); doc.setLineWidth(0.6)
doc.circle(x, y, radius, 'FD')
}