Skip to content

Commit

Permalink
[FE-17705] Dynamically update categorical legend domain (#666)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmatzenbach authored May 3, 2024
1 parent 2a86cff commit f8e0dd5
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 91 deletions.
203 changes: 136 additions & 67 deletions src/chart-addons/stacked-legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,38 @@ function setColorScaleDomain_v1(domain) {
}
}

export function getLegendStateFromChart(chart, useMap, selectedLayer) {
async function getTopValues(layer, layerName, size) {
const OFFSET = 0
const chartId = layer?.crossfilter()?.chartId
const dimension = layer?.getState()?.encoding?.color?.field
const newCf = layer?.crossfilter()?.cloneWithChartId(chartId, layerName)

if (chartId && dimension && newCf) {
return newCf
.dimension(dimension)
.group()
.topAsync(size, OFFSET)
.then(results => results?.map(result => result.key0) ?? null)
.catch(error => null)
} else {
return null
}
}

function getUpdatedDomainRange(newDomain, oldDomain, range, defaultColor) {
const oldDomainRange = new Map(
[...oldDomain].map((key, index) => [key, range[index]])
)
const newDomainRange = new Map(
[...newDomain].map(key => [key, oldDomainRange.get(key) ?? defaultColor])
)
return {
newDomain: [...newDomainRange.keys()],
newRange: [...newDomainRange.values()]
}
}

export async function getLegendStateFromChart(chart, useMap, selectedLayer) {
// the getLegendStateFromChart in _doRender gets called from few different options
// and some of them are calling with all layers in chart.
// As a result, a legend for each layer is rendered.
Expand All @@ -119,77 +150,105 @@ export function getLegendStateFromChart(chart, useMap, selectedLayer) {
}

return toLegendState(
getRealLayers(chart.getLayerNames()).map(layer_name => {
const layer = chart.getLayer(layer_name)
if (typeof layer.getPrimaryColorScaleAndLegend === "function") {
const vega = chart.getMaterializedVegaSpec()
const [
color_scale,
color_legend,
legend_position
] = layer.getPrimaryColorScaleAndLegend()
if (color_scale === null) {
return {}
}
const color_scale_name = color_scale.name || ""
const materialized_color_scale = vega.scales.find(
scale => scale.name === color_scale_name
)
if (!materialized_color_scale) {
return {}
}
await Promise.all(
getRealLayers(chart.getLayerNames()).map(async layer_name => {
const layer = chart.getLayer(layer_name)
if (typeof layer.getPrimaryColorScaleAndLegend === "function") {
const vega = chart.getMaterializedVegaSpec()
const [
color_scale,
color_legend,
legend_position
] = layer.getPrimaryColorScaleAndLegend()
if (color_scale === null) {
return {}
}
const color_scale_name = color_scale.name || ""
const materialized_color_scale = vega.scales.find(
scale => scale.name === color_scale_name
)
if (!materialized_color_scale) {
return {}
}

return {
...materialized_color_scale,
legend: color_legend,
legend_position,
version: 2.0
}
} else {
// TODO(croot): this can be removed once all raster layer types are
// transitioned to the getPrimaryColorScaleName/getLegendDefinitionForProperty
// form above
const layerState = layer.getState()
const color = layer.getState().encoding.color
let color_legend_descriptor = null

return {
...materialized_color_scale,
legend: color_legend,
legend_position,
version: 2.0
}
} else {
// TODO(croot): this can be removed once all raster layer types are
// transitioned to the getPrimaryColorScaleName/getLegendDefinitionForProperty
// form above
const layerState = layer.getState()
const color = layer.getState().encoding.color

let color_legend_descriptor = null

if (
(layers.length > 1 || _.isEqual(selectedLayer, layerState)) &&
typeof color !== "undefined"
) {
if (
typeof color.scale === "object" &&
color.scale.domain === "auto"
(layers.length > 1 || _.isEqual(selectedLayer, layerState)) &&
typeof color !== "undefined"
) {
color_legend_descriptor = {
...color,
scale: {
...color.scale,
if (
typeof color.scale === "object" &&
color.scale.domain === "auto"
) {
color_legend_descriptor = {
...color,
scale: {
...color.scale,
domain: layer.colorDomain()
}
}
} else if (
typeof color.scale === "undefined" &&
color.domain === "auto"
) {
color_legend_descriptor = {
...color,
domain: layer.colorDomain()
}
}
} else if (
typeof color.scale === "undefined" &&
color.domain === "auto"
) {
color_legend_descriptor = {
...color,
domain: layer.colorDomain()
} else if (color.type === "ordinal") {
const colValues = await getTopValues(
layer,
layer_name,
color.domain.length
)
const { newDomain, newRange } = colValues
? getUpdatedDomainRange(
colValues,
color.domain,
color.range,
color.defaultOtherRange
)
: {}

// don't show the other category when new domain is smaller than original max
color.otherActive = false
if (!color.hideOther && newDomain.length < color.domain.length) {
color.otherActive = false
} else if (!color.hideOther) {
color.otherActive = true
}

color_legend_descriptor =
newDomain && newRange
? { ...color, domain: newDomain, range: newRange }
: { ...color }
} else {
color_legend_descriptor = { ...color }
}
} else {
color_legend_descriptor = { ...color }
}
} else {
color_legend_descriptor = { ...color }
}

return {
...color_legend_descriptor,
version: 1.0
return {
...color_legend_descriptor,
version: 1.0
}
}
}
}),
})
),
chart,
useMap
)
Expand Down Expand Up @@ -237,7 +296,9 @@ export function handleLegendOpen(index = 0) {
: false
}))
)
this.legend().setState(getLegendStateFromChart(this))
getLegendStateFromChart(this).then(state => {
this.legend().setState(state)
})
}

export function handleLegendLock({ locked, index = 0 }) {
Expand Down Expand Up @@ -281,7 +342,9 @@ export function handleLegendLock({ locked, index = 0 }) {
if (redraw) {
this.renderAsync() // not setting the state for the legend here because it'll happen on the redraw
} else {
this.legend().setState(getLegendStateFromChart(this))
getLegendStateFromChart(this).then(state => {
this.legend().setState(state)
})
}
}
}
Expand Down Expand Up @@ -311,8 +374,10 @@ export function handleLegendInput({ domain, index = 0 }) {
)
}

