diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx
index 3b0dba2ad0..fc27bdb6a7 100644
--- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx
+++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx
@@ -61,7 +61,10 @@ import {
ConnectedScatterLegend,
ConnectedScatterLegendManager,
} from "./ConnectedScatterLegend"
-import { VerticalColorLegend } from "../verticalColorLegend/VerticalColorLegend"
+import {
+ VerticalColorLegendComponent,
+ VerticalColorLegend,
+} from "../verticalColorLegend/VerticalColorLegend"
import { DualAxisComponent } from "../axis/AxisViews"
import { DualAxis, HorizontalAxis, VerticalAxis } from "../axis/Axis"
@@ -506,15 +509,14 @@ export class ScatterPlotChart
return this.tooltipState.target?.series
}
- @computed private get verticalColorLegend(): {
- width: number
- height: number
- } {
- return VerticalColorLegend.dimensions({
+ @computed private get verticalColorLegend(): VerticalColorLegend {
+ return new VerticalColorLegend({
maxLegendWidth: this.maxLegendWidth,
fontSize: this.fontSize,
legendItems: this.legendItems,
legendTitle: this.legendTitle,
+ activeColors: this.activeColors,
+ focusColors: this.focusColors,
})
}
@@ -832,18 +834,15 @@ export class ScatterPlotChart
/>
))}
{this.points}
-
{sizeLegend && (
<>
diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx
index 4fdc2e90b7..31db65c60b 100644
--- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx
+++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx
@@ -19,6 +19,7 @@ import { NoDataModal } from "../noDataModal/NoDataModal"
import {
VerticalColorLegend,
LegendItem,
+ VerticalColorLegendComponent,
} from "../verticalColorLegend/VerticalColorLegend"
import { TooltipFooterIcon } from "../tooltip/TooltipProps.js"
import {
@@ -313,14 +314,12 @@ export class StackedBarChart
)
}
- @computed private get verticalColorLegend(): {
- width: number
- height: number
- } {
- return VerticalColorLegend.dimensions({
+ @computed private get verticalColorLegend(): VerticalColorLegend {
+ return new VerticalColorLegend({
maxLegendWidth: this.maxLegendWidth,
fontSize: this.fontSize,
legendItems: this.legendItems,
+ activeColors: this.activeColors,
})
}
@@ -480,6 +479,13 @@ export class StackedBarChart
if (!showLegend) return
+ const eventListeners = this.manager.isStatic
+ ? undefined
+ : {
+ onLegendMouseOver: this.onLegendMouseOver,
+ onLegendMouseLeave: this.onLegendMouseLeave,
+ }
+
return showHorizontalLegend ? (
) : (
-
)
}
diff --git a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.stories.tsx b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.stories.tsx
index 38f6a6bdc6..9b721761de 100644
--- a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.stories.tsx
+++ b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.stories.tsx
@@ -2,6 +2,7 @@ import React from "react"
import {
VerticalColorLegend,
VerticalColorLegendProps,
+ VerticalColorLegendComponent,
} from "./VerticalColorLegend"
export default {
@@ -26,9 +27,10 @@ const props: VerticalColorLegendProps = {
}
export const CategoricalBins = (): React.ReactElement => {
+ const verticalColorLegend = new VerticalColorLegend(props)
return (
)
}
diff --git a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx
index 38585abcdc..4eaa9b73d8 100644
--- a/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx
+++ b/packages/@ourworldindata/grapher/src/verticalColorLegend/VerticalColorLegend.tsx
@@ -1,8 +1,12 @@
import React from "react"
-import { sum, max, makeIdForHumanConsumption } from "@ourworldindata/utils"
+import {
+ sum,
+ max,
+ makeIdForHumanConsumption,
+ isEmpty,
+} from "@ourworldindata/utils"
import { TextWrap } from "@ourworldindata/components"
import { computed } from "mobx"
-import { observer } from "mobx-react"
import {
GRAPHER_FONT_SCALE_11_2,
BASE_FONT_SIZE,
@@ -14,14 +18,8 @@ export interface VerticalColorLegendProps {
maxLegendWidth?: number
fontSize?: number
legendTitle?: string
- onLegendMouseOver?: (color: string) => void
- onLegendClick?: (color: string) => void
- onLegendMouseLeave?: () => void
- legendX?: number
- legendY?: number
- activeColors?: Color[]
- focusColors?: Color[]
- isStatic?: boolean
+ activeColors?: Color[] // inactive colors are grayed out
+ focusColors?: Color[] // focused colors are bolded
}
export interface LegendItem {
@@ -39,22 +37,13 @@ interface SizedLegendSeries {
yOffset: number
}
-@observer
-export class VerticalColorLegend extends React.Component {
- private rectPadding = 5
- private lineHeight = 5
+export class VerticalColorLegend {
+ rectPadding = 5
+ lineHeight = 5
- static dimensions(
- props: Pick<
- VerticalColorLegendProps,
- "legendItems" | "maxLegendWidth" | "fontSize" | "legendTitle"
- >
- ): { width: number; height: number } {
- const legend = new VerticalColorLegend(props)
- return {
- width: legend.width,
- height: legend.height,
- }
+ props: VerticalColorLegendProps
+ constructor(props: VerticalColorLegendProps) {
+ this.props = props
}
@computed private get maxLegendWidth(): number {
@@ -65,11 +54,11 @@ export class VerticalColorLegend extends React.Component void
+ onLegendClick?: (color: string) => void
+ onLegendMouseLeave?: () => void
}
+}): React.ReactElement {
+ return (
+
+ {state.title &&
+ state.title.render(x, y, {
+ textProps: {
+ fontWeight: 700,
+ },
+ })}
+
+
+ {eventListeners && !isEmpty(eventListeners) && (
+
+ )}
+
+ )
+}
- @computed get legendY(): number {
- return this.props.legendY ?? 0
- }
+function Labels({
+ x,
+ y,
+ state,
+}: {
+ x: number
+ y: number
+ state: VerticalColorLegend
+}): React.ReactElement {
+ return (
+
+ {state.series.map((series) => {
+ const isFocus =
+ state.props.focusColors?.includes(series.color) ?? false
- renderLabels(): React.ReactElement {
- const { series, rectSize, rectPadding } = this
- const { focusColors } = this.props
+ const textX = x + state.rectSize + state.rectPadding
+ const textY = y + series.yOffset
- return (
-
- {series.map((series) => {
- const isFocus = focusColors?.includes(series.color) ?? false
+ return (
+
+ {series.textWrap.render(
+ textX,
+ textY,
+ isFocus
+ ? {
+ textProps: {
+ style: { fontWeight: "bold" },
+ },
+ }
+ : undefined
+ )}
+
+ )
+ })}
+
+ )
+}
- const textX = this.legendX + rectSize + rectPadding
- const textY = this.legendY + series.yOffset
+function Swatches({
+ x,
+ y,
+ state,
+}: {
+ x: number
+ y: number
+ state: VerticalColorLegend
+}): React.ReactElement {
+ return (
+
+ {state.series.map((series) => {
+ const isActive = state.props.activeColors?.includes(
+ series.color
+ )
- return (
-
- {series.textWrap.render(
- textX,
- textY,
- isFocus
- ? {
- textProps: {
- style: { fontWeight: "bold" },
- },
- }
- : undefined
- )}
-
- )
- })}
-
- )
- }
+ const textX = x + state.rectSize + state.rectPadding
+ const textY = y + series.yOffset
- renderSwatches(): React.ReactElement {
- const { series, rectSize, rectPadding } = this
- const { activeColors = [] } = this.props
+ const renderedTextPosition =
+ series.textWrap.getPositionForSvgRendering(textX, textY)
- return (
-
- {series.map((series) => {
- const isActive = activeColors.includes(series.color)
+ return (
+
+ )
+ })}
+
+ )
+}
- const textX = this.legendX + rectSize + rectPadding
- const textY = this.legendY + series.yOffset
+function InteractiveElement({
+ x,
+ y,
+ state,
+ eventListeners,
+}: {
+ x: number
+ y: number
+ state: VerticalColorLegend
+ eventListeners?: {
+ onLegendMouseOver?: (color: string) => void
+ onLegendClick?: (color: string) => void
+ onLegendMouseLeave?: () => void
+ }
+}): React.ReactElement {
+ const { onLegendMouseOver, onLegendMouseLeave, onLegendClick } =
+ eventListeners ?? {}
+ return (
+
+ {state.series.map((series) => {
+ const mouseOver = onLegendMouseOver
+ ? (): void => onLegendMouseOver(series.color)
+ : undefined
+ const mouseLeave = onLegendMouseLeave
+ const click = onLegendClick
+ ? (): void => onLegendClick(series.color)
+ : undefined
- const renderedTextPosition =
- series.textWrap.getPositionForSvgRendering(textX, textY)
+ const cursor = click ? "pointer" : "default"
- return (
+ return (
+
- )
- })}
-
- )
- }
-
- renderInteractiveElements(): React.ReactElement {
- const { series, lineHeight } = this
- const { onLegendClick, onLegendMouseOver, onLegendMouseLeave } =
- this.props
- return (
-
- {series.map((series) => {
- const mouseOver = onLegendMouseOver
- ? (): void => onLegendMouseOver(series.color)
- : undefined
- const mouseLeave = onLegendMouseLeave || undefined
- const click = onLegendClick
- ? (): void => onLegendClick(series.color)
- : undefined
-
- const cursor = click ? "pointer" : "default"
-
- return (
-
-
-
- )
- })}
-
- )
- }
-
- render(): React.ReactElement {
- return (
-
- {this.title &&
- this.title.render(this.legendX, this.legendY, {
- textProps: {
- fontWeight: 700,
- },
- })}
- {this.renderLabels()}
- {this.renderSwatches()}
- {!this.props.isStatic && this.renderInteractiveElements()}
-
- )
- }
+
+ )
+ })}
+
+ )
}