diff --git a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx index 3f79cc7bdec..028c9df6da6 100644 --- a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx @@ -75,8 +75,8 @@ import { } from "../color/ColorConstants" import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" import { - HorizontalColorLegendManager, HorizontalNumericColorLegend, + HorizontalNumericColorLegendProps, } from "../horizontalColorLegend/HorizontalColorLegends" import { BaseType, Selection } from "d3" import { TextWrap } from "@ourworldindata/components" @@ -514,7 +514,7 @@ export class DiscreteBarChart <> {this.renderDefs()} {this.showColorLegend && ( - + )} {this.showHorizontalAxis && ( <> @@ -833,6 +833,24 @@ export class DiscreteBarChart return sortBy(legendBins, (bin) => bin instanceof CategoricalBin) } + @computed + private get legendProps(): HorizontalNumericColorLegendProps { + return { + fontSize: this.fontSize, + legendX: this.legendX, + legendAlign: this.legendAlign, + legendMaxWidth: this.legendMaxWidth, + numericLegendData: this.numericLegendData, + numericBinSize: this.numericBinSize, + numericBinStroke: this.numericBinStroke, + equalSizeBins: this.equalSizeBins, + legendTitle: this.legendTitle, + numericLegendY: this.numericLegendY, + legendTextColor: this.legendTextColor, + legendTickSize: this.legendTickSize, + } + } + @computed get projectedDataColorInLegend(): string { // if a single color is in use, use that color in the legend if (uniqBy(this.series, "color").length === 1) @@ -840,7 +858,9 @@ export class DiscreteBarChart return DEFAULT_PROJECTED_DATA_COLOR_IN_LEGEND } - @computed get externalLegend(): HorizontalColorLegendManager | undefined { + @computed get externalNumericLegend(): + | HorizontalNumericColorLegendProps + | undefined { if (this.hasColorLegend) { return { numericLegendData: this.numericLegendData, @@ -860,10 +880,10 @@ export class DiscreteBarChart legendTextColor = "#555" legendTickSize = 1 - @computed get numericLegend(): HorizontalNumericColorLegend | undefined { + @computed get legendHeight(): number { return this.hasColorScale && this.manager.showLegend - ? new HorizontalNumericColorLegend({ manager: this }) - : undefined + ? HorizontalNumericColorLegend.height(this.legendProps) + : 0 } @computed get numericLegendY(): number { @@ -876,10 +896,6 @@ export class DiscreteBarChart : undefined } - @computed get legendHeight(): number { - return this.numericLegend?.height ?? 0 - } - // End of color legend props @computed get series(): DiscreteBarSeries[] { diff --git a/packages/@ourworldindata/grapher/src/chart/ChartInterface.ts b/packages/@ourworldindata/grapher/src/chart/ChartInterface.ts index cfd81654f90..669a7558f8f 100644 --- a/packages/@ourworldindata/grapher/src/chart/ChartInterface.ts +++ b/packages/@ourworldindata/grapher/src/chart/ChartInterface.ts @@ -7,7 +7,10 @@ import { } from "@ourworldindata/types" import { ColorScale } from "../color/ColorScale" import { HorizontalAxis, VerticalAxis } from "../axis/Axis" -import { HorizontalColorLegendManager } from "../horizontalColorLegend/HorizontalColorLegends" +import { + HorizontalCategoricalColorLegendProps, + HorizontalNumericColorLegendProps, +} from "../horizontalColorLegend/HorizontalColorLegends" // The idea of this interface is to try and start reusing more code across our Chart classes and make it easier // for a dev to work on a chart type they haven't touched before if they've worked with another that implements // this interface. @@ -41,9 +44,14 @@ export interface ChartInterface { /** * The legend that has been hidden from the chart plot (using `manager.hideLegend`). - * Used to create a global legend for faceted charts. + * Used to create a global categorical legend for faceted charts. + */ + externalCategoricalLegend?: HorizontalCategoricalColorLegendProps + /** + * The legend that has been hidden from the chart plot (using `manager.hideLegend`). + * Used to create a global numeric legend for faceted charts. */ - externalLegend?: HorizontalColorLegendManager + externalNumericLegend?: HorizontalNumericColorLegendProps /** * Which facet strategies the chart type finds reasonable in its current setting, if any. diff --git a/packages/@ourworldindata/grapher/src/facetChart/FacetChart.tsx b/packages/@ourworldindata/grapher/src/facetChart/FacetChart.tsx index ef091fb9110..3bfcaefa5e0 100644 --- a/packages/@ourworldindata/grapher/src/facetChart/FacetChart.tsx +++ b/packages/@ourworldindata/grapher/src/facetChart/FacetChart.tsx @@ -55,9 +55,11 @@ import { AxisConfig } from "../axis/AxisConfig" import { HorizontalAxis, VerticalAxis } from "../axis/Axis" import { HorizontalCategoricalColorLegend, + HorizontalCategoricalColorLegendProps, HorizontalColorLegend, - HorizontalColorLegendManager, + HorizontalColorLegendProps, HorizontalNumericColorLegend, + HorizontalNumericColorLegendProps, } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin, @@ -118,7 +120,7 @@ interface AxesInfo { @observer export class FacetChart extends React.Component - implements ChartInterface, HorizontalColorLegendManager + implements ChartInterface { transformTable(table: OwidTable): OwidTable { return table @@ -589,26 +591,28 @@ export class FacetChart // legend utils - @computed private get externalLegends(): HorizontalColorLegendManager[] { + @computed + private get externalCategoricalLegends(): HorizontalCategoricalColorLegendProps[] { return excludeUndefined( this.intermediateChartInstances.map( - (instance) => instance.externalLegend + (instance) => instance.externalCategoricalLegend ) ) } - @computed private get isNumericLegend(): boolean { - return this.externalLegends.some((legend) => - legend.numericLegendData?.some((bin) => bin instanceof NumericBin) + @computed + private get externalNumericLegends(): HorizontalNumericColorLegendProps[] { + return excludeUndefined( + this.intermediateChartInstances.map( + (instance) => instance.externalNumericLegend + ) ) } - @computed private get LegendClass(): - | typeof HorizontalNumericColorLegend - | typeof HorizontalCategoricalColorLegend { - return this.isNumericLegend - ? HorizontalNumericColorLegend - : HorizontalCategoricalColorLegend + @computed private get isNumericLegend(): boolean { + return this.externalNumericLegends.some((legend) => + legend.numericLegendData.some((bin) => bin instanceof NumericBin) + ) } @computed private get showLegend(): boolean { @@ -641,10 +645,21 @@ export class FacetChart return false } - private getExternalLegendProp< - Prop extends keyof HorizontalColorLegendManager, - >(prop: Prop): HorizontalColorLegendManager[Prop] | undefined { - for (const externalLegend of this.externalLegends) { + private getCategoricalExternalLegendProp< + Prop extends keyof HorizontalCategoricalColorLegendProps, + >(prop: Prop): HorizontalCategoricalColorLegendProps[Prop] | undefined { + for (const externalLegend of this.externalCategoricalLegends) { + if (externalLegend[prop] !== undefined) { + return externalLegend[prop] + } + } + return undefined + } + + private getNumericExternalLegendProp< + Prop extends keyof HorizontalNumericColorLegendProps, + >(prop: Prop): HorizontalNumericColorLegendProps[Prop] | undefined { + for (const externalLegend of this.externalNumericLegends) { if (externalLegend[prop] !== undefined) { return externalLegend[prop] } @@ -667,64 +682,50 @@ export class FacetChart // legend props - @computed get legendX(): number { - return this.bounds.x - } - - @computed get numericLegendY(): number { - return this.bounds.top - } - - @computed get categoryLegendY(): number { - return this.bounds.top - } - - @computed get legendMaxWidth(): number { - return this.bounds.width - } - - @computed get legendAlign(): HorizontalAlign { - return HorizontalAlign.left - } - - @computed get legendTitle(): string | undefined { - return this.getExternalLegendProp("legendTitle") - } - - @computed get legendHeight(): number | undefined { - return this.getExternalLegendProp("legendHeight") - } - - @computed get legendOpacity(): number | undefined { - return this.getExternalLegendProp("legendOpacity") - } - - @computed get legendTextColor(): Color | undefined { - return this.getExternalLegendProp("legendTextColor") - } - - @computed get legendTickSize(): number | undefined { - return this.getExternalLegendProp("legendTickSize") - } - - @computed get categoricalBinStroke(): Color | undefined { - return this.getExternalLegendProp("categoricalBinStroke") - } - - @computed get numericBinSize(): number | undefined { - return this.getExternalLegendProp("numericBinSize") - } - - @computed get numericBinStroke(): Color | undefined { - return this.getExternalLegendProp("numericBinStroke") + @computed private get commonLegendProps(): HorizontalColorLegendProps { + return { + fontSize: this.fontSize, + legendX: this.bounds.x, + legendMaxWidth: this.bounds.width, + legendAlign: HorizontalAlign.left, + onLegendMouseOver: this.onLegendMouseOver, + onLegendMouseLeave: this.onLegendMouseLeave, + } } - @computed get numericBinStrokeWidth(): number | undefined { - return this.getExternalLegendProp("numericBinStrokeWidth") + @computed + private get numericLegendProps(): HorizontalNumericColorLegendProps { + return { + ...this.commonLegendProps, + numericLegendY: this.bounds.top, + legendTitle: this.getNumericExternalLegendProp("legendTitle"), + legendTextColor: + this.getNumericExternalLegendProp("legendTextColor"), + legendTickSize: this.getNumericExternalLegendProp("legendTickSize"), + numericBinSize: this.getNumericExternalLegendProp("numericBinSize"), + numericBinStroke: + this.getNumericExternalLegendProp("numericBinStroke"), + numericBinStrokeWidth: this.getNumericExternalLegendProp( + "numericBinStrokeWidth" + ), + equalSizeBins: this.getNumericExternalLegendProp("equalSizeBins"), + numericLegendData: this.numericLegendData, + } } - @computed get equalSizeBins(): boolean | undefined { - return this.getExternalLegendProp("equalSizeBins") + @computed + private get categoricalLegendProps(): HorizontalCategoricalColorLegendProps { + return { + ...this.commonLegendProps, + categoryLegendY: this.bounds.top, + categoricalBinStroke: this.getCategoricalExternalLegendProp( + "categoricalBinStroke" + ), + hoverColors: this.hoverColors, + activeColors: this.activeColors, + categoricalLegendData: this.categoricalLegendData, + onLegendClick: this.onLegendClick, + } } @computed get hoverColors(): Color[] | undefined { @@ -750,13 +751,14 @@ export class FacetChart @computed get numericLegendData(): ColorScaleBin[] { if (!this.isNumericLegend || !this.hideFacetLegends) return [] - const allBins: ColorScaleBin[] = this.externalLegends.flatMap( - (legend) => [ - ...(legend.numericLegendData ?? []), - ...(legend.categoricalLegendData ?? []), - ] - ) - const uniqBins = this.getUniqBins(allBins) + const uniqBins = this.getUniqBins([ + ...this.externalCategoricalLegends.flatMap( + (legend) => legend.categoricalLegendData + ), + ...this.externalNumericLegends.flatMap( + (legend) => legend.numericLegendData + ), + ]) const sortedBins = sortBy( uniqBins, (bin) => bin instanceof CategoricalBin @@ -766,12 +768,9 @@ export class FacetChart @computed get categoricalLegendData(): CategoricalBin[] { if (this.isNumericLegend || !this.hideFacetLegends) return [] - const allBins: CategoricalBin[] = this.externalLegends - .flatMap((legend) => [ - ...(legend.numericLegendData ?? []), - ...(legend.categoricalLegendData ?? []), - ]) - .filter((bin) => bin instanceof CategoricalBin) as CategoricalBin[] + const allBins = this.externalCategoricalLegends.flatMap( + (legend) => legend.categoricalLegendData + ) const uniqBins = this.getUniqBins(allBins) const newBins = uniqBins.map( // remap index to ensure it's unique (the above procedure can lead to duplicates) @@ -814,7 +813,9 @@ export class FacetChart // end of legend props @computed private get legend(): HorizontalColorLegend { - return new this.LegendClass({ manager: this }) + return this.isNumericLegend + ? new HorizontalNumericColorLegend(this.numericLegendProps) + : new HorizontalCategoricalColorLegend(this.categoricalLegendProps) } /** @@ -858,11 +859,21 @@ export class FacetChart return { fontSize, shortenedLabel: label } } + private renderLegend(): React.ReactElement { + return this.isNumericLegend ? ( + + ) : ( + + ) + } + render(): React.ReactElement { - const { facetFontSize, LegendClass, showLegend } = this + const { facetFontSize, showLegend } = this return ( - {showLegend && } + {showLegend && this.renderLegend()} {this.placedSeries.map((facetChart, index: number) => { const ChartClass = ChartComponentClassMap.get(this.chartTypeName) ?? diff --git a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.test.ts b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.test.ts index bc38e848369..3a420462480 100755 --- a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.test.ts +++ b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.test.ts @@ -21,55 +21,53 @@ describe(HorizontalNumericColorLegend, () => { }) const legend = new HorizontalNumericColorLegend({ - manager: { numericLegendData: [bin] }, + numericLegendData: [bin], }) expect(legend.height).toBeGreaterThan(0) }) it("adds margins between categorical but not numeric bins", () => { const legend = new HorizontalNumericColorLegend({ - manager: { - numericLegendData: [ - new CategoricalBin({ - index: 0, - value: "a", - label: "a", - color: "#fff", - }), - new CategoricalBin({ - index: 0, - value: "b", - label: "b", - color: "#fff", - }), - new NumericBin({ - isFirst: true, - isOpenLeft: false, - isOpenRight: false, - min: 0, - max: 1, - displayMin: "0", - displayMax: "1", - color: "#fff", - }), - new NumericBin({ - isFirst: false, - isOpenLeft: false, - isOpenRight: false, - min: 1, - max: 2, - displayMin: "1", - displayMax: "2", - color: "#fff", - }), - new CategoricalBin({ - index: 0, - value: "c", - label: "c", - color: "#fff", - }), - ], - }, + numericLegendData: [ + new CategoricalBin({ + index: 0, + value: "a", + label: "a", + color: "#fff", + }), + new CategoricalBin({ + index: 0, + value: "b", + label: "b", + color: "#fff", + }), + new NumericBin({ + isFirst: true, + isOpenLeft: false, + isOpenRight: false, + min: 0, + max: 1, + displayMin: "0", + displayMax: "1", + color: "#fff", + }), + new NumericBin({ + isFirst: false, + isOpenLeft: false, + isOpenRight: false, + min: 1, + max: 2, + displayMin: "1", + displayMax: "2", + color: "#fff", + }), + new CategoricalBin({ + index: 0, + value: "c", + label: "c", + color: "#fff", + }), + ], }) const margin = legend["itemMargin"] @@ -100,7 +98,7 @@ describe(HorizontalCategoricalColorLegend, () => { }) const legend = new HorizontalCategoricalColorLegend({ - manager: { categoricalLegendData: [bin] }, + categoricalLegendData: [bin], }) expect(legend.height).toBeGreaterThan(0) }) diff --git a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx index d53b679238a..b8e42f95783 100644 --- a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx +++ b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx @@ -65,40 +65,47 @@ interface MarkLine { marks: CategoricalMark[] } -// TODO unify properties across categorical & numeric legend. -// This would make multiple legends per chart less convenient (only used in Map), but we shouldn't -// be using multiple anyway – instead the numeric should also handle categorical bins too. -export interface HorizontalColorLegendManager { +export interface HorizontalColorLegendProps { fontSize?: number legendX?: number legendAlign?: HorizontalAlign - legendTitle?: string - categoryLegendY?: number - numericLegendY?: number + legendWidth?: number - legendMaxWidth?: number - legendHeight?: number legendOpacity?: number - legendTextColor?: Color - legendTickSize?: number - categoricalLegendData?: CategoricalBin[] - categoricalFocusBracket?: CategoricalBin - categoricalBinStroke?: Color - numericLegendData?: ColorScaleBin[] - numericFocusBracket?: ColorScaleBin - numericBinSize?: number - numericBinStroke?: Color - numericBinStrokeWidth?: number - equalSizeBins?: boolean + legendMaxWidth?: number + onLegendMouseLeave?: () => void onLegendMouseOver?: (d: ColorScaleBin) => void - onLegendClick?: (d: ColorScaleBin) => void - activeColors?: string[] // inactive colors are grayed out +} + +export interface HorizontalCategoricalColorLegendProps + extends HorizontalColorLegendProps { + categoricalLegendData: CategoricalBin[] + categoricalBinStroke?: Color + categoryLegendY?: number + focusColors?: string[] // focused colors are bolded hoverColors?: string[] // non-hovered colors are muted + activeColors?: string[] // inactive colors are grayed out + + onLegendClick?: (d: ColorScaleBin) => void isStatic?: boolean } +export interface HorizontalNumericColorLegendProps + extends HorizontalColorLegendProps { + numericLegendData: ColorScaleBin[] + numericBinSize?: number + numericBinStroke?: Color + numericBinStrokeWidth?: number + equalSizeBins?: boolean + legendTitle?: string + numericFocusBracket?: ColorScaleBin + numericLegendY?: number + legendTextColor?: Color + legendTickSize?: number +} + const DEFAULT_NUMERIC_BIN_SIZE = 10 const DEFAULT_NUMERIC_BIN_STROKE = "#333" const DEFAULT_NUMERIC_BIN_STROKE_WIDTH = 0.3 @@ -110,60 +117,82 @@ const FOCUS_BORDER_COLOR = "#111" const SPACE_BETWEEN_CATEGORICAL_BINS = 7 const MINIMUM_LABEL_DISTANCE = 5 -export abstract class HorizontalColorLegend extends React.Component<{ - manager: HorizontalColorLegendManager -}> { - @computed protected get manager(): HorizontalColorLegendManager { - return this.props.manager - } - +export abstract class HorizontalColorLegend< + Props extends HorizontalColorLegendProps = HorizontalColorLegendProps, +> extends React.Component { @computed protected get legendX(): number { - return this.manager.legendX ?? 0 + return this.props.legendX ?? 0 } - @computed protected get categoryLegendY(): number { - return this.manager.categoryLegendY ?? 0 + @computed protected get legendAlign(): HorizontalAlign { + // Assume center alignment if none specified, for backwards-compatibility + return this.props.legendAlign ?? HorizontalAlign.center } - @computed protected get numericLegendY(): number { - return this.manager.numericLegendY ?? 0 + @computed protected get fontSize(): number { + return this.props.fontSize ?? BASE_FONT_SIZE } @computed protected get legendMaxWidth(): number | undefined { - return this.manager.legendMaxWidth + return this.props.legendMaxWidth } - @computed protected get legendHeight(): number { - return this.manager.legendHeight ?? 200 - } + abstract get height(): number + abstract get width(): number +} - @computed protected get legendAlign(): HorizontalAlign { - // Assume center alignment if none specified, for backwards-compatibility - return this.manager.legendAlign ?? HorizontalAlign.center - } +@observer +export class HorizontalNumericColorLegend extends HorizontalColorLegend { + base: React.RefObject = React.createRef() - @computed protected get fontSize(): number { - return this.manager.fontSize ?? BASE_FONT_SIZE + static height( + props: Pick< + HorizontalNumericColorLegendProps, + | "numericLegendData" + | "numericBinSize" + | "fontSize" + | "legendWidth" + | "legendMaxWidth" + | "legendTitle" + | "equalSizeBins" + | "legendAlign" + | "legendX" + > + ): number { + const legend = new HorizontalNumericColorLegend(props) + return legend.height } - @computed protected get legendTextColor(): Color { - return this.manager.legendTextColor ?? DEFAULT_TEXT_COLOR + static width( + props: Pick< + HorizontalNumericColorLegendProps, + | "numericLegendData" + | "numericBinSize" + | "fontSize" + | "legendWidth" + | "legendMaxWidth" + | "legendTitle" + | "equalSizeBins" + > + ): number { + const legend = new HorizontalNumericColorLegend(props) + return legend.width } - @computed protected get legendTickSize(): number { - return this.manager.legendTickSize ?? DEFAULT_TICK_SIZE + @computed private get numericLegendY(): number { + return this.props.numericLegendY ?? 0 } - abstract get height(): number - abstract get width(): number -} + @computed private get legendTextColor(): Color { + return this.props.legendTextColor ?? DEFAULT_TEXT_COLOR + } -@observer -export class HorizontalNumericColorLegend extends HorizontalColorLegend { - base: React.RefObject = React.createRef() + @computed private get legendTickSize(): number { + return this.props.legendTickSize ?? DEFAULT_TICK_SIZE + } @computed private get numericLegendData(): ColorScaleBin[] { - return this.manager.numericLegendData ?? [] + return this.props.numericLegendData ?? [] } @computed private get visibleBins(): ColorScaleBin[] { @@ -177,17 +206,16 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } @computed private get numericBinSize(): number { - return this.props.manager.numericBinSize ?? DEFAULT_NUMERIC_BIN_SIZE + return this.props.numericBinSize ?? DEFAULT_NUMERIC_BIN_SIZE } @computed private get numericBinStroke(): Color { - return this.props.manager.numericBinStroke ?? DEFAULT_NUMERIC_BIN_STROKE + return this.props.numericBinStroke ?? DEFAULT_NUMERIC_BIN_STROKE } @computed private get numericBinStrokeWidth(): number { return ( - this.props.manager.numericBinStrokeWidth ?? - DEFAULT_NUMERIC_BIN_STROKE_WIDTH + this.props.numericBinStrokeWidth ?? DEFAULT_NUMERIC_BIN_STROKE_WIDTH ) } @@ -212,7 +240,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } @computed private get maxWidth(): number { - return this.manager.legendMaxWidth ?? this.manager.legendWidth ?? 200 + return this.props.legendMaxWidth ?? this.props.legendWidth ?? 200 } private getTickLabelWidth(label: string): number { @@ -240,8 +268,8 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { @computed private get isAutoWidth(): boolean { return ( - this.manager.legendWidth === undefined && - this.manager.legendMaxWidth !== undefined + this.props.legendWidth === undefined && + this.props.legendMaxWidth !== undefined ) } @@ -270,7 +298,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { shareOfTotal: (bin.max - bin.min) / this.rangeSize, })) // Make sure the legend is big enough to avoid overlapping labels (including `raisedMode`) - if (this.manager.equalSizeBins) { + if (this.props.equalSizeBins) { // Try to keep the minimum close to the size of the "No data" bin, // so they look visually balanced somewhat. const minBinWidth = this.fontSize * 3.25 @@ -326,7 +354,6 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { @computed private get positionedBins(): PositionedBin[] { const { - manager, rangeSize, availableNumericWidth, visibleBins, @@ -334,6 +361,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { legendTitleWidth, x, } = this + const { equalSizeBins } = this.props let xOffset = x + legendTitleWidth let prevBin: ColorScaleBin | undefined @@ -344,7 +372,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { let marginLeft: number = isFirst ? 0 : this.itemMargin if (bin instanceof NumericBin) { - if (manager.equalSizeBins) { + if (equalSizeBins) { width = availableNumericWidth / numericBins.length } else { width = @@ -374,7 +402,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } @computed private get legendTitle(): TextWrap | undefined { - const { legendTitle } = this.manager + const { legendTitle } = this.props return legendTitle ? new TextWrap({ text: legendTitle, @@ -498,8 +526,9 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } @action.bound private onMouseMove(ev: MouseEvent | TouchEvent): void { - const { manager, base, positionedBins } = this - const { numericFocusBracket } = manager + const { base, positionedBins } = this + const { numericFocusBracket, onLegendMouseLeave, onLegendMouseOver } = + this.props if (base.current) { const mouse = getRelativeMouse(base.current, ev) @@ -512,8 +541,8 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { // If outside legend bounds, trigger onMouseLeave if there is an existing bin in focus. if (!this.bounds.contains(mouse)) { - if (numericFocusBracket && manager.onLegendMouseLeave) - return manager.onLegendMouseLeave() + if (numericFocusBracket && onLegendMouseLeave) + return onLegendMouseLeave() return } @@ -524,8 +553,8 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { newFocusBracket = bin.bin }) - if (newFocusBracket && manager.onLegendMouseOver) - manager.onLegendMouseOver(newFocusBracket) + if (newFocusBracket && onLegendMouseOver) + onLegendMouseOver(newFocusBracket) } } @@ -546,14 +575,8 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } render(): React.ReactElement { - const { - manager, - numericLabels, - numericBinSize, - positionedBins, - height, - } = this - const { numericFocusBracket } = manager + const { numericLabels, numericBinSize, positionedBins, height } = this + const { numericFocusBracket, legendOpacity } = this.props const stroke = this.numericBinStroke const strokeWidth = this.numericBinStrokeWidth @@ -604,7 +627,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { ? `url(#${bin.patternRef})` : bin.color } - opacity={manager.legendOpacity} // defaults to undefined which removes the prop + opacity={legendOpacity} // defaults to undefined which removes the prop stroke={ isFocus ? FOCUS_BORDER_COLOR : stroke } @@ -700,16 +723,47 @@ const NumericBinRect = (props: NumericBinRectProps) => { } @observer -export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { +export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { private rectPadding = 5 private markPadding = 5 + static height( + props: Pick< + HorizontalCategoricalColorLegendProps, + | "categoricalLegendData" + | "fontSize" + | "legendWidth" + | "legendMaxWidth" + | "legendAlign" + > + ): number { + const legend = new HorizontalCategoricalColorLegend(props) + return legend.height + } + + static numLines( + props: Pick< + HorizontalCategoricalColorLegendProps, + | "categoricalLegendData" + | "fontSize" + | "legendWidth" + | "legendMaxWidth" + > + ): number { + const legend = new HorizontalCategoricalColorLegend(props) + return legend.numLines + } + + @computed private get categoryLegendY(): number { + return this.props.categoryLegendY ?? 0 + } + @computed get width(): number { - return this.manager.legendWidth ?? this.manager.legendMaxWidth ?? 200 + return this.props.legendWidth ?? this.legendMaxWidth ?? 200 } @computed private get categoricalLegendData(): CategoricalBin[] { - return this.manager.categoricalLegendData ?? [] + return this.props.categoricalLegendData ?? [] } @computed private get visibleCategoricalBins(): CategoricalBin[] { @@ -814,8 +868,8 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { } renderLabels(): React.ReactElement { - const { manager, marks } = this - const { focusColors, hoverColors = [] } = manager + const { marks } = this + const { focusColors, hoverColors = [] } = this.props return ( @@ -847,8 +901,13 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { } renderSwatches(): React.ReactElement { - const { manager, marks } = this - const { activeColors, hoverColors = [] } = manager + const { marks } = this + const { + activeColors, + hoverColors = [], + legendOpacity, + categoricalBinStroke, + } = this.props return ( @@ -869,7 +928,7 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { const opacity = isNotHovered ? GRAPHER_OPACITY_MUTE - : manager.legendOpacity + : legendOpacity return ( @@ -891,21 +950,21 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { } renderInteractiveElements(): React.ReactElement { - const { manager, marks } = this + const { marks, props } = this return ( {marks.map((mark, index) => { const mouseOver = (): void => - manager.onLegendMouseOver - ? manager.onLegendMouseOver(mark.bin) + props.onLegendMouseOver + ? props.onLegendMouseOver(mark.bin) : undefined const mouseLeave = (): void => - manager.onLegendMouseLeave - ? manager.onLegendMouseLeave() + props.onLegendMouseLeave + ? props.onLegendMouseLeave() : undefined - const click = manager.onLegendClick - ? (): void => manager.onLegendClick?.(mark.bin) + const click = props.onLegendClick + ? (): void => props.onLegendClick?.(mark.bin) : undefined const cursor = click ? "pointer" : "default" @@ -948,7 +1007,7 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend { > {this.renderSwatches()} {this.renderLabels()} - {!this.manager.isStatic && this.renderInteractiveElements()} + {!this.props.isStatic && this.renderInteractiveElements()} ) } diff --git a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.test.ts b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.test.ts index bc77d3dee6f..42eef7e07db 100755 --- a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.test.ts +++ b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.test.ts @@ -293,16 +293,17 @@ describe("externalLegendBins", () => { const chart = new LineChart({ manager: { ...baseManager, showLegend: true }, }) - expect(chart["externalLegend"]).toBeUndefined() + expect(chart.externalCategoricalLegend).toBeUndefined() + expect(chart.externalNumericLegend).toBeUndefined() }) it("exposes externalLegendBins when legend is hidden", () => { const chart = new LineChart({ manager: { ...baseManager, showLegend: false }, }) - expect(chart["externalLegend"]?.categoricalLegendData?.length).toEqual( - 2 - ) + expect( + chart.externalCategoricalLegend?.categoricalLegendData?.length + ).toEqual(2) }) }) diff --git a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx index dd54175a663..04d4e9202a5 100644 --- a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx +++ b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx @@ -104,8 +104,9 @@ import { MultiColorPolyline } from "../scatterCharts/MultiColorPolyline" import { CategoricalColorAssigner } from "../color/CategoricalColorAssigner" import { darkenColorForLine } from "../color/ColorUtils" import { - HorizontalColorLegendManager, + HorizontalCategoricalColorLegendProps, HorizontalNumericColorLegend, + HorizontalNumericColorLegendProps, } from "../horizontalColorLegend/HorizontalColorLegends" import { AnnotationsMap, @@ -344,11 +345,7 @@ export class LineChart bounds?: Bounds manager: LineChartManager }> - implements - ChartInterface, - AxisManager, - ColorScaleManager, - HorizontalColorLegendManager + implements ChartInterface, AxisManager, ColorScaleManager { base: React.RefObject = React.createRef() @@ -498,7 +495,7 @@ export class LineChart @computed private get boundsWithoutColorLegend(): Bounds { return this.bounds.padTop( - this.hasColorLegend ? this.legendHeight + LEGEND_PADDING : 0 + this.hasColorLegend ? this.colorLegendHeight + LEGEND_PADDING : 0 ) } @@ -931,7 +928,7 @@ export class LineChart renderColorLegend(): React.ReactElement | void { if (this.hasColorLegend) - return + return } /** @@ -1157,10 +1154,28 @@ export class LineChart return this.manager.backgroundColor ?? GRAPHER_BACKGROUND_DEFAULT } - @computed get numericLegend(): HorizontalNumericColorLegend | undefined { + @computed get colorLegendHeight(): number { return this.hasColorScale && this.manager.showLegend - ? new HorizontalNumericColorLegend({ manager: this }) - : undefined + ? HorizontalNumericColorLegend.height(this.colorLegendProps) + : 0 + } + + get colorLegendProps(): HorizontalNumericColorLegendProps { + return { + fontSize: this.fontSize, + legendX: this.legendX, + legendAlign: this.legendAlign, + legendMaxWidth: this.legendMaxWidth, + numericLegendData: this.numericLegendData, + numericBinSize: this.numericBinSize, + numericBinStroke: this.numericBinStroke, + numericBinStrokeWidth: this.numericBinStrokeWidth, + equalSizeBins: this.equalSizeBins, + legendTitle: this.legendTitle, + numericLegendY: this.numericLegendY, + legendTextColor: this.legendTextColor, + legendTickSize: this.legendTickSize, + } } @computed get numericLegendY(): number { @@ -1173,10 +1188,6 @@ export class LineChart : undefined } - @computed get legendHeight(): number { - return this.numericLegend?.height ?? 0 - } - // End of color legend props @computed private get annotationsMap(): AnnotationsMap | undefined { @@ -1467,11 +1478,10 @@ export class LineChart return this.dualAxis.horizontalAxis } - @computed get externalLegend(): HorizontalColorLegendManager | undefined { + @computed get externalCategoricalLegend(): + | HorizontalCategoricalColorLegendProps + | undefined { if (!this.manager.showLegend) { - const numericLegendData = this.hasColorScale - ? this.numericLegendData - : [] const categoricalLegendData = this.hasColorScale ? [] : this.series.map( @@ -1483,6 +1493,20 @@ export class LineChart color: series.color, }) ) + return { + categoricalLegendData, + } + } + return undefined + } + + @computed get externalNumericLegend(): + | HorizontalNumericColorLegendProps + | undefined { + if (!this.manager.showLegend) { + const numericLegendData = this.hasColorScale + ? this.numericLegendData + : [] return { legendTitle: this.legendTitle, legendTextColor: this.legendTextColor, @@ -1492,7 +1516,6 @@ export class LineChart numericBinStroke: this.numericBinStroke, numericBinStrokeWidth: this.numericBinStrokeWidth, numericLegendData, - categoricalLegendData, } } return undefined diff --git a/packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx b/packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx index 6a3c29eb8c7..bfbcc27d1e8 100644 --- a/packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx +++ b/packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx @@ -18,7 +18,6 @@ import { observable, computed, action } from "mobx" import { observer } from "mobx-react" import { HorizontalCategoricalColorLegend, - HorizontalColorLegendManager, HorizontalNumericColorLegend, } from "../horizontalColorLegend/HorizontalColorLegends" import { MapProjectionGeos } from "./MapProjections" @@ -163,7 +162,7 @@ const renderFeaturesFor = ( @observer export class MapChart extends React.Component - implements ChartInterface, HorizontalColorLegendManager, ColorScaleManager + implements ChartInterface, ColorScaleManager { @observable focusEntity?: MapEntity @observable focusBracket?: MapBracket @@ -525,50 +524,72 @@ export class MapChart return this.categoryLegendHeight + this.numericLegendHeight + 10 } - @computed get numericLegendHeight(): number { - return this.numericLegend ? this.numericLegend.height : 0 + @computed private get hasCategoryLegend(): boolean { + return this.categoricalLegendData.length > 1 } - @computed get categoryLegendHeight(): number { - return this.categoryLegend ? this.categoryLegend.height + 5 : 0 + @computed private get hasNumericLegend(): boolean { + return this.numericLegendData.length > 1 } - @computed get categoryLegend(): - | HorizontalCategoricalColorLegend - | undefined { - return this.categoricalLegendData.length > 1 - ? new HorizontalCategoricalColorLegend({ manager: this }) - : undefined + @computed get categoryLegendHeight(): number { + return this.hasCategoryLegend + ? HorizontalCategoricalColorLegend.height({ + fontSize: this.fontSize, + legendAlign: this.legendAlign, + legendMaxWidth: this.legendMaxWidth, + categoricalLegendData: this.categoricalLegendData, + }) + 5 + : 0 + } + + @computed get categoryLegendNumLines(): number { + return this.hasCategoryLegend + ? HorizontalCategoricalColorLegend.numLines({ + fontSize: this.fontSize, + legendMaxWidth: this.legendMaxWidth, + categoricalLegendData: this.categoricalLegendData, + }) + : 0 } - @computed get numericLegend(): HorizontalNumericColorLegend | undefined { - return this.numericLegendData.length > 1 - ? new HorizontalNumericColorLegend({ manager: this }) - : undefined + @computed get numericLegendHeight(): number { + return this.hasNumericLegend + ? HorizontalNumericColorLegend.height({ + fontSize: this.fontSize, + legendX: this.legendX, + legendAlign: this.legendAlign, + legendMaxWidth: this.legendMaxWidth, + numericLegendData: this.numericLegendData, + equalSizeBins: this.equalSizeBins, + }) + : 0 } @computed get categoryLegendY(): number { - const { categoryLegend, bounds, categoryLegendHeight } = this + const { hasCategoryLegend, bounds, categoryLegendHeight } = this - if (categoryLegend) return bounds.bottom - categoryLegendHeight + if (hasCategoryLegend) return bounds.bottom - categoryLegendHeight return 0 } @computed get legendAlign(): HorizontalAlign { - if (this.numericLegend) return HorizontalAlign.center - const { numLines = 0 } = this.categoryLegend ?? {} - return numLines > 1 ? HorizontalAlign.left : HorizontalAlign.center + if (this.hasNumericLegend) return HorizontalAlign.center + + return this.categoryLegendNumLines > 1 + ? HorizontalAlign.left + : HorizontalAlign.center } @computed get numericLegendY(): number { const { - numericLegend, + hasNumericLegend, numericLegendHeight, bounds, categoryLegendHeight, } = this - if (numericLegend) + if (hasNumericLegend) return ( bounds.bottom - categoryLegendHeight - numericLegendHeight - 4 ) @@ -588,15 +609,37 @@ export class MapChart } renderMapLegend(): React.ReactElement { - const { numericLegend, categoryLegend } = this + const { hasNumericLegend, hasCategoryLegend } = this return ( <> - {numericLegend && ( - + {hasNumericLegend && ( + )} - {categoryLegend && ( - + {hasCategoryLegend && ( + )} ) diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx index fc1ee9d3f3e..3b0dba2ad0e 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx @@ -844,7 +844,6 @@ export class ScatterPlotChart legendY={this.legendY} activeColors={this.activeColors} focusColors={this.focusColors} - isStatic={manager.isStatic} /> {sizeLegend && ( <> diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx index 3d01ce36072..a63195b3998 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx @@ -85,7 +85,7 @@ import { } from "../lineCharts/LineChartHelpers" import { SelectionArray } from "../selection/SelectionArray" import { Halo } from "@ourworldindata/components" -import { HorizontalColorLegendManager } from "../horizontalColorLegend/HorizontalColorLegends" +import { HorizontalCategoricalColorLegendProps } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin } from "../color/ColorScaleBin" import { OWID_NON_FOCUSED_GRAY, @@ -590,7 +590,9 @@ export class SlopeChart : 0 } - @computed get externalLegend(): HorizontalColorLegendManager | undefined { + @computed get externalCategoricalLegend(): + | HorizontalCategoricalColorLegendProps + | undefined { if (!this.manager.showLegend) { const categoricalLegendData = this.series.map( (series, index) => diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx index 5b40a3086bb..e6824d85d18 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx @@ -38,7 +38,7 @@ import { select } from "d3-selection" import { ColorSchemes } from "../color/ColorSchemes" import { SelectionArray } from "../selection/SelectionArray" import { CategoricalBin } from "../color/ColorScaleBin" -import { HorizontalColorLegendManager } from "../horizontalColorLegend/HorizontalColorLegends" +import { HorizontalCategoricalColorLegendProps } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalColorAssigner, CategoricalColorMap, @@ -436,7 +436,9 @@ export class AbstractStackedChart return this.unstackedSeries } - @computed get externalLegend(): HorizontalColorLegendManager | undefined { + @computed get externalCategoricalLegend(): + | HorizontalCategoricalColorLegendProps + | undefined { if (!this.manager.showLegend) { const categoricalLegendData = this.series .map( diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx index c95f9cc3c72..7de3e4b740c 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx @@ -58,10 +58,7 @@ import { makeTooltipRoundingNotice, makeTooltipToleranceNotice, } from "../tooltip/Tooltip" -import { - HorizontalCategoricalColorLegend, - HorizontalColorLegendManager, -} from "../horizontalColorLegend/HorizontalColorLegends" +import { HorizontalCategoricalColorLegend } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" import { DualAxis, HorizontalAxis, VerticalAxis } from "../axis/Axis" import { ColorScale, ColorScaleManager } from "../color/ColorScale" @@ -262,7 +259,7 @@ export class MarimekkoChart manager: MarimekkoChartManager containerElement?: HTMLDivElement }> - implements ChartInterface, HorizontalColorLegendManager, ColorScaleManager + implements ChartInterface, ColorScaleManager { base: React.RefObject = React.createRef() @@ -551,7 +548,7 @@ export class MarimekkoChart return this.bounds .padBottom(this.longestLabelHeight + 2) .padBottom(labelLinesHeight) - .padTop(this.legend.height + this.legendPaddingTop) + .padTop(this.legendHeight + this.legendPaddingTop) .padLeft(marginToEnsureWidestEntityLabelFitsEvenIfAtX0) } @@ -838,7 +835,7 @@ export class MarimekkoChart // legend props @computed get legendPaddingTop(): number { - return this.legend.height > 0 ? this.baseFontSize : 0 + return this.legendHeight > 0 ? this.baseFontSize : 0 } @computed get legendX(): number { @@ -897,8 +894,13 @@ export class MarimekkoChart this.focusColorBin = undefined } - @computed private get legend(): HorizontalCategoricalColorLegend { - return new HorizontalCategoricalColorLegend({ manager: this }) + @computed private get legendHeight(): number { + return HorizontalCategoricalColorLegend.height({ + fontSize: this.fontSize, + legendAlign: this.legendAlign, + legendWidth: this.legendWidth, + categoricalLegendData: this.categoricalLegendData, + }) } @computed private get formatColumn(): CoreColumn { @@ -1053,7 +1055,18 @@ export class MarimekkoChart } detailsMarker={manager.detailsMarkerInSvg} /> - + {this.renderBars()} {target && ( { const chart = new StackedAreaChart({ manager: { ...baseManager, showLegend: true }, }) - expect(chart["externalLegend"]).toBeUndefined() + expect(chart.externalCategoricalLegend).toBeUndefined() }) it("exposes externalLegendBins when legend is hidden", () => { const chart = new StackedAreaChart({ manager: { ...baseManager, showLegend: false }, }) - expect(chart["externalLegend"]?.categoricalLegendData?.length).toEqual( - 2 - ) + expect( + chart.externalCategoricalLegend?.categoricalLegendData?.length + ).toEqual(2) }) }) diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx index 922f0a3d9f6..4fdc2e90b7c 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx @@ -210,7 +210,7 @@ export class StackedBarChart @computed protected get paddingForLegendTop(): number { return this.showHorizontalLegend - ? this.horizontalColorLegend.height + 8 + ? this.horizontalColorLegendHeight + 8 : 0 } @@ -306,13 +306,9 @@ export class StackedBarChart @computed get sidebarWidth(): number { if (!this.manager.showLegend) return 0 - const { - sidebarMinWidth, - sidebarMaxWidth, - verticalColorLegend: legendDimensions, - } = this + const { sidebarMinWidth, sidebarMaxWidth, verticalColorLegend } = this return Math.max( - Math.min(legendDimensions.width, sidebarMaxWidth), + Math.min(verticalColorLegend.width, sidebarMaxWidth), sidebarMinWidth ) } @@ -329,8 +325,13 @@ export class StackedBarChart } @computed - private get horizontalColorLegend(): HorizontalCategoricalColorLegend { - return new HorizontalCategoricalColorLegend({ manager: this }) + private get horizontalColorLegendHeight(): number { + return HorizontalCategoricalColorLegend.height({ + fontSize: this.fontSize, + legendAlign: this.legendAlign, + legendWidth: this.legendWidth, + categoricalLegendData: this.categoricalLegendData, + }) } @computed get formatColumn(): CoreColumn { @@ -480,7 +481,18 @@ export class StackedBarChart if (!showLegend) return return showHorizontalLegend ? ( - + ) : ( { const chart = new StackedDiscreteBarChart({ manager: { ...baseManager, showLegend: true }, }) - expect(chart["legend"].height).toBeGreaterThan(0) + expect(chart["legendHeight"]).toBeGreaterThan(0) expect(chart["categoricalLegendData"].length).toBeGreaterThan(0) - expect(chart["externalLegend"]).toBeUndefined() + expect(chart["externalCategoricalLegend"]).toBeUndefined() }) it("exposes externalLegendBins when showLegend is false", () => { const chart = new StackedDiscreteBarChart({ manager: { ...baseManager, showLegend: false }, }) - expect(chart["legend"].height).toEqual(0) + expect(chart["legendHeight"]).toEqual(0) expect(chart["categoricalLegendData"].length).toEqual(0) - expect(chart["externalLegend"]?.categoricalLegendData?.length).toEqual( - 2 - ) + expect( + chart["externalCategoricalLegend"]?.categoricalLegendData?.length + ).toEqual(2) }) }) diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx index b298d2ae160..d26714dbb60 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx @@ -68,7 +68,7 @@ import { StackedPoint, StackedSeries } from "./StackedConstants" import { ColorSchemes } from "../color/ColorSchemes" import { HorizontalCategoricalColorLegend, - HorizontalColorLegendManager, + HorizontalCategoricalColorLegendProps, } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" import { isDarkColor } from "../color/ColorUtils" @@ -131,7 +131,7 @@ export class StackedDiscreteBarChart manager: StackedDiscreteBarChartManager containerElement?: HTMLDivElement }> - implements ChartInterface, HorizontalColorLegendManager + implements ChartInterface { base: React.RefObject = React.createRef() @@ -330,8 +330,8 @@ export class StackedDiscreteBarChart @computed private get boundsWithoutLegend(): Bounds { return this.bounds.padTop( - this.showLegend && this.legend.height > 0 - ? this.legend.height + this.legendPaddingTop + this.showLegend && this.legendHeight > 0 + ? this.legendHeight + this.legendPaddingTop : 0 ) } @@ -525,7 +525,9 @@ export class StackedDiscreteBarChart return this.showLegend ? this.legendBins : [] } - @computed get externalLegend(): HorizontalColorLegendManager | undefined { + @computed get externalCategoricalLegend(): + | HorizontalCategoricalColorLegendProps + | undefined { if (!this.showLegend) { return { categoricalLegendData: this.legendBins, @@ -546,8 +548,13 @@ export class StackedDiscreteBarChart this.focusSeriesName = undefined } - @computed private get legend(): HorizontalCategoricalColorLegend { - return new HorizontalCategoricalColorLegend({ manager: this }) + @computed private get legendHeight(): number { + return HorizontalCategoricalColorLegend.height({ + fontSize: this.fontSize, + legendAlign: this.legendAlign, + legendWidth: this.legendWidth, + categoricalLegendData: this.categoricalLegendData, + }) } @computed private get formatColumn(): CoreColumn { @@ -724,7 +731,19 @@ export class StackedDiscreteBarChart renderLegend(): React.ReactElement | void { if (!this.showLegend) return - return + return ( + + ) } renderStatic(): React.ReactElement { diff --git a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx index 5d33814e6f7..38585abcdcf 100644 --- a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx +++ b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx @@ -24,11 +24,6 @@ export interface VerticalColorLegendProps { isStatic?: boolean } -type VerticalColorLegendPropsMinimal = Pick< - VerticalColorLegendProps, - "legendItems" | "maxLegendWidth" | "fontSize" | "legendTitle" -> - export interface LegendItem { label?: string minText?: string @@ -49,10 +44,12 @@ export class VerticalColorLegend extends React.Component + ): { width: number; height: number } { const legend = new VerticalColorLegend(props) return { width: legend.width,