diff --git a/lib/layer/styleParser.mjs b/lib/layer/styleParser.mjs index 5acb3c94e..31a33f29d 100644 --- a/lib/layer/styleParser.mjs +++ b/lib/layer/styleParser.mjs @@ -289,6 +289,27 @@ export default layer => { styleObject(cat, structuredClone(layer.style.default)) }) + + // Check validity of categorized theme with multiple fields. + if (theme.type === 'categorized' && Array.isArray(theme.fields)) { + + theme.categories.forEach(cat => { + + if (!theme.fields.includes(cat.field)) { + + console.warn(`Layer: ${layer.key}; Cat ${cat.label} missed valid field.`) + } + + // Multiple field cat theme style must be icon. + if (!cat.style.icon) { + + console.warn(`Layer: ${layer.key}; Cat ${cat.label} has invalid icon style.`) + + cat.style.icon = { type: 'dot' } + } + }) + + } } /** @@ -314,8 +335,15 @@ export default layer => { if (cat.style.icon) { + // Do not merge default style into icon array. if (Array.isArray(cat.style.icon)) return; + // Do not merge default style into icon with type definition. + if (cat.style.icon.type) return; + + // Do not merge default style into svg [type] icons. + if (cat.style.icon.svg) return; + if (defaultStyle.icon && !Array.isArray(defaultStyle.icon)) { cat.style.icon = { diff --git a/lib/layer/themes/categorized.mjs b/lib/layer/themes/categorized.mjs index c1f999b0f..40315d751 100644 --- a/lib/layer/themes/categorized.mjs +++ b/lib/layer/themes/categorized.mjs @@ -1,21 +1,30 @@ /** ### mapp.layer.themes.categorized() -This module exports a function that applies a categorized theme to a feature based on a specified field value. + +The module exports the categorized function as a mapp.layer.theme. + @module /layer/themes/categorized - */ +*/ /** +@function categorized + +@description +The categorized theme method will assign a style from category matching the features properties. + +Cluster features may not be styled by a categorized theme. - * @function categorized - * @param {Object} theme - The theme configuration object. - * @param {string} theme.field - The field name used for determining the category. - * @param {Array} theme.categories - An array of category objects. - * @param {Object} feature - The feature object. - * @param {Object} feature.properties - The properties of the feature. - * @param {Array} [feature.properties.features] - An array of clustered features. - * @returns {void} - */ -export default function (theme, feature) { +A theme can have a fields array to apply an icon style array for the individual property fields. + +@param {Object} theme The theme configuration object. +@param {string} [theme.field] The feature property field to theme. +@param {array} [theme.fields] A fields array to style multiple feature properties. +@param {Array} theme.categories +@param {Object} feature +@param {Object} feature.properties +@param {Array} [feature.properties.features] A cluster feature will have a features array property. +*/ +export default function categorized(theme, feature) { // The categorized theme requires feature.properties. if (!feature.properties) return; @@ -23,6 +32,37 @@ export default function (theme, feature) { // Cluster features can not be styled by category. if (feature.properties.features?.length > 1) return; + let flat; + + // Theme is using multiple fields. + if (Array.isArray(theme.fields)) { + + // Map different theme fields + feature.style.icon = theme.fields.map(field => { + + // Get the field value from feature properties + const catValue = feature.properties[field] + + // Find category matching field and catValue + const cat = theme.categories.find(cat => (cat.value === encodeURIComponent(catValue) || cat.value === catValue) && cat.field === field) + + if (!cat) return; + + flat ||= Array.isArray(cat.style.icon) + + return cat.style.icon + + // Filter out empty icon entries from map response. + }).filter(icon => !!icon); + + if (flat) { + + feature.style.icon = feature.style.icon.flat() + } + + return; + } + const catValue = feature.properties[theme.field] const cat = theme.categories.find(cat => cat.value === encodeURIComponent(catValue) || cat.value === catValue) diff --git a/lib/ui/layers/legends/categorized.mjs b/lib/ui/layers/legends/categorized.mjs index 8b3d744e9..bf6aaf591 100644 --- a/lib/ui/layers/legends/categorized.mjs +++ b/lib/ui/layers/legends/categorized.mjs @@ -13,7 +13,7 @@ export default (layer) => { let timeout; // Switch all control - theme.legend.switch = layer.filter && mapp.utils.html` + theme.legend.switch = theme.field && layer.filter && mapp.utils.html`
@@ -41,12 +41,14 @@ export default (layer) => { theme.categories.forEach(cat => { + const field = cat.field || theme.field + // Check whether cat is in current filter. - cat.disabled = layer.filter?.current[theme.field]?.ni?.indexOf(cat.value) >= 0 + cat.disabled = layer.filter?.current[field]?.ni?.indexOf(cat.value) >= 0 if (layer.featureFields && theme.distribution === 'count') { - cat.count = layer.featureFields[theme.field]?.[cat.value] + cat.count = layer.featureFields[field]?.[cat.value] if (!cat.disabled && !cat.count) return; } @@ -70,7 +72,7 @@ export default (layer) => { if (!layer.filter) return; - const filter = layer.filter.list?.find(f => f.type === 'ni' && f.field === theme.field) + const filter = layer.filter.list?.find(f => f.type === 'ni' && f.field === field) e.target.classList.toggle('disabled') @@ -78,23 +80,23 @@ export default (layer) => { if (e.target.classList.contains('disabled')) { // Create empty field filter object if non exists. - if (!layer.filter.current[theme.field]) { - layer.filter.current[theme.field] = {} + if (!layer.filter.current[field]) { + layer.filter.current[field] = {} } // Create empty NI filter array for field if non exists. - if (!layer.filter.current[theme.field].ni) { - layer.filter.current[theme.field].ni = [] + if (!layer.filter.current[field].ni) { + layer.filter.current[field].ni = [] } // Push cat value into the NI filter array. layer.filter - .current[theme.field].ni + .current[field].ni .push(cat.keys || cat.value) // Flatten the filter in case of arrays filter. layer.filter - .current[theme.field].ni = layer.filter.current[theme.field].ni.flat() + .current[field].ni = layer.filter.current[field].ni.flat() // Remove cat value from current NI field filter. } else { @@ -105,8 +107,8 @@ export default (layer) => { // Splice key out of the NI array. layer.filter - .current[theme.field].ni - .splice(layer.filter.current[theme.field].ni.indexOf(key), 1) + .current[field].ni + .splice(layer.filter.current[field].ni.indexOf(key), 1) }) @@ -114,16 +116,16 @@ export default (layer) => { // Splice value out of the NI array. layer.filter - .current[theme.field].ni - .splice(layer.filter.current[theme.field].ni.indexOf(cat.value), 1) + .current[field].ni + .splice(layer.filter.current[field].ni.indexOf(cat.value), 1) } // Delete current field filter if NI array is empty. - if (!layer.filter.current[theme.field].ni.length) { - delete layer.filter.current[theme.field].ni - if (!Object.keys(layer.filter.current[theme.field]).length) { - delete layer.filter.current[theme.field] + if (!layer.filter.current[field].ni.length) { + delete layer.filter.current[field].ni + if (!Object.keys(layer.filter.current[field]).length) { + delete layer.filter.current[field] } } }