this.legend().setState(getLegendStateFromChart(this))
this.renderAsync()
getLegendStateFromChart(this).then(state => {
this.legend().setState(state)
this.renderAsync()
})
}

function isQuantitativeType(type_string) {
Expand Down Expand Up @@ -350,10 +415,14 @@ function legendState_v1(state, useMap) {
range:
!state.hideOther &&
state.hasOwnProperty("showOther") &&
state.showOther === true
state.showOther &&
state.otherActive
? state.range.concat([state.defaultOtherRange]) // When Other is toggled OFF, don't show color swatch in legend
: state.range,
domain: state.hideOther ? state.domain : state.domain.concat(["Other"]),
domain:
state.hideOther || !state.otherActive
? state.domain
: state.domain.concat(["Other"]),
position
}
} else if (
Expand Down
49 changes: 25 additions & 24 deletions src/charts/raster-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,33 +807,34 @@ export default function rasterChart(parent, useMap, chartGroup, _mapboxgl) {
}
}

const state = getLegendStateFromChart(_chart, useMap, selectedLayer)
_legend.setState(state)

if (_chart.isLoaded()) {
if (Object.keys(data).length) {
_chart._setOverlay({
data: data.image,
bounds: _renderBoundsMap[data.nonce],
nonce: data.nonce,
browser,
redraw: Boolean(redraw)
})
_hasBeenRendered = true
getLegendStateFromChart(_chart, useMap, selectedLayer).then(state => {
_legend.setState(state)

if (_chart.isLoaded()) {
if (Object.keys(data).length) {
_chart._setOverlay({
data: data.image,
bounds: _renderBoundsMap[data.nonce],
nonce: data.nonce,
browser,
redraw: Boolean(redraw)
})
_hasBeenRendered = true
} else {
_chart._setOverlay({
data: null,
bounds: null,
nonce: null,
browser,
redraw: Boolean(redraw)
})
}
} else {
_chart._setOverlay({
data: null,
bounds: null,
nonce: null,
browser,
redraw: Boolean(redraw)
_chart.map().once("style.load", () => {
_chart._doRender(data, redraw, doNotForceData)
})
}
} else {
_chart.map().once("style.load", () => {
_chart._doRender(data, redraw, doNotForceData)
})
}
})
}

_chart._doRedraw = function(data) {
Expand Down

0 comments on commit f8e0dd5

Please sign in to comment.