diff --git a/client/dive-common/components/Attributes/AttributeValueColors.vue b/client/dive-common/components/Attributes/AttributeValueColors.vue index e3557bcc..9dae0a5a 100644 --- a/client/dive-common/components/Attributes/AttributeValueColors.vue +++ b/client/dive-common/components/Attributes/AttributeValueColors.vue @@ -52,8 +52,8 @@ export default defineComponent({ if (!props.attribute.user && track.attributes[props.attribute.name]) { valueMap[track.attributes[props.attribute.name] as string] = true; } else if (props.attribute.user && track.attributes.userAttributes) { - const userAttr = (track.attributes.userAttributes[user]) as StringKeyObject; - if (userAttr[props.attribute.name]) { + const userAttr = track.attributes.userAttributes[user] as StringKeyObject | undefined; + if (userAttr && userAttr[props.attribute.name]) { valueMap[userAttr[props.attribute.name] as string] = true; } } @@ -63,8 +63,8 @@ export default defineComponent({ if (!props.attribute.user && feature.attributes[props.attribute.name]) { valueMap[feature.attributes[props.attribute.name] as string] = true; } else if (props.attribute.user && feature.attributes.userAttributes) { - const userAttr = (feature.attributes.userAttributes[user]) as StringKeyObject; - if (userAttr[props.attribute.name]) { + const userAttr = feature.attributes.userAttributes[user] as StringKeyObject | undefined; + if (userAttr && userAttr[props.attribute.name]) { valueMap[userAttr[props.attribute.name] as string] = true; } } diff --git a/client/src/layers/AnnotationLayers/AttributeLayer.ts b/client/src/layers/AnnotationLayers/AttributeLayer.ts index dc05ab67..09d338f9 100644 --- a/client/src/layers/AnnotationLayers/AttributeLayer.ts +++ b/client/src/layers/AnnotationLayers/AttributeLayer.ts @@ -353,10 +353,22 @@ export default class AttributeLayer extends BaseLayer { this.renderAttributes = attributes; this.user = user; this.autoColorIndex = []; + const getMissingValueColor = (attribute: Attribute) => { + if (attribute.noneColor) { + return attribute.noneColor; + } + return attribute.valueColors?.['']; + }; // We create the color formatter for the render attributesW this.renderAttributes.forEach((item) => { if (item.datatype === 'text') { this.autoColorIndex.push((data: string | number | boolean) => { + if (data === undefined || data === null || data === '') { + return getMissingValueColor(item) || item.color || 'white'; + } + if (item.staticColor) { + return item.color || 'white'; + } if (item.valueColors && Object.keys(item.valueColors).length) { return item.valueColors[data as string] || item.color || 'white'; } @@ -364,9 +376,13 @@ export default class AttributeLayer extends BaseLayer { }); } else if (item.datatype === 'number') { this.autoColorIndex.push((data: string | number | boolean) => { + if (data === undefined || data === null || data === '') { + return getMissingValueColor(item) || item.color || 'white'; + } if (item.valueColors && Object.keys(item.valueColors).length) { const colorArr = Object.entries(item.valueColors as Record) - .map(([key, val]) => ({ key: parseFloat(key), val })); + .map(([key, val]) => ({ key: parseFloat(key), val })) + .filter((entry) => !Number.isNaN(entry.key)); colorArr.sort((a, b) => a.key - b.key); const colorNums = colorArr.map((map) => map.key); diff --git a/client/src/use/useAttributes.ts b/client/src/use/useAttributes.ts index c4313d3e..0e7d0789 100644 --- a/client/src/use/useAttributes.ts +++ b/client/src/use/useAttributes.ts @@ -48,6 +48,10 @@ export default function UseAttributes( login, }: UseAttributesParams, ) { + function getMissingValueColor(attribute?: Attribute) { + return attribute?.valueColors?.['']; + } + const attributes: Ref> = ref({}); const attributeFilters: Ref = ref([]); const timelineGraphs: Ref> = ref({}); @@ -490,7 +494,15 @@ export default function UseAttributes( return null; }); - const getAttributeValueColor = (attribute: Attribute, val: string) => { + const getAttributeValueColor = (attribute: Attribute, val?: string | number | boolean) => { + if (val === undefined || val === null || val === '') { + if (attribute.noneColor) { + return attribute.noneColor; + } + return getMissingValueColor(attribute) + || attribute.color + || trackStyleManager.typeStyling.value.color(attribute.name); + } if (attribute.datatype === 'text') { if (attribute.staticColor) { if (attribute.color) { @@ -498,20 +510,30 @@ export default function UseAttributes( } return trackStyleManager.typeStyling.value.color(attribute.name); } - if (attribute.valueColors && attribute.valueColors[val]) { - return attribute.valueColors[val]; + const strVal = val.toString(); + if (attribute.valueColors && attribute.valueColors[strVal]) { + return attribute.valueColors[strVal]; } } - return trackStyleManager.typeStyling.value.color(val); + return trackStyleManager.typeStyling.value.color(val.toString()); }; const numericalColorScaling = computed(() => { const autoColorIndex: Record string> = {}; Object.entries(attributes.value).forEach(([baseKey, item]) => { autoColorIndex[baseKey] = ((data: string | number | boolean) => { + if (data === undefined || data === null || data === '') { + if (item.noneColor) { + return item.noneColor; + } + return getMissingValueColor(item) + || item.color + || trackStyleManager.typeStyling.value.color(item.name); + } if (item.datatype === 'number' && item.valueColors && Object.keys(item.valueColors).length) { const colorArr = Object.entries(item.valueColors as Record) - .map(([key, val]) => ({ key: parseFloat(key), val })); + .map(([key, val]) => ({ key: parseFloat(key), val })) + .filter((entry) => !Number.isNaN(entry.key)); colorArr.sort((a, b) => a.key - b.key); const colorNums = colorArr.map((map) => map.key); diff --git a/docs/UI-AttributeRendering.md b/docs/UI-AttributeRendering.md index d3ebfa5f..43d7d8ef 100644 --- a/docs/UI-AttributeRendering.md +++ b/docs/UI-AttributeRendering.md @@ -28,6 +28,7 @@ Under the Rendering Tab for the Attribute Editor if you turn on Render there wil * **Value** * *Value Text Size* - Text size in pixel for the value. This will remain constant when scrolling in/out of the track. * *Value Color* - Text color for the display text. If set to auto it will utilize the attribute color. If Auto is turned off you can set a custom display text color. + * For text attributes with Value Color set to auto, colors follow Attribute Value Colors behavior (None Color for empty/missing values, Static Color for non-empty values when enabled, otherwise per-value mappings). * **Dimensions** * *% Type* - For width and height it will size the area for the attribute based on the track width/height. * *px Type* - It will size the dimension of the width/height in pixels. This is useful if you have tracks of varying sizes and always want the attributes to fit properly. diff --git a/docs/UI-AttributeSwimlanes.md b/docs/UI-AttributeSwimlanes.md index 76b98697..0a86ea98 100644 --- a/docs/UI-AttributeSwimlanes.md +++ b/docs/UI-AttributeSwimlanes.md @@ -30,6 +30,12 @@ Simply use the key filter to select the attributes you whish to graph and config If you are creating a swimlane for a numerical attribute, utilize the value colors to create a color gradient which can be used to represent the values. +For text attributes in swimlanes, color resolution follows the Attribute Value Colors settings: + +1. Missing (`undefined`/`null`) or empty (`''`) values use **None Color** when enabled. +1. If **Static Color** is enabled, non-empty values use the attribute base color. +1. Otherwise, per-value text color mappings are used. + ## Swimlane Key ![Swimlane Key](images/AttributeTimeline/SwimlaneKey.png) diff --git a/docs/UI-Attributes.md b/docs/UI-Attributes.md index 99cf6adf..cf5c8581 100644 --- a/docs/UI-Attributes.md +++ b/docs/UI-Attributes.md @@ -152,6 +152,17 @@ Attributes that are of type text can have their colors preset and saved in the c These colors can be used in the Attribute Rendering or the Swimlane views for attributes to properly render the system. +For text attributes, color selection follows this order: + +1. If the value is missing (`undefined`/`null`) or empty (`''`), use **None Color** when enabled. +1. If the value is empty (`''`) and a color is set for the empty string key in Value Colors, that color can be used when None Color is not set. +1. If **Static Color** is enabled, all non-empty text values use the attribute base color. +1. Otherwise, per-value color mappings are used. + +!!! info + + Empty/missing aliases like `NA`, `N/A`, or `__EMPTY__` are not used for automatic empty handling. Use **None Color** and/or the empty string (`''`) value key. + ![Edit Attribute Value Number Colors](images/Attributes/AttributeValueNumberColors.png) Added the capability to create color gradients for Attribute Values. This will allow numerical values to have custom color gradients which can be used in swimlanes, or in displaying the values of attributes as well.