diff --git a/packages/@ourworldindata/grapher/src/lineLegend/LineLegend.tsx b/packages/@ourworldindata/grapher/src/lineLegend/LineLegend.tsx index 6f75106720..969207a66f 100644 --- a/packages/@ourworldindata/grapher/src/lineLegend/LineLegend.tsx +++ b/packages/@ourworldindata/grapher/src/lineLegend/LineLegend.tsx @@ -56,10 +56,11 @@ export interface LineLabelSeries extends ChartSeries { } interface SizedSeries extends LineLabelSeries { - textWrap: TextWrapGroup + textWrap: TextWrap | TextWrapGroup annotationTextWrap?: TextWrap width: number height: number + fontWeight?: number } interface PlacedSeries extends SizedSeries { @@ -162,7 +163,24 @@ class LineLabels extends React.Component<{ nonFocused && !hovered ? BACKGROUND_TEXT_COLOR : darkenColorForText(series.color) - return ( + return series.textWrap instanceof TextWrap ? ( + + {series.textWrap.render(labelText.x, labelText.y, { + textProps: { + fill: textColor, + opacity: this.textOpacityForSeries(series), + textAnchor: this.anchor, + // might override the textWrap's fontWeight + fontWeight: series.fontWeight, + }, + })} + + ) : ( {series.textWrap.render(labelText.x, labelText.y, { showTextOutline: this.showTextOutline, @@ -381,52 +399,85 @@ export class LineLegend extends React.Component { return this.props.verticalAlign ?? VerticalAlign.middle } - @computed.struct get sizedLabels(): SizedSeries[] { - const { fontSize, maxWidth } = this - const maxTextWidth = maxWidth - DEFAULT_CONNECTOR_LINE_WIDTH - const maxAnnotationWidth = Math.min(maxTextWidth, 150) + @computed private get textMaxWidth(): number { + return this.maxWidth - DEFAULT_CONNECTOR_LINE_WIDTH + } + + private makeLabelTextWrap( + series: LineLabelSeries + ): TextWrap | TextWrapGroup { + if (!series.formattedValue) { + return new TextWrap({ + text: series.label, + maxWidth: this.textMaxWidth, + fontSize: this.fontSize, + // focused labels are bold while non-focused labels are regular. + // if we used the actual font-weights to compute the layout, + // the layout would be jumpy since different font weights lead + // to different text widths. that's why we always use bold labels + // for comupting the layout. + fontWeight: 700, + }) + } + + // text label fragment + const textLabel = { text: series.label, fontWeight: 700 } + + // value label fragment + const newLine = series.placeFormattedValueInNewLine + ? "always" + : "avoid-wrap" + const valueLabel = { + text: series.formattedValue, + fontWeight: 400, + newLine, + } + + return new TextWrapGroup({ + fragments: [textLabel, valueLabel], + maxWidth: this.textMaxWidth, + fontSize: this.fontSize, + }) + } + private makeAnnotationTextWrap( + series: LineLabelSeries + ): TextWrap | undefined { + if (!series.annotation) return undefined + const maxWidth = Math.min(this.textMaxWidth, 150) + return new TextWrap({ + text: series.annotation, + maxWidth, + fontSize: this.fontSize * 0.9, + lineHeight: 1, + }) + } + + @computed.struct get sizedLabels(): SizedSeries[] { + const { fontWeight: globalFontWeight } = this return this.props.labelSeries.map((label) => { - // if a formatted value is given, make the main label bold - const fontWeight = label.formattedValue ? 700 : this.fontWeight - - const mainLabel = { text: label.label, fontWeight } - const valueLabel = label.formattedValue - ? { - text: label.formattedValue, - newLine: (label.placeFormattedValueInNewLine - ? "always" - : "avoid-wrap") as "always" | "avoid-wrap", - } - : undefined - const labelFragments = excludeUndefined([mainLabel, valueLabel]) - const textWrap = new TextWrapGroup({ - fragments: labelFragments, - maxWidth: maxTextWidth, - fontSize, - }) - const annotationTextWrap = label.annotation - ? new TextWrap({ - text: label.annotation, - maxWidth: maxAnnotationWidth, - fontSize: fontSize * 0.9, - lineHeight: 1, - }) - : undefined - - const annotationWidth = annotationTextWrap - ? annotationTextWrap.width - : 0 + const textWrap = this.makeLabelTextWrap(label) + const annotationTextWrap = this.makeAnnotationTextWrap(label) + + const annotationWidth = annotationTextWrap?.width ?? 0 const annotationHeight = annotationTextWrap ? ANNOTATION_PADDING + annotationTextWrap.height : 0 + // font weight priority: + // series focus state > globally set font weight > default font weight + const activeFontWeight = label.focus?.active ? 700 : undefined + const seriesFontWeight = label.formattedValue ? 700 : undefined + const fontWeight = + activeFontWeight ?? seriesFontWeight ?? globalFontWeight + return { ...label, textWrap, annotationTextWrap, width: Math.max(textWrap.width, annotationWidth), height: textWrap.height + annotationHeight, + fontWeight, } }) }