terriajs
Version:
Geospatial data visualization platform.
292 lines (268 loc) • 9.85 kB
text/typescript
import i18next from "i18next";
import { computed, makeObservable } from "mobx";
import isDefined from "../Core/isDefined";
import { JsonObject } from "../Core/Json";
import ConstantColorMap from "../Map/ColorMap/ConstantColorMap";
import ContinuousColorMap from "../Map/ColorMap/ContinuousColorMap";
import DiscreteColorMap from "../Map/ColorMap/DiscreteColorMap";
import EnumColorMap from "../Map/ColorMap/EnumColorMap";
import TableMixin from "../ModelMixins/TableMixin";
import createStratumInstance from "../Models/Definition/createStratumInstance";
import LoadableStratum from "../Models/Definition/LoadableStratum";
import { BaseModel } from "../Models/Definition/Model";
import StratumFromTraits from "../Models/Definition/StratumFromTraits";
import LegendTraits, {
LegendItemTraits
} from "../Traits/TraitsClasses/LegendTraits";
import TableStyle from "./TableStyle";
export class ColorStyleLegend extends LoadableStratum(LegendTraits) {
/**
*
* @param catalogItem
* @param index index of column in catalogItem (if -1 or undefined, then default style will be used)
*/
constructor(
readonly catalogItem: TableMixin.Instance,
readonly legendItemOverrides: Partial<LegendItemTraits> = {}
) {
super();
makeObservable(this);
}
duplicateLoadableStratum(newModel: BaseModel): this {
return new ColorStyleLegend(newModel as TableMixin.Instance) as this;
}
get tableStyle() {
return this.catalogItem.activeTableStyle;
}
// Keep these here until we deprecate LegendTraits in TableColorStyleTraits
// See https://github.com/TerriaJS/terriajs/issues/6356
get oldLegendTraits() {
return this.tableStyle.colorTraits.legend;
}
get url() {
return this.oldLegendTraits.url;
}
get imageScaling() {
return this.oldLegendTraits.imageScaling;
}
get urlMimeType() {
return this.oldLegendTraits.urlMimeType;
}
get backgroundColor() {
return this.oldLegendTraits.backgroundColor;
}
/** Add column title as legend title if showing a Discrete or Enum ColorMap */
get title() {
if (this.oldLegendTraits.title) return this.oldLegendTraits.title;
if (
this.tableStyle.colorMap instanceof ContinuousColorMap ||
this.tableStyle.colorMap instanceof DiscreteColorMap ||
this.tableStyle.colorMap instanceof EnumColorMap
)
return this.tableStyle.colorColumn?.title ?? this.tableStyle.title;
}
get items(): StratumFromTraits<LegendItemTraits>[] {
// This is a bit dodgy - but should be fine until we deprecate LegendTraits in TableColorStyleTraits
if (this.oldLegendTraits.items && this.oldLegendTraits.items.length > 0)
return this.oldLegendTraits.traits.items.toJson(
this.oldLegendTraits.items
);
let items: StratumFromTraits<LegendItemTraits>[] = [];
const colorMap = this.tableStyle.colorMap;
if (colorMap instanceof DiscreteColorMap) {
items = this._createLegendItemsFromDiscreteColorMap(
this.tableStyle,
colorMap,
this.legendItemOverrides
);
} else if (colorMap instanceof ContinuousColorMap) {
items = this._createLegendItemsFromContinuousColorMap(
this.tableStyle,
colorMap,
this.legendItemOverrides
);
} else if (colorMap instanceof EnumColorMap) {
items = this._createLegendItemsFromEnumColorMap(
this.tableStyle,
colorMap,
this.legendItemOverrides
);
} else if (colorMap instanceof ConstantColorMap) {
items = this._createLegendItemsFromConstantColorMap(
this.tableStyle,
colorMap,
this.legendItemOverrides
);
}
return items;
}
private _createLegendItemsFromContinuousColorMap(
style: TableStyle,
colorMap: ContinuousColorMap,
legendItemOverrides: Partial<LegendItemTraits>
): StratumFromTraits<LegendItemTraits>[] {
const colorColumn = style.colorColumn;
const nullBin =
colorColumn &&
colorColumn.valuesAsNumbers.numberOfValidNumbers <
colorColumn.valuesAsNumbers.values.length
? [
createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: style.colorTraits.nullColor || "rgba(0, 0, 0, 0)",
addSpacingAbove: true,
title:
style.colorTraits.nullLabel ||
i18next.t("models.tableData.legendNullLabel")
})
]
: [];
const outlierBin = style.tableColorMap.outlierColor
? [
createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: style.tableColorMap.outlierColor.toCssColorString(),
addSpacingAbove: true,
title:
style.colorTraits.outlierLabel ||
i18next.t("models.tableData.legendZFilterLabel")
})
]
: [];
return new Array(this.tableStyle.colorTraits.legendTicks)
.fill(0)
.map((_, i) => {
// Use maxValue for last value so we don't get funky JS precision
const value =
i === this.tableStyle.colorTraits.legendTicks - 1
? colorMap.maxValue
: colorMap.minValue +
(colorMap.maxValue - colorMap.minValue) *
(i / (this.tableStyle.colorTraits.legendTicks - 1));
return createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: colorMap.mapValueToColor(value).toCssColorString(),
title: this._formatValue(value, this.tableStyle.numberFormatOptions)
});
})
.reverse()
.concat(nullBin, outlierBin);
}
private _createLegendItemsFromDiscreteColorMap(
style: TableStyle,
colorMap: DiscreteColorMap,
legendItemOverrides: Partial<LegendItemTraits>
): StratumFromTraits<LegendItemTraits>[] {
const colorColumn = style.colorColumn;
const minimum =
colorColumn && colorColumn.valuesAsNumbers.minimum !== undefined
? colorColumn.valuesAsNumbers.minimum
: 0.0;
const nullBin =
colorColumn &&
colorColumn.valuesAsNumbers.numberOfValidNumbers <
colorColumn.valuesAsNumbers.values.length
? [
createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: style.colorTraits.nullColor || "rgba(0, 0, 0, 0)",
addSpacingAbove: true,
title: style.colorTraits.nullLabel || "(No value)"
})
]
: [];
return colorMap.maximums
.map((maximum, i) => {
const isBottom = i === 0;
const formattedMin = isBottom
? this._formatValue(minimum, this.tableStyle.numberFormatOptions)
: this._formatValue(
colorMap.maximums[i - 1],
this.tableStyle.numberFormatOptions
);
const formattedMax = this._formatValue(
maximum,
this.tableStyle.numberFormatOptions
);
return createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: colorMap.colors[i].toCssColorString(),
title: `${formattedMin} to ${formattedMax}`
// titleBelow: isBottom ? minimum.toString() : undefined, // TODO: format value
// titleAbove: maximum.toString() // TODO: format value
});
})
.reverse()
.concat(nullBin);
}
private _createLegendItemsFromEnumColorMap(
style: TableStyle,
colorMap: EnumColorMap,
legendItemOverrides: Partial<LegendItemTraits>
): StratumFromTraits<LegendItemTraits>[] {
const colorColumn = style.colorColumn;
// Show null bin if data has null values - or if EnumColorMap doesn't have enough colors to show all values
const nullBin =
colorColumn &&
(colorColumn.uniqueValues.numberOfNulls > 0 ||
colorColumn.uniqueValues.values.length > colorMap.values.length)
? [
createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: style.colorTraits.nullColor || "rgba(0, 0, 0, 0)",
addSpacingAbove: true,
title: style.colorTraits.nullLabel || "(No value)"
})
]
: [];
// Aggregate colours (don't show multiple legend items for the same colour)
const colorMapValues = colorMap.values.reduce<{
[color: string]: string[];
}>((prev, current, i) => {
const cssCol = colorMap.colors[i].toCssColorString();
if (isDefined(prev[cssCol])) {
prev[cssCol].push(current);
} else {
prev[cssCol] = [current];
}
return prev;
}, {});
return Object.entries(colorMapValues)
.map(([color, multipleTitles]) =>
multipleTitles.length > 1
? createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
multipleTitles,
color
})
: createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
title: multipleTitles[0],
color
})
)
.concat(nullBin);
}
private _createLegendItemsFromConstantColorMap(
_style: TableStyle,
colorMap: ConstantColorMap,
legendItemOverrides: Partial<LegendItemTraits>
): StratumFromTraits<LegendItemTraits>[] {
return [
createStratumInstance(LegendItemTraits, {
...legendItemOverrides,
color: colorMap.color.toCssColorString(),
title: colorMap.title
})
];
}
private _formatValue(
value: number,
format: Intl.NumberFormatOptions | JsonObject | undefined
): string {
return (
format?.maximumFractionDigits ? value : Math.round(value)
).toLocaleString(undefined, format);
}
}