Skip to content

Commit 8197b2b

Browse files
authored
Empty attribute color (#408)
* add none color for attributes * documentation for attribute changes
1 parent ea297b9 commit 8197b2b

6 files changed

Lines changed: 66 additions & 10 deletions

File tree

client/dive-common/components/Attributes/AttributeValueColors.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export default defineComponent({
5252
if (!props.attribute.user && track.attributes[props.attribute.name]) {
5353
valueMap[track.attributes[props.attribute.name] as string] = true;
5454
} else if (props.attribute.user && track.attributes.userAttributes) {
55-
const userAttr = (track.attributes.userAttributes[user]) as StringKeyObject;
56-
if (userAttr[props.attribute.name]) {
55+
const userAttr = track.attributes.userAttributes[user] as StringKeyObject | undefined;
56+
if (userAttr && userAttr[props.attribute.name]) {
5757
valueMap[userAttr[props.attribute.name] as string] = true;
5858
}
5959
}
@@ -63,8 +63,8 @@ export default defineComponent({
6363
if (!props.attribute.user && feature.attributes[props.attribute.name]) {
6464
valueMap[feature.attributes[props.attribute.name] as string] = true;
6565
} else if (props.attribute.user && feature.attributes.userAttributes) {
66-
const userAttr = (feature.attributes.userAttributes[user]) as StringKeyObject;
67-
if (userAttr[props.attribute.name]) {
66+
const userAttr = feature.attributes.userAttributes[user] as StringKeyObject | undefined;
67+
if (userAttr && userAttr[props.attribute.name]) {
6868
valueMap[userAttr[props.attribute.name] as string] = true;
6969
}
7070
}

client/src/layers/AnnotationLayers/AttributeLayer.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,20 +353,36 @@ export default class AttributeLayer extends BaseLayer<AttributeTextData> {
353353
this.renderAttributes = attributes;
354354
this.user = user;
355355
this.autoColorIndex = [];
356+
const getMissingValueColor = (attribute: Attribute) => {
357+
if (attribute.noneColor) {
358+
return attribute.noneColor;
359+
}
360+
return attribute.valueColors?.[''];
361+
};
356362
// We create the color formatter for the render attributesW
357363
this.renderAttributes.forEach((item) => {
358364
if (item.datatype === 'text') {
359365
this.autoColorIndex.push((data: string | number | boolean) => {
366+
if (data === undefined || data === null || data === '') {
367+
return getMissingValueColor(item) || item.color || 'white';
368+
}
369+
if (item.staticColor) {
370+
return item.color || 'white';
371+
}
360372
if (item.valueColors && Object.keys(item.valueColors).length) {
361373
return item.valueColors[data as string] || item.color || 'white';
362374
}
363375
return item.color || 'white';
364376
});
365377
} else if (item.datatype === 'number') {
366378
this.autoColorIndex.push((data: string | number | boolean) => {
379+
if (data === undefined || data === null || data === '') {
380+
return getMissingValueColor(item) || item.color || 'white';
381+
}
367382
if (item.valueColors && Object.keys(item.valueColors).length) {
368383
const colorArr = Object.entries(item.valueColors as Record<string, string>)
369-
.map(([key, val]) => ({ key: parseFloat(key), val }));
384+
.map(([key, val]) => ({ key: parseFloat(key), val }))
385+
.filter((entry) => !Number.isNaN(entry.key));
370386
colorArr.sort((a, b) => a.key - b.key);
371387

372388
const colorNums = colorArr.map((map) => map.key);

client/src/use/useAttributes.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export default function UseAttributes(
4848
login,
4949
}: UseAttributesParams,
5050
) {
51+
function getMissingValueColor(attribute?: Attribute) {
52+
return attribute?.valueColors?.[''];
53+
}
54+
5155
const attributes: Ref<Record<string, Attribute>> = ref({});
5256
const attributeFilters: Ref<AttributeFilter[]> = ref([]);
5357
const timelineGraphs: Ref<Record<string, TimelineGraph & {filtered?: boolean}>> = ref({});
@@ -490,28 +494,46 @@ export default function UseAttributes(
490494
return null;
491495
});
492496

493-
const getAttributeValueColor = (attribute: Attribute, val: string) => {
497+
const getAttributeValueColor = (attribute: Attribute, val?: string | number | boolean) => {
498+
if (val === undefined || val === null || val === '') {
499+
if (attribute.noneColor) {
500+
return attribute.noneColor;
501+
}
502+
return getMissingValueColor(attribute)
503+
|| attribute.color
504+
|| trackStyleManager.typeStyling.value.color(attribute.name);
505+
}
494506
if (attribute.datatype === 'text') {
495507
if (attribute.staticColor) {
496508
if (attribute.color) {
497509
return attribute.color;
498510
}
499511
return trackStyleManager.typeStyling.value.color(attribute.name);
500512
}
501-
if (attribute.valueColors && attribute.valueColors[val]) {
502-
return attribute.valueColors[val];
513+
const strVal = val.toString();
514+
if (attribute.valueColors && attribute.valueColors[strVal]) {
515+
return attribute.valueColors[strVal];
503516
}
504517
}
505-
return trackStyleManager.typeStyling.value.color(val);
518+
return trackStyleManager.typeStyling.value.color(val.toString());
506519
};
507520

508521
const numericalColorScaling = computed(() => {
509522
const autoColorIndex: Record<string, (data: string | number | boolean) => string> = {};
510523
Object.entries(attributes.value).forEach(([baseKey, item]) => {
511524
autoColorIndex[baseKey] = ((data: string | number | boolean) => {
525+
if (data === undefined || data === null || data === '') {
526+
if (item.noneColor) {
527+
return item.noneColor;
528+
}
529+
return getMissingValueColor(item)
530+
|| item.color
531+
|| trackStyleManager.typeStyling.value.color(item.name);
532+
}
512533
if (item.datatype === 'number' && item.valueColors && Object.keys(item.valueColors).length) {
513534
const colorArr = Object.entries(item.valueColors as Record<string, string>)
514-
.map(([key, val]) => ({ key: parseFloat(key), val }));
535+
.map(([key, val]) => ({ key: parseFloat(key), val }))
536+
.filter((entry) => !Number.isNaN(entry.key));
515537
colorArr.sort((a, b) => a.key - b.key);
516538

517539
const colorNums = colorArr.map((map) => map.key);

docs/UI-AttributeRendering.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Under the Rendering Tab for the Attribute Editor if you turn on Render there wil
2828
* **Value**
2929
* *Value Text Size* - Text size in pixel for the value. This will remain constant when scrolling in/out of the track.
3030
* *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.
31+
* 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).
3132
* **Dimensions**
3233
* *% Type* - For width and height it will size the area for the attribute based on the track width/height.
3334
* *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.

docs/UI-AttributeSwimlanes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ Simply use the key filter to select the attributes you whish to graph and config
3030

3131
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.
3232

33+
For text attributes in swimlanes, color resolution follows the Attribute Value Colors settings:
34+
35+
1. Missing (`undefined`/`null`) or empty (`''`) values use **None Color** when enabled.
36+
1. If **Static Color** is enabled, non-empty values use the attribute base color.
37+
1. Otherwise, per-value text color mappings are used.
38+
3339
## Swimlane Key
3440

3541
![Swimlane Key](images/AttributeTimeline/SwimlaneKey.png)

docs/UI-Attributes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,17 @@ Attributes that are of type text can have their colors preset and saved in the c
152152

153153
These colors can be used in the Attribute Rendering or the Swimlane views for attributes to properly render the system.
154154

155+
For text attributes, color selection follows this order:
156+
157+
1. If the value is missing (`undefined`/`null`) or empty (`''`), use **None Color** when enabled.
158+
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.
159+
1. If **Static Color** is enabled, all non-empty text values use the attribute base color.
160+
1. Otherwise, per-value color mappings are used.
161+
162+
!!! info
163+
164+
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.
165+
155166
![Edit Attribute Value Number Colors](images/Attributes/AttributeValueNumberColors.png)
156167

157168
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.

0 commit comments

Comments
 (0)