feat(tickets map): ticket-stub markers instead of generic teardrop
Replace the canvas teardrop pin with an admission-ticket silhouette (side perforation notches + a dashed tear line, tapering to a bottom point that anchors on the location) drawn via Path2D at 4x (pixelRatio 4). Keeps every existing rule: fill = SLA colour, vivid = open / pastel = closed, white outline, bottom anchor, and the fan-out declutter. Markers now read clearly as tickets and are distinct from the round vehicle markers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
51a84b66c6
commit
b653327b0f
1 changed files with 20 additions and 16 deletions
|
|
@ -983,22 +983,26 @@ function incQs() {
|
||||||
return p.toString();
|
return p.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a teardrop map-pin (head circle + point) at 2x for crispness; returns
|
// Ticket-stub map marker: an admission-ticket silhouette (perforation notches on the
|
||||||
// ImageData added to the map style. Recoloured per SLA state / closed.
|
// sides + a dashed tear line) tapering to a bottom point that anchors on the location.
|
||||||
|
// Drawn at 4x for crispness (paired with pixelRatio 4 in addImage). The `fill` param
|
||||||
|
// lets the same shape back every pin — vivid SLA colours for open, pastel for closed.
|
||||||
|
const _PIN_BODY = 'M5 2h18a2 2 0 0 1 2 2v3a2.4 2.4 0 0 0 0 4.8V15a2 2 0 0 1-2 2H15.3'
|
||||||
|
+ 'L14 20.5 12.7 17H5a2 2 0 0 1-2-2v-3.2a2.4 2.4 0 0 0 0-4.8V4a2 2 0 0 1 2-2Z';
|
||||||
function pinImageData(fill) {
|
function pinImageData(fill) {
|
||||||
const r = 22, sw = 4, cx = r + sw, cy = r + sw, tip = cy + r * 2.55;
|
const S = 4, vbW = 28, vbH = 21.5; // viewBox units; tip at (14, 20.5)
|
||||||
const w = 2 * (r + sw), h = Math.ceil(tip + sw);
|
const cv = document.createElement('canvas');
|
||||||
const cv = document.createElement('canvas'); cv.width = w; cv.height = h;
|
cv.width = vbW * S; cv.height = Math.ceil(vbH * S);
|
||||||
const ctx = cv.getContext('2d');
|
const ctx = cv.getContext('2d');
|
||||||
ctx.beginPath();
|
ctx.scale(S, S);
|
||||||
ctx.arc(cx, cy, r, Math.PI * 0.75, Math.PI * 0.25, false); // top ~270° of the head
|
const body = new Path2D(_PIN_BODY);
|
||||||
ctx.lineTo(cx, tip); // taper to the tip
|
ctx.fillStyle = fill; ctx.fill(body);
|
||||||
ctx.closePath();
|
ctx.lineJoin = 'round'; ctx.lineWidth = 1.5; ctx.strokeStyle = 'rgba(255,255,255,.95)';
|
||||||
ctx.fillStyle = fill; ctx.fill();
|
ctx.stroke(body);
|
||||||
ctx.lineWidth = sw; ctx.strokeStyle = 'rgba(255,255,255,.95)'; ctx.lineJoin = 'round'; ctx.stroke();
|
ctx.setLineDash([1.6, 1.8]); ctx.lineWidth = 1.4; // perforation tear line down the middle
|
||||||
ctx.beginPath(); ctx.arc(cx, cy, r * 0.4, 0, 2 * Math.PI); // inner white hole
|
ctx.beginPath(); ctx.moveTo(14, 4.5); ctx.lineTo(14, 14.5); ctx.stroke();
|
||||||
ctx.fillStyle = 'rgba(255,255,255,.95)'; ctx.fill();
|
ctx.setLineDash([]);
|
||||||
return ctx.getImageData(0, 0, w, h);
|
return ctx.getImageData(0, 0, cv.width, cv.height);
|
||||||
}
|
}
|
||||||
function addPinImages() {
|
function addPinImages() {
|
||||||
const pins = {
|
const pins = {
|
||||||
|
|
@ -1010,7 +1014,7 @@ function addPinImages() {
|
||||||
'pin-closed-unknown': pastel(CLOSED_COLOR),
|
'pin-closed-unknown': pastel(CLOSED_COLOR),
|
||||||
};
|
};
|
||||||
for (const [id, fill] of Object.entries(pins))
|
for (const [id, fill] of Object.entries(pins))
|
||||||
if (!tkMap.hasImage(id)) tkMap.addImage(id, pinImageData(fill), { pixelRatio: 2 });
|
if (!tkMap.hasImage(id)) tkMap.addImage(id, pinImageData(fill), { pixelRatio: 4 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Co-located declutter ("fan-out"). Cluster-centroid tickets share an EXACT coordinate,
|
// Co-located declutter ("fan-out"). Cluster-centroid tickets share an EXACT coordinate,
|
||||||
|
|
@ -1086,7 +1090,7 @@ function initIncMap() {
|
||||||
tkPopup = new maplibregl.Popup({ closeButton: false, closeOnClick: false, offset: 14 });
|
tkPopup = new maplibregl.Popup({ closeButton: false, closeOnClick: false, offset: 14 });
|
||||||
|
|
||||||
tkMap.on('load', () => {
|
tkMap.on('load', () => {
|
||||||
addPinImages(); // teardrop pin icons (open by SLA; closed = faded same SLA colour)
|
addPinImages(); // ticket-stub pin icons (open by SLA; closed = faded same SLA colour)
|
||||||
// Closed overlay (windowed) — drawn UNDER the live open layer. Each closed ticket
|
// Closed overlay (windowed) — drawn UNDER the live open layer. Each closed ticket
|
||||||
// uses a faded ('light') version of its SLA colour (Breached→light red, Compliant→
|
// uses a faded ('light') version of its SLA colour (Breached→light red, Compliant→
|
||||||
// light green), slightly smaller, so it reads as the same status but inactive.
|
// light green), slightly smaller, so it reads as the same status but inactive.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue