diff --git a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx index ad01d3562b3b3f55fe5a61474678f270309a7721..2d4148ae35b259e001f92b5662c4b14ec97013d2 100644 --- a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx +++ b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx @@ -69,6 +69,36 @@ function loadGeoJson(geoJsonPath, callback) { } } +export function getLegendTitles(groups, columnSettings) { + const formatMetric = (value, compact) => + formatValue(value, { ...columnSettings, compact }); + + const compact = shouldUseCompactFormatting(groups, formatMetric); + + return groups.map((group, index) => { + const min = formatMetric(group[0], compact); + const max = formatMetric(group[group.length - 1], compact); + return index === groups.length - 1 + ? `${min} +` // the last value in the list + : min !== max + ? `${min} - ${max}` // typical case + : min; // special case to avoid zero-width ranges e.g. $88-$88 + }); +} + +// if the average formatted length is greater than this, we switch to compact formatting +const AVERAGE_LENGTH_CUTOFF = 5; +function shouldUseCompactFormatting(groups, formatMetric) { + const minValues = groups.map(([x]) => x); + const maxValues = groups.slice(0, -1).map(group => group[group.length - 1]); + const allValues = minValues.concat(maxValues); + const formattedValues = allValues.map(value => formatMetric(value, false)); + const averageLength = + formattedValues.reduce((sum, { length }) => sum + length, 0) / + formattedValues.length; + return averageLength > AVERAGE_LENGTH_CUTOFF; +} + export default class ChoroplethMap extends Component { static propTypes = {}; @@ -197,9 +227,6 @@ export default class ChoroplethMap extends Component { const getFeatureValue = feature => valuesMap[getFeatureKey(feature)]; - const formatMetric = value => - formatValue(value, settings.column(cols[metricIndex])); - const rowByFeatureKey = new Map(rows.map(row => [getRowKey(row), row])); const getFeatureClickObject = (row, feature) => ({ @@ -253,10 +280,7 @@ export default class ChoroplethMap extends Component { const domain = Array.from(domainSet); const _heatMapColors = settings["map.colors"] || HEAT_MAP_COLORS; - const heatMapColors = - domain.length < _heatMapColors.length - ? _heatMapColors.slice(_heatMapColors.length - domain.length) - : _heatMapColors; + const heatMapColors = _heatMapColors.slice(-domain.length); const groups = ss.ckmeans(domain, heatMapColors.length); const groupBoundaries = groups.slice(1).map(cluster => cluster[0]); @@ -266,14 +290,8 @@ export default class ChoroplethMap extends Component { .domain(groupBoundaries) .range(heatMapColors); - const legendColors = heatMapColors; - const legendTitles = heatMapColors.map((color, index) => { - const min = groups[index][0]; - const max = groups[index].slice(-1)[0]; - return index === heatMapColors.length - 1 - ? formatMetric(min) + " +" - : formatMetric(min) + " - " + formatMetric(max); - }); + const columnSettings = settings.column(cols[metricIndex]); + const legendTitles = getLegendTitles(groups, columnSettings); const getColor = feature => { const value = getFeatureValue(feature); @@ -295,7 +313,7 @@ export default class ChoroplethMap extends Component { className={className} aspectRatio={aspectRatio} legendTitles={legendTitles} - legendColors={legendColors} + legendColors={heatMapColors} gridSize={gridSize} hovered={hovered} onHoverChange={onHoverChange} diff --git a/frontend/test/metabase/visualizations/components/ChoroplethMap.unit.spec.js b/frontend/test/metabase/visualizations/components/ChoroplethMap.unit.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..8329c1760761033c6c7509eeef61bea214729383 --- /dev/null +++ b/frontend/test/metabase/visualizations/components/ChoroplethMap.unit.spec.js @@ -0,0 +1,35 @@ +import { getLegendTitles } from "metabase/visualizations/components/ChoroplethMap"; + +describe("getLegendTitles", () => { + it("should not format short values compactly", () => { + const groups = [[1.12, 1.12, 1.25], [1.32, 1.48], [9, 12, 13]]; + const columnSettings = { + column: { base_type: "type/Float" }, + number_style: "currency", + currency: "USD", + currency_style: "symbol", + }; + + const titles = getLegendTitles(groups, columnSettings); + + expect(titles).toEqual(["$1.12 - $1.25", "$1.32 - $1.48", "$9.00 +"]); + }); + + it("should format long values compactly", () => { + const groups = [ + [1000.12, 1100.12, 1200.25], + [2000.32, 2200, 2500.48], + [11000, 12000, 13000], + ]; + const columnSettings = { + column: { base_type: "type/Float" }, + number_style: "currency", + currency: "USD", + currency_style: "symbol", + }; + + const titles = getLegendTitles(groups, columnSettings); + + expect(titles).toEqual(["$1.0k - $1.2k", "$2.0k - $2.5k", "$11.0k +"]); + }); +});