Skip to content

Commit b03fb90

Browse files
authored
Add Legend tool display options (#735)
* Add configuration options * Add header options for legend tool * Improve syntax
1 parent 9bf3f88 commit b03fb90

File tree

10 files changed

+177
-69
lines changed

10 files changed

+177
-69
lines changed

configure/public/toolConfigs.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

configure/src/metaconfigs/layer-image-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,15 @@
192192
"name": "Legend From URL",
193193
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
194194
"type": "text",
195-
"width": 12
195+
"width": 10
196+
},
197+
{
198+
"new": true,
199+
"field": "variables.hideLegendLayerName",
200+
"name": "Hide layer name",
201+
"description": "Hide layer name in the legend.",
202+
"type": "checkbox",
203+
"width": 2
196204
}
197205
]
198206
},

configure/src/metaconfigs/layer-model-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,15 @@
425425
"name": "Legend From URL",
426426
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
427427
"type": "text",
428-
"width": 12
428+
"width": 10
429+
},
430+
{
431+
"new": true,
432+
"field": "variables.hideLegendLayerName",
433+
"name": "Hide layer name",
434+
"description": "Hide layer name in the legend.",
435+
"type": "checkbox",
436+
"width": 2
429437
}
430438
]
431439
},

configure/src/metaconfigs/layer-query-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,15 @@
548548
"name": "Legend From URL",
549549
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
550550
"type": "text",
551-
"width": 12
551+
"width": 10
552+
},
553+
{
554+
"new": true,
555+
"field": "variables.hideLegendLayerName",
556+
"name": "Hide layer name",
557+
"description": "Hide layer name in the legend.",
558+
"type": "checkbox",
559+
"width": 2
552560
}
553561
]
554562
},

configure/src/metaconfigs/layer-tile-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,15 @@
398398
"name": "Legend From URL",
399399
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
400400
"type": "text",
401-
"width": 12
401+
"width": 10
402+
},
403+
{
404+
"new": true,
405+
"field": "variables.hideLegendLayerName",
406+
"name": "Hide layer name",
407+
"description": "Hide layer name in the legend.",
408+
"type": "checkbox",
409+
"width": 2
402410
}
403411
]
404412
},

configure/src/metaconfigs/layer-vector-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,15 @@
486486
"name": "Legend From URL",
487487
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
488488
"type": "text",
489-
"width": 12
489+
"width": 10
490+
},
491+
{
492+
"new": true,
493+
"field": "variables.hideLegendLayerName",
494+
"name": "Hide layer name",
495+
"description": "Hide layer name in the legend.",
496+
"type": "checkbox",
497+
"width": 2
490498
}
491499
]
492500
},

configure/src/metaconfigs/layer-vectortile-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,15 @@
442442
"name": "Legend From URL",
443443
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
444444
"type": "text",
445-
"width": 12
445+
"width": 10
446+
},
447+
{
448+
"new": true,
449+
"field": "variables.hideLegendLayerName",
450+
"name": "Hide layer name",
451+
"description": "Hide layer name in the legend.",
452+
"type": "checkbox",
453+
"width": 2
446454
}
447455
]
448456
},

configure/src/metaconfigs/layer-velocity-config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,15 @@
372372
"name": "Legend From URL",
373373
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
374374
"type": "text",
375-
"width": 12
375+
"width": 10
376+
},
377+
{
378+
"new": true,
379+
"field": "variables.hideLegendLayerName",
380+
"name": "Hide layer name",
381+
"description": "Hide layer name in the legend.",
382+
"type": "checkbox",
383+
"width": 2
376384
}
377385
]
378386
},

src/essence/Tools/Legend/LegendTool.js

