Skip to content

Commit

Permalink
[#188261676] Bug fix: A date attribute can be added to a map to creat…
Browse files Browse the repository at this point in the history
…e a date legend

* In data-configuration-model.ts we provide `getLegendColorForDateValue`
* In legend.tsx we allow for the possibility that the attribute has type 'date'
* Added titles for the choropleth rectangles that show up on hover
* Detecting that attribute is of type 'date' and modifying the formatting of the labels that appear below the choropleth rectangles accordingly
* If label text collides, only show the min and max labels
* In data-configuration-model.ts modify `getCasesForLegendQuantile` to use `dataDisplayGetNumericValue` instead of `Dataset:getNumeric` so that dates will be properly retrieved
* Add a style for choroplet-rect so that pointer cursor will show on hover
  • Loading branch information
bfinzer committed Sep 25, 2024
1 parent f107127 commit 4832b9a
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {axisBottom, scaleLinear, format, select, range, min, max, ScaleQuantile, NumberValue} from "d3"
import {kChoroplethHeight} from "../../../data-display-types"
import {neededSigDigitsArrayForQuantiles} from "../../../../../utilities/math-utils"
import { axisBottom, format, max, min, NumberValue, range, scaleLinear, ScaleQuantile, select } from "d3"
import { kChoroplethHeight } from "../../../data-display-types"
import { neededSigDigitsArrayForQuantiles } from "../../../../../utilities/math-utils"
import { DatePrecision, determineLevels, formatDate, mapLevelToPrecision } from "../../../../../utilities/date-utils"
import { getStringBounds } from "../../../../axis/axis-utils"

export type ChoroplethLegendProps = {
isDate?: boolean,
tickSize?: number,
width?: number,
rectHeight?: number,
Expand All @@ -16,14 +19,16 @@ export type ChoroplethLegendProps = {
}

type ChoroplethScale = ScaleQuantile<string>

export function choroplethLegend(scale: ChoroplethScale, choroplethElt: SVGGElement, props: ChoroplethLegendProps) {
if (scale.domain().length === 0) {
select(choroplethElt).selectAll("*").remove()
return
}

const {
tickSize = 6, transform = '', width = 320, marginTop = 0, marginRight = 0, marginLeft = 0,
isDate, tickSize = 6, transform = '', width = 320,
marginTop = 0, marginRight = 0, marginLeft = 0,
ticks = 5, clickHandler, casesInQuantileSelectedHandler
} = props,
minValue = min(scale.domain()) ?? 0,
Expand All @@ -41,43 +46,58 @@ export function choroplethLegend(scale: ChoroplethScale, choroplethElt: SVGGElem
const thresholds = scale.quantiles(),
fullBoundaries = [minValue, ...thresholds, maxValue],
domainValues = scale.domain(),
significantDigits = neededSigDigitsArrayForQuantiles(fullBoundaries, domainValues)
significantDigits = neededSigDigitsArrayForQuantiles(fullBoundaries, domainValues),

Check warning on line 49 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L49

Added line #L49 was not covered by tests
dateLevels = isDate ? determineLevels(minValue, maxValue) : {increment: 1, outerLevel: 0, innerLevel: 0},
datePrecision = isDate ? mapLevelToPrecision(dateLevels.innerLevel + 1) : DatePrecision.None

const thresholdFormat = format(tickFormatSpec)
const thresholdFormat = isDate ? (date: number) => formatDate(date * 1000, datePrecision) ?? ''
: format(tickFormatSpec)

Check warning on line 54 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L54

Added line #L54 was not covered by tests

const x = scaleLinear()
.domain([-1, scale.range().length - 1])
.rangeRound([marginLeft, width - marginRight])
const legendScale = scaleLinear()

Check warning on line 56 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L56

Added line #L56 was not covered by tests
.domain([-1, scale.range().length - 1])
.rangeRound([marginLeft, width - marginRight]),
tickValues = range(thresholds.length),
tickFormat = (i: NumberValue) => thresholdFormat(thresholds[Number(i)]),

Check warning on line 60 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L59-L60

Added lines #L59 - L60 were not covered by tests
minMaxFormat = isDate ? thresholdFormat
: (d: number, i: number) => format(`.${significantDigits[i === 0 ? 0 : 5]}r`)(d),
minStringWidth = getStringBounds(minMaxFormat(minValue, 0)).width,
onlyShowMinMax = minStringWidth > 3 * width / 20 - 10

Check warning on line 64 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L63-L64

Added lines #L63 - L64 were not covered by tests

svg.append("g")
.selectAll("rect")
.data(scale.range())
.join("rect")
.attr('class', 'choropleth-rect')
.classed('legend-rect-selected',
(color) => {
return casesInQuantileSelectedHandler(scale.range().indexOf(color))
})
.attr('transform', transform)
.attr("x", (d, i) => x(i - 1))
.attr("x", (d, i) => legendScale(i - 1))

Check warning on line 76 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L76

Added line #L76 was not covered by tests
.attr("y", marginTop)
.attr("width", (d, i) => x(i) - x(i - 1))
.attr("width", (d, i) => legendScale(i) - legendScale(i - 1))

Check warning on line 78 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L78

Added line #L78 was not covered by tests
.attr("height", kChoroplethHeight /*height - marginTop - marginBottom*/)
.attr("fill", (d: string) => d)
.on('click', (event, color) => {
clickHandler(scale.range().indexOf(color), event.shiftKey)
})
.append('title')
.text((color) => {
const quantile = scale.range().indexOf(color)
return `${thresholdFormat(fullBoundaries[quantile])} - ${thresholdFormat(fullBoundaries[quantile + 1])}`

Check warning on line 87 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L85-L87

Added lines #L85 - L87 were not covered by tests
})

const tickValues = range(thresholds.length)
const tickFormat = (i: NumberValue) => thresholdFormat(thresholds[Number(i)])

svg.append("g")
const legendAxis = svg.append("g")

Check warning on line 91 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L91

Added line #L91 was not covered by tests
.attr('class', 'legend-axis')
.attr("transform", `${transform} translate(0,${kChoroplethHeight + marginTop})`)
.call(axisBottom(x)
if (!onlyShowMinMax) {
legendAxis.call(axisBottom(legendScale)

Check warning on line 95 in v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/components/legend/choropleth-legend/choropleth-legend.ts#L95

Added line #L95 was not covered by tests
.ticks(ticks)
.tickFormat(tickFormat)
.tickSize(tickSize)
.tickValues(tickValues))
}

svg.select('.legend-axis')
.append('g')
Expand All @@ -90,7 +110,7 @@ export function choroplethLegend(scale: ChoroplethScale, choroplethElt: SVGGElem
.attr('y', kChoroplethHeight)
.style('text-anchor', (d, i) => i ? 'end' : 'start')
.attr('x', (d, i) => i * width)
.text((d, i) => format(`.${significantDigits[i === 0 ? 0 : 5]}r`)(d))
.text(minMaxFormat)
)

return svg.node()
Expand Down
4 changes: 4 additions & 0 deletions v3/src/components/data-display/components/legend/legend.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
stroke-width: 2px !important;
}

.choropleth-rect {
cursor: pointer;
}

.legend-categories {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const Legend = function Legend({
? <CategoricalLegend
layerIndex={layerIndex}
setDesiredExtent={setDesiredExtent}/>
: attrType === 'numeric'
: attrType === 'numeric' || attrType === 'date'
? <NumericLegend
layerIndex={layerIndex}
setDesiredExtent={setDesiredExtent}/> : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const NumericLegend =
quantileScale.current.domain(valuesRef.current).range(schemeBlues[5])
choroplethLegend(quantileScale.current, choroplethElt,
{
isDate: dataConfiguration?.attributeType('legend') === 'date',
width: tileWidth,
marginLeft: 6, marginTop: labelHeight, marginRight: 6, ticks: 5,
clickHandler: (quantile: number, extend: boolean) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {applyModelChange} from "../../../models/history/apply-model-change"
import {cachedFnWithArgsFactory, onAnyAction} from "../../../utilities/mst-utils"
import { isFiniteNumber } from "../../../utilities/math-utils"
import { stringValuesToDateSeconds } from "../../../utilities/date-utils"
import {AttributeType, attributeTypes} from "../../../models/data/attribute"
import {DataSet, IDataSet} from "../../../models/data/data-set"
import {ICase} from "../../../models/data/data-set-types"
Expand Down Expand Up @@ -418,6 +419,11 @@ export const DataConfigurationModel = types
return self.legendQuantileScale(value)
},

getLegendColorForDateValue(value: string): string {
const dateValueArray = stringValuesToDateSeconds([value])
return self.legendQuantileScale(dateValueArray[0])

Check warning on line 424 in v3/src/components/data-display/models/data-configuration-model.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/models/data-configuration-model.ts#L422-L424

Added lines #L422 - L424 were not covered by tests
},

getCasesForCategoryValues(
primaryAttrRole: AttrRole, primaryValue: string, secondaryValue?: string, primarySplitValue?: string,
secondarySplitValue?: string, legendCat?: string, extend = false
Expand Down Expand Up @@ -487,7 +493,7 @@ export const DataConfigurationModel = types
max = quantile === thresholds.length ? Infinity : thresholds[quantile]
return legendID
? self.caseDataArray.filter((aCaseData: CaseData) => {
const value = dataset?.getNumeric(aCaseData.caseID, legendID)
const value = dataDisplayGetNumericValue(dataset, aCaseData.caseID, legendID)

Check warning on line 496 in v3/src/components/data-display/models/data-configuration-model.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/models/data-configuration-model.ts#L496

Added line #L496 was not covered by tests
return value !== undefined && value >= min && value < max
}).map((aCaseData: CaseData) => aCaseData.caseID)
: []
Expand Down Expand Up @@ -526,6 +532,8 @@ export const DataConfigurationModel = types
return self.getLegendColorForCategory(legendValue)
case 'numeric':
return self.getLegendColorForNumericValue(Number(legendValue))
case 'date':
return self.getLegendColorForDateValue(legendValue)

Check warning on line 536 in v3/src/components/data-display/models/data-configuration-model.ts

View check run for this annotation

Codecov / codecov/patch

v3/src/components/data-display/models/data-configuration-model.ts#L535-L536

Added lines #L535 - L536 were not covered by tests
default:
return ''
}
Expand Down

0 comments on commit 4832b9a

Please sign in to comment.