From 215ea0032c017140e9b4a3a5540368ed918780eb Mon Sep 17 00:00:00 2001 From: Sophia Mersmann Date: Thu, 3 Oct 2024 13:11:13 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20(discrete=20bar)=20use=20short=20en?= =?UTF-8?q?tity=20names=20as=20labels=20(#3999)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/barCharts/DiscreteBarChart.tsx | 28 ++++++++++++++++--- .../barCharts/DiscreteBarChartConstants.ts | 2 ++ .../grapher/src/chart/ChartUtils.tsx | 26 ++++++++++++++++- .../grapher/src/slopeCharts/SlopeChart.tsx | 7 +++-- .../src/stackedCharts/MarimekkoChart.tsx | 12 ++++---- .../stackedCharts/StackedDiscreteBarChart.tsx | 27 ++++++++++++++++-- packages/@ourworldindata/grapher/src/utils.ts | 19 ------------- 7 files changed, 86 insertions(+), 35 deletions(-) delete mode 100644 packages/@ourworldindata/grapher/src/utils.ts diff --git a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx index 635c50bcdb0..9002cb98543 100644 --- a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx @@ -58,6 +58,7 @@ import { import { autoDetectSeriesStrategy, autoDetectYColumnSlugs, + getShortNameForEntity, makeSelectionArray, } from "../chart/ChartUtils" import { HorizontalAxis } from "../axis/Axis" @@ -81,6 +82,9 @@ const labelToBarPadding = 5 const LEGEND_PADDING = 25 const DEFAULT_PROJECTED_DATA_COLOR_IN_LEGEND = "#787878" +// if an entity name exceeds this width, we use the short name instead (if available) +const SOFT_MAX_LABEL_WIDTH = 90 + export interface Label { valueString: string timeString: string @@ -890,6 +894,8 @@ export class DiscreteBarChart time, colorValue, seriesName, + entityName: seriesName, + shortEntityName: getShortNameForEntity(seriesName), // the error color should never be used but I prefer it here instead of throwing an exception if something goes wrong color: color ?? @@ -908,11 +914,13 @@ export class DiscreteBarChart return this.series.map((series) => { // make sure we're dealing with a single-line text fragment - const seriesName = series.seriesName.replace(/\n/g, " ").trim() + const entityName = series.entityName.replace(/\n/g, " ").trim() + + const maxLegendWidth = 0.3 * this.boundsWithoutColorLegend.width let label = new TextWrap({ - text: seriesName, - maxWidth: this.boundsWithoutColorLegend.width * 0.3, + text: entityName, + maxWidth: maxLegendWidth, ...this.legendLabelStyle, }) @@ -924,13 +932,25 @@ export class DiscreteBarChart step < 10 // safety net ) { label = new TextWrap({ - text: seriesName, + text: entityName, maxWidth: label.maxWidth + 20, ...this.legendLabelStyle, }) step += 1 } + // if the label is too long, use the short name instead + const tooLong = + label.width > SOFT_MAX_LABEL_WIDTH || + label.width > maxLegendWidth + if (tooLong && series.shortEntityName) { + label = new TextWrap({ + text: series.shortEntityName, + maxWidth: label.maxWidth, + ...this.legendLabelStyle, + }) + } + return { ...series, label } }) } diff --git a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChartConstants.ts b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChartConstants.ts index ac8a29b8c2e..f1e4b62dcf9 100644 --- a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChartConstants.ts +++ b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChartConstants.ts @@ -5,6 +5,8 @@ import { CoreValueType, Time } from "@ourworldindata/types" import { TextWrap } from "@ourworldindata/components" export interface DiscreteBarSeries extends ChartSeries { + entityName: string + shortEntityName?: string yColumn: CoreColumn value: number time: Time diff --git a/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx b/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx index b70253a4af2..7a17f9980dd 100644 --- a/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx +++ b/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx @@ -1,9 +1,14 @@ import React from "react" -import { Box } from "@ourworldindata/utils" +import { Box, getCountryByName } from "@ourworldindata/utils" import { SeriesStrategy, EntityName } from "@ourworldindata/types" import { LineChartSeries } from "../lineCharts/LineChartConstants" import { SelectionArray } from "../selection/SelectionArray" import { ChartManager } from "./ChartManager" +import { + GRAPHER_SIDE_PANEL_CLASS, + GRAPHER_TIMELINE_CLASS, + GRAPHER_SETTINGS_CLASS, +} from "../core/GrapherConstants" export const autoDetectYColumnSlugs = (manager: ChartManager): string[] => { if (manager.yColumnSlugs && manager.yColumnSlugs.length) @@ -83,3 +88,22 @@ export const makeSelectionArray = ( selection instanceof SelectionArray ? selection : new SelectionArray(selection ?? []) + +export function isElementInteractive(element: HTMLElement): boolean { + const interactiveTags = ["a", "button", "input"] + const interactiveClassNames = [ + GRAPHER_TIMELINE_CLASS, + GRAPHER_SIDE_PANEL_CLASS, + GRAPHER_SETTINGS_CLASS, + ].map((className) => `.${className}`) + + const selector = [...interactiveTags, ...interactiveClassNames].join(", ") + + // check if the target is an interactive element or contained within one + return element.closest(selector) !== null +} + +export function getShortNameForEntity(entityName: string): string | undefined { + const country = getCountryByName(entityName) + return country?.shortName +} diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx index eb46d10964d..cf16a98fc0a 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx @@ -53,7 +53,11 @@ import { SlopeEntryProps, } from "./SlopeChartConstants" import { OwidTable } from "@ourworldindata/core-table" -import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils" +import { + autoDetectYColumnSlugs, + makeSelectionArray, + isElementInteractive, +} from "../chart/ChartUtils" import { AxisConfig, AxisManager } from "../axis/AxisConfig" import { VerticalAxis } from "../axis/Axis" import { VerticalAxisComponent } from "../axis/AxisViews" @@ -63,7 +67,6 @@ import { } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" import { NoDataSection } from "../scatterCharts/NoDataSection" -import { isElementInteractive } from "../utils" export interface SlopeChartManager extends ChartManager { isModalOpen?: boolean diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx index cf1b21edc9d..e98f481292c 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx @@ -21,7 +21,6 @@ import { ColorSchemeName, EntitySelectionMode, makeIdForHumanConsumption, - getCountryByName, } from "@ourworldindata/utils" import { action, computed, observable } from "mobx" import { observer } from "mobx-react" @@ -43,7 +42,11 @@ import { colorScaleConfigDefaults, } from "@ourworldindata/types" import { OwidTable, CoreColumn } from "@ourworldindata/core-table" -import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils" +import { + autoDetectYColumnSlugs, + getShortNameForEntity, + makeSelectionArray, +} from "../chart/ChartUtils" import { StackedPoint, StackedSeries } from "./StackedConstants" import { ColorSchemes } from "../color/ColorSchemes" import { TooltipFooterIcon } from "../tooltip/TooltipProps.js" @@ -1770,8 +1773,3 @@ export class MarimekkoChart return yColumns.every((col) => col.isEmpty) ? "No matching data" : "" } } - -function getShortNameForEntity(entityName: string): string | undefined { - const country = getCountryByName(entityName) - return country?.shortName -} diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx index a595dae72f5..6d435b100d0 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx @@ -44,7 +44,11 @@ import { NoDataModal } from "../noDataModal/NoDataModal" import { AxisConfig } from "../axis/AxisConfig" import { ChartInterface } from "../chart/ChartInterface" import { OwidTable, CoreColumn } from "@ourworldindata/core-table" -import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils" +import { + autoDetectYColumnSlugs, + getShortNameForEntity, + makeSelectionArray, +} from "../chart/ChartUtils" import { stackSeries, withMissingValuesAsZeroes, @@ -76,6 +80,9 @@ import { CategoricalColorAssigner } from "../color/CategoricalColorAssigner.js" import { Halo } from "../halo/Halo" import { TextWrap } from "@ourworldindata/components" +// if an entity name exceeds this width, we use the short name instead (if available) +const SOFT_MAX_LABEL_WIDTH = 90 + const labelToBarPadding = 5 export interface StackedDiscreteBarChartManager extends ChartManager { @@ -85,6 +92,7 @@ export interface StackedDiscreteBarChartManager extends ChartManager { interface Item { entityName: string + shortEntityName?: string label: TextWrap bars: Bar[] totalValue: number @@ -357,6 +365,7 @@ export class StackedDiscreteBarChart return { entityName, + shortEntityName: getShortNameForEntity(entityName), bars, totalValue, } @@ -374,9 +383,11 @@ export class StackedDiscreteBarChart // make sure we're dealing with a single-line text fragment const entityName = item.entityName.replace(/\n/g, " ").trim() + const maxLegendWidth = 0.3 * this.boundsWithoutLegend.width + let label = new TextWrap({ text: entityName, - maxWidth: this.boundsWithoutLegend.width * 0.3, + maxWidth: maxLegendWidth, ...this.labelStyle, }) @@ -395,6 +406,18 @@ export class StackedDiscreteBarChart step += 1 } + // if the label is too long, use the short name instead + const tooLong = + label.width > SOFT_MAX_LABEL_WIDTH || + label.width > maxLegendWidth + if (tooLong && item.shortEntityName) { + label = new TextWrap({ + text: item.shortEntityName, + maxWidth: label.maxWidth, + ...this.labelStyle, + }) + } + return { ...item, label } }) } diff --git a/packages/@ourworldindata/grapher/src/utils.ts b/packages/@ourworldindata/grapher/src/utils.ts deleted file mode 100644 index 72b6e2f4d96..00000000000 --- a/packages/@ourworldindata/grapher/src/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - GRAPHER_SIDE_PANEL_CLASS, - GRAPHER_TIMELINE_CLASS, - GRAPHER_SETTINGS_CLASS, -} from "./core/GrapherConstants" - -export function isElementInteractive(element: HTMLElement): boolean { - const interactiveTags = ["a", "button", "input"] - const interactiveClassNames = [ - GRAPHER_TIMELINE_CLASS, - GRAPHER_SIDE_PANEL_CLASS, - GRAPHER_SETTINGS_CLASS, - ].map((className) => `.${className}`) - - const selector = [...interactiveTags, ...interactiveClassNames].join(", ") - - // check if the target is an interactive element or contained within one - return element.closest(selector) !== null -}