UNPKG

terriajs

Version:

Geospatial data visualization platform.

292 lines (268 loc) 9.85 kB
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; } @computed get tableStyle() { return this.catalogItem.activeTableStyle; } // Keep these here until we deprecate LegendTraits in TableColorStyleTraits // See https://github.com/TerriaJS/terriajs/issues/6356 @computed get oldLegendTraits() { return this.tableStyle.colorTraits.legend; } @computed get url() { return this.oldLegendTraits.url; } @computed get imageScaling() { return this.oldLegendTraits.imageScaling; } @computed get urlMimeType() { return this.oldLegendTraits.urlMimeType; } @computed get backgroundColor() { return this.oldLegendTraits.backgroundColor; } /** Add column title as legend title if showing a Discrete or Enum ColorMap */ @computed 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; } @computed 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); } }