Skip to content

Commit

Permalink
✨ (discrete bar) use short entity names as labels (#3999)
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann authored Oct 3, 2024
1 parent 4a04800 commit 215ea00
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
import {
autoDetectSeriesStrategy,
autoDetectYColumnSlugs,
getShortNameForEntity,
makeSelectionArray,
} from "../chart/ChartUtils"
import { HorizontalAxis } from "../axis/Axis"
Expand All @@ -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
Expand Down Expand Up @@ -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 ??
Expand All @@ -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,
})

Expand All @@ -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 }
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
ColorSchemeName,
EntitySelectionMode,
makeIdForHumanConsumption,
getCountryByName,
} from "@ourworldindata/utils"
import { action, computed, observable } from "mobx"
import { observer } from "mobx-react"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -85,6 +92,7 @@ export interface StackedDiscreteBarChartManager extends ChartManager {

interface Item {
entityName: string
shortEntityName?: string
label: TextWrap
bars: Bar[]
totalValue: number
Expand Down Expand Up @@ -357,6 +365,7 @@ export class StackedDiscreteBarChart

return {
entityName,
shortEntityName: getShortNameForEntity(entityName),
bars,
totalValue,
}
Expand All @@ -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,
})

Expand All @@ -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 }
})
}
Expand Down
19 changes: 0 additions & 19 deletions packages/@ourworldindata/grapher/src/utils.ts

This file was deleted.

0 comments on commit 215ea00

Please sign in to comment.