Lines changed: 105 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var LegendTool = {
2020
//Get tool variables
2121
this.displayOnStart = L_.getToolVars('legend')['displayOnStart']
2222
this.justification = L_.getToolVars('legend')['justification']
23+
this.showHeadersInLegend = L_.getToolVars('legend')['showHeadersInLegend']
2324
if (this.justification == 'right') {
2425
const toolController = d3.select('#toolcontroller_sepdiv')
2526
const toolContent = d3.select('#toolContentSeparated_Legend')
@@ -99,78 +100,109 @@ function interfaceWithMMWebGIS() {
99100
function refreshLegends() {
100101
$('#LegendTool').empty()
101102

102-
for (let l in L_.layers.on) {
103-
if (L_.layers.on[l] == true) {
104-
if (L_.layers.data[l].type != 'header') {
105-
if (L_.layers.data[l]?._legend === undefined
106-
&& ((['image', 'tile'].includes(L_.layers.data[l].type) && L_.layers.data[l].cogTransform)
107-
|| L_.layers.data[l].type === 'velocity')) {
108-
const layersTool = ToolController_.getTool('LayersTool')
109-
layersTool.populateCogScale(L_.layers.data[l].name)
110-
}
103+
function _refreshLegends(node, parent, depth) {
104+
let shift = LegendTool.showHeadersInLegend === true ? depth : 0
105+
for (let i in node) {
106+
let l = node[i].name
107+
if (L_.layers.on[l] == true) {
108+
if (L_.layers.data[l].type != 'header') {
109+
if (L_.layers.data[l]?._legend === undefined
110+
&& ((['image', 'tile'].includes(L_.layers.data[l].type) && L_.layers.data[l].cogTransform)
111+
|| L_.layers.data[l].type === 'velocity')) {
112+
const layersTool = ToolController_.getTool('LayersTool')
113+
layersTool.populateCogScale(L_.layers.data[l].name)
114+
}
111115

112-
// Check if there's a legend URL that points to an image
113-
const legendURL = L_.layers.data[l]?.legend
114-
if (legendURL && typeof legendURL === 'string') {
115-
let isImageUrl = false
116-
117-
// First check for file extensions
118-
const fileExtension = legendURL.toLowerCase().split('.').pop().split('?')[0] // Remove query params
119-
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'tiff', 'tif', 'bmp', 'ico', 'avif']
120-
121-
if (imageExtensions.includes(fileExtension)) {
122-
isImageUrl = true
123-
} else if (['csv'].includes(fileExtension)) {
124-
isImageUrl = false
125-
} else {
126-
// If no file extension and not a csv, check for image MIME types in URL parameters (e.g., WMS GetLegendGraphic)
127-
try {
128-
const url = new URL(legendURL)
129-
const formatParam = url.searchParams.get('FORMAT') || url.searchParams.get('format')
130-
131-
if (formatParam) {
132-
const imageMimeTypes = [
133-
'image/png', 'image/jpeg', 'image/jpg', 'image/gif',
134-
'image/svg+xml', 'image/webp', 'image/tiff',
135-
'image/bmp', 'image/ico', 'image/avif'
136-
]
137-
138-
const decodedFormat = decodeURIComponent(formatParam).toLowerCase()
139-
if (imageMimeTypes.includes(decodedFormat)) {
140-
isImageUrl = true
116+
// Check if there's a legend URL that points to an image
117+
const legendURL = L_.layers.data[l]?.legend
118+
if (legendURL && typeof legendURL === 'string') {
119+
let isImageUrl = false
120+
121+
// First check for file extensions
122+
const fileExtension = legendURL.toLowerCase().split('.').pop().split('?')[0] // Remove query params
123+
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'tiff', 'tif', 'bmp', 'ico', 'avif']
124+
125+
if (imageExtensions.includes(fileExtension)) {
126+
isImageUrl = true
127+
} else if (['csv'].includes(fileExtension)) {
128+
isImageUrl = false
129+
} else {
130+
// If no file extension and not a csv, check for image MIME types in URL parameters (e.g., WMS GetLegendGraphic)
131+
try {
132+
const url = new URL(legendURL)
133+
const formatParam = url.searchParams.get('FORMAT') || url.searchParams.get('format')
134+
135+
if (formatParam) {
136+
const imageMimeTypes = [
137+
'image/png', 'image/jpeg', 'image/jpg', 'image/gif',
138+
'image/svg+xml', 'image/webp', 'image/tiff',
139+
'image/bmp', 'image/ico', 'image/avif'
140+
]
141+
142+
const decodedFormat = decodeURIComponent(formatParam).toLowerCase()
143+
if (imageMimeTypes.includes(decodedFormat)) {
144+
isImageUrl = true
145+
}
141146
}
147+
} catch (e) {
148+
// URL parsing failed, treat as non-image
149+
console.warn('Failed to parse legend URL:', legendURL)
142150
}
143-
} catch (e) {
144-
// URL parsing failed, treat as non-image
145-
console.warn('Failed to parse legend URL:', legendURL)
151+
}
152+
153+
if (isImageUrl) {
154+
// Handle image legend directly
155+
drawLegends(
156+
LegendTool.tools,
157+
legendURL, // Pass the URL string directly
158+
l,
159+
L_.layers.data[l].display_name,
160+
L_.layers.opacity[l],
161+
shift
162+
)
163+
continue; // Skip the CSV processing below
146164
}
147165
}
148166

149-
if (isImageUrl) {
150-
// Handle image legend directly
167+
if (L_.layers.data[l]?._legend != undefined) {
151168
drawLegends(
152169
LegendTool.tools,
153-
legendURL, // Pass the URL string directly
170+
L_.layers.data[l]?._legend,
154171
l,
155172
L_.layers.data[l].display_name,
156-
L_.layers.opacity[l]
173+
L_.layers.opacity[l],
174+
shift
157175
)
158-
continue; // Skip the CSV processing below
159176
}
160-
}
161-
162-
if (L_.layers.data[l]?._legend != undefined) {
163-
drawLegends(
164-
LegendTool.tools,
165-
L_.layers.data[l]?._legend,
166-
l,
167-
L_.layers.data[l].display_name,
168-
L_.layers.opacity[l]
169-
)
170-
}
177+
} else if (LegendTool.showHeadersInLegend === true) {
178+
const haveLegends = L_.layers.data[l].sublayers
179+
.map(i => i.name)
180+
.filter(i => {
181+
return ((L_.layers.data[i]._legend?.length > 0
182+
|| (L_.layers.data[i]?._legend === undefined
183+
&& ((['image', 'tile'].includes(L_.layers.data[i].type) && L_.layers.data[i].cogTransform)
184+
|| L_.layers.data[i].type === 'velocity'))) && L_.layers.on[i])
185+
})
186+
187+
if (haveLegends.length > 0) {
188+
drawLegends(
189+
LegendTool.tools,
190+
L_.layers.data[l]?._legend,
191+
l,
192+
L_.layers.data[l].display_name,
193+
L_.layers.opacity[l],
194+
shift
195+
)
196+
}
197+
}
171198
}
199+
200+
if (node[i].sublayers)
201+
_refreshLegends(node[i].sublayers, node[i], depth + 1)
172202
}
173203
}
204+
205+
_refreshLegends(L_.configData.layers, {}, 0)
174206
}
175207

176208
// The legends parameter should be an array of objects, where each object must contain
@@ -244,25 +276,37 @@ function drawLegendHeader() {
244276
return tools
245277
}
246278

247-
function drawLegends(tools, _legend, layerUUID, display_name, opacity) {
279+
function drawLegends(tools, _legend, layerUUID, display_name, opacity, shift) {
248280
if (tools == null) return
281+
282+
const layerConfig = L_.layers.data[layerUUID]
283+
284+
const isHeader = layerConfig.type === 'header'
285+
286+
// If option to hide layer name in legend is checked in the configuration
287+
const hideLegendLayerName = layerConfig.variables?.hideLegendLayerName || false;
288+
249289
var c = tools
250290
.append('div')
251291
.attr('class', 'mmgisScrollbar')
252292
.style('width', '100%')
253293
.style('display', 'inline-block')
254294
.style('padding-top', '5px')
255295
.style('padding-right', '12px')
256-
.style('border-bottom', '1px solid var(--color-i)')
296+
.style('padding-left', shift > 0 ? `${shift * 16}px` : '')
297+
.style('border-bottom', isHeader ? '' : '1px solid var(--color-i)')
257298

258299
c.append('div')
259300
.attr('class', 'row')
260301
.append('p')
261302
.style('font-size', '13px')
262303
.style('color', 'var(--color-f)')
263-
.style('margin-bottom', '5px')
304+
.style('margin-bottom', isHeader ? '' : '5px')
264305
.style('padding-left', '8px')
265-
.text(display_name)
306+
.style('font-weight', isHeader ? 'bold' : '')
307+
.text(hideLegendLayerName ? '' : display_name)
308+
309+
if (isHeader) return
266310

267311
let lastContinues = []
268312
let lastShape = ''

src/essence/Tools/Legend/config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@
4242
"type": "dropdown",
4343
"width": 2,
4444
"options": ["left", "right"]
45+
},
46+
{
47+
"field": "variables.showHeadersInLegend",
48+
"name": "Show Headers in Legend",
49+
"description": "If true, the legend will display the name of Header layer types.",
50+
"type": "checkbox",
51+
"width": 3,
52+
"defaultChecked": false
4553
}
4654
]
4755
}

0 commit comments

Comments
 (0)