Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔨 drop manager pattern for horizontal color legend #4369

Open
wants to merge 17 commits into
base: refactor-vertical-color-legend
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { HorizontalAxisZeroLine } from "../axis/AxisViews"
import { NoDataModal } from "../noDataModal/NoDataModal"
import { AxisConfig, AxisManager } from "../axis/AxisConfig"
import { ColorSchemes } from "../color/ColorSchemes"
import { ChartInterface } from "../chart/ChartInterface"
import { ChartInterface, ExternalLegendProps } from "../chart/ChartInterface"
import {
BACKGROUND_COLOR,
DiscreteBarChartManager,
Expand Down Expand Up @@ -70,12 +70,10 @@ import {
OWID_NO_DATA_GRAY,
} from "../color/ColorConstants"
import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin"
import {
HorizontalColorLegendManager,
HorizontalNumericColorLegend,
} from "../horizontalColorLegend/HorizontalColorLegends"
import { BaseType, Selection } from "d3"
import { TextWrap } from "@ourworldindata/components"
import { HorizontalNumericColorLegend } from "../horizontalColorLegend/HorizontalNumericColorLegend"
import { HorizontalNumericColorLegendComponent } from "../horizontalColorLegend/HorizontalNumericColorLegendComponent"

const labelToTextPadding = 10
const labelToBarPadding = 5
Expand Down Expand Up @@ -170,7 +168,7 @@ export class DiscreteBarChart

@computed private get boundsWithoutColorLegend(): Bounds {
return this.bounds.padTop(
this.showColorLegend ? this.legendHeight + LEGEND_PADDING : 0
this.numericLegend ? this.legendHeight + LEGEND_PADDING : 0
)
}

Expand Down Expand Up @@ -504,8 +502,12 @@ export class DiscreteBarChart
return (
<>
{this.renderDefs()}
{this.showColorLegend && (
<HorizontalNumericColorLegend manager={this} />
{this.numericLegend && (
<HorizontalNumericColorLegendComponent
legend={this.numericLegend}
binStrokeColor={this.numericBinStroke}
textColor={this.legendTextColor}
/>
)}
{!this.isLogScale && (
<HorizontalAxisZeroLine
Expand Down Expand Up @@ -823,10 +825,10 @@ export class DiscreteBarChart
return DEFAULT_PROJECTED_DATA_COLOR_IN_LEGEND
}

@computed get externalLegend(): HorizontalColorLegendManager | undefined {
@computed get externalLegend(): ExternalLegendProps | undefined {
if (this.hasColorLegend) {
return {
numericLegendData: this.numericLegendData,
numericBins: this.numericLegendData,
}
}
return undefined
Expand All @@ -843,9 +845,22 @@ export class DiscreteBarChart
legendTextColor = "#555"
legendTickSize = 1

@computed get numericLegend(): HorizontalNumericColorLegend | undefined {
return this.hasColorScale && this.manager.showLegend
? new HorizontalNumericColorLegend({ manager: this })
@computed private get numericLegend():
| HorizontalNumericColorLegend
| undefined {
return this.showColorLegend
? new HorizontalNumericColorLegend({
fontSize: this.fontSize,
x: this.legendX,
align: this.legendAlign,
maxWidth: this.legendMaxWidth,
numericBins: this.numericLegendData,
binSize: this.numericBinSize,
equalSizeBins: this.equalSizeBins,
title: this.legendTitle,
y: this.numericLegendY,
tickSize: this.legendTickSize,
})
: undefined
}

Expand Down
10 changes: 8 additions & 2 deletions packages/@ourworldindata/grapher/src/chart/ChartInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
} from "@ourworldindata/types"
import { ColorScale } from "../color/ColorScale"
import { HorizontalAxis, VerticalAxis } from "../axis/Axis"
import { HorizontalColorLegendManager } from "../horizontalColorLegend/HorizontalColorLegends"
import { HorizontalCategoricalColorLegendProps } from "../horizontalColorLegend/HorizontalCategoricalColorLegend"
import { HorizontalNumericColorLegendProps } from "../horizontalColorLegend/HorizontalNumericColorLegend"

// 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.
Expand All @@ -19,6 +21,10 @@ export interface ChartSeries {

export type ChartTableTransformer = (inputTable: OwidTable) => OwidTable

export type ExternalLegendProps =
Partial<HorizontalCategoricalColorLegendProps> &
Partial<HorizontalNumericColorLegendProps>

export interface ChartInterface {
failMessage: string // We require every chart have some fail message(s) to show to the user if something went wrong

Expand All @@ -43,7 +49,7 @@ 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.
*/
externalLegend?: HorizontalColorLegendManager
externalLegend?: ExternalLegendProps

/**
* Which facet strategies the chart type finds reasonable in its current setting, if any.
Expand Down
166 changes: 83 additions & 83 deletions packages/@ourworldindata/grapher/src/facetChart/FacetChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
DefaultChartClass,
} from "../chart/ChartTypeMap"
import { ChartManager } from "../chart/ChartManager"
import { ChartInterface } from "../chart/ChartInterface"
import { ChartInterface, ExternalLegendProps } from "../chart/ChartInterface"
import {
getChartPadding,
getFontSize,
Expand All @@ -53,18 +53,22 @@ import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils"
import { SelectionArray } from "../selection/SelectionArray"
import { AxisConfig } from "../axis/AxisConfig"
import { HorizontalAxis, VerticalAxis } from "../axis/Axis"
import {
HorizontalCategoricalColorLegend,
HorizontalColorLegend,
HorizontalColorLegendManager,
HorizontalNumericColorLegend,
} from "../horizontalColorLegend/HorizontalColorLegends"
import {
CategoricalBin,
ColorScaleBin,
NumericBin,
} from "../color/ColorScaleBin"
import { GRAPHER_DARK_TEXT } from "../color/ColorConstants"
import {
HorizontalCategoricalColorLegend,
HorizontalCategoricalColorLegendProps,
} from "../horizontalColorLegend/HorizontalCategoricalColorLegend"
import {
HorizontalNumericColorLegend,
HorizontalNumericColorLegendProps,
} from "../horizontalColorLegend/HorizontalNumericColorLegend"
import { HorizontalNumericColorLegendComponent } from "../horizontalColorLegend/HorizontalNumericColorLegendComponent"
import { HorizontalCategoricalColorLegendComponent } from "../horizontalColorLegend/HorizontalCategoricalColorLegendComponent"

const SHARED_X_AXIS_MIN_FACET_COUNT = 12

Expand Down Expand Up @@ -118,7 +122,7 @@ interface AxesInfo {
@observer
export class FacetChart
extends React.Component<FacetChartProps>
implements ChartInterface, HorizontalColorLegendManager
implements ChartInterface
{
transformTable(table: OwidTable): OwidTable {
return table
Expand Down Expand Up @@ -589,7 +593,8 @@ export class FacetChart

// legend utils

@computed private get externalLegends(): HorizontalColorLegendManager[] {
@computed
private get externalLegends(): ExternalLegendProps[] {
return excludeUndefined(
this.intermediateChartInstances.map(
(instance) => instance.externalLegend
Expand All @@ -599,18 +604,10 @@ export class FacetChart

@computed private get isNumericLegend(): boolean {
return this.externalLegends.some((legend) =>
legend.numericLegendData?.some((bin) => bin instanceof NumericBin)
legend.numericBins?.some((bin) => bin instanceof NumericBin)
)
}

@computed private get LegendClass():
| typeof HorizontalNumericColorLegend
| typeof HorizontalCategoricalColorLegend {
return this.isNumericLegend
? HorizontalNumericColorLegend
: HorizontalCategoricalColorLegend
}

@computed private get showLegend(): boolean {
const { isNumericLegend, categoricalLegendData, numericLegendData } =
this
Expand Down Expand Up @@ -641,9 +638,9 @@ export class FacetChart
return false
}

private getExternalLegendProp<
Prop extends keyof HorizontalColorLegendManager,
>(prop: Prop): HorizontalColorLegendManager[Prop] | undefined {
private getExternalLegendProp<Prop extends keyof ExternalLegendProps>(
prop: Prop
): ExternalLegendProps[Prop] | undefined {
for (const externalLegend of this.externalLegends) {
if (externalLegend[prop] !== undefined) {
return externalLegend[prop]
Expand All @@ -667,64 +664,35 @@ 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(): ExternalLegendProps {
return {
fontSize: this.fontSize,
x: this.bounds.x,
maxWidth: this.bounds.width,
align: HorizontalAlign.left,
}
}

@computed get numericBinStrokeWidth(): number | undefined {
return this.getExternalLegendProp("numericBinStrokeWidth")
@computed
private get numericLegendProps(): HorizontalNumericColorLegendProps {
return {
...this.commonLegendProps,
y: this.bounds.top,
title: this.getExternalLegendProp("title"),
tickSize: this.getExternalLegendProp("tickSize"),
binSize: this.getExternalLegendProp("binSize"),
equalSizeBins: this.getExternalLegendProp("equalSizeBins"),
numericBins: this.numericLegendData,
}
}

@computed get equalSizeBins(): boolean | undefined {
return this.getExternalLegendProp("equalSizeBins")
@computed
private get categoricalLegendProps(): HorizontalCategoricalColorLegendProps {
return {
...this.commonLegendProps,
categoricalBins: this.categoricalLegendData,
}
}

@computed get hoverColors(): Color[] | undefined {
Expand Down Expand Up @@ -752,8 +720,8 @@ export class FacetChart
if (!this.isNumericLegend || !this.hideFacetLegends) return []
const allBins: ColorScaleBin[] = this.externalLegends.flatMap(
(legend) => [
...(legend.numericLegendData ?? []),
...(legend.categoricalLegendData ?? []),
...(legend.numericBins ?? []),
...(legend.categoricalBins ?? []),
]
)
const uniqBins = this.getUniqBins(allBins)
Expand All @@ -768,8 +736,8 @@ export class FacetChart
if (this.isNumericLegend || !this.hideFacetLegends) return []
const allBins: CategoricalBin[] = this.externalLegends
.flatMap((legend) => [
...(legend.numericLegendData ?? []),
...(legend.categoricalLegendData ?? []),
...(legend.numericBins ?? []),
...(legend.categoricalBins ?? []),
])
.filter((bin) => bin instanceof CategoricalBin) as CategoricalBin[]
const uniqBins = this.getUniqBins(allBins)
Expand Down Expand Up @@ -814,8 +782,18 @@ export class FacetChart

// end of legend props

@computed private get legend(): HorizontalColorLegend {
return new this.LegendClass({ manager: this })
@computed private get categoryLegend(): HorizontalCategoricalColorLegend {
return new HorizontalCategoricalColorLegend(this.categoricalLegendProps)
}

@computed private get numericLegend(): HorizontalNumericColorLegend {
return new HorizontalNumericColorLegend(this.numericLegendProps)
}

@computed private get legend():
| HorizontalNumericColorLegend
| HorizontalCategoricalColorLegend {
return this.isNumericLegend ? this.numericLegend : this.categoryLegend
}

@computed private get isFocusModeSupported(): boolean {
Expand Down Expand Up @@ -866,11 +844,33 @@ export class FacetChart
return { fontSize, shortenedLabel: label }
}

private renderLegend(): React.ReactElement {
return this.isNumericLegend ? (
<HorizontalNumericColorLegendComponent
legend={this.numericLegend}
x={this.bounds.x}
onMouseOver={this.onLegendMouseOver}
onMouseLeave={this.onLegendMouseLeave}
/>
) : (
<HorizontalCategoricalColorLegendComponent
legend={this.categoryLegend}
x={this.bounds.x}
y={this.bounds.top}
onMouseOver={this.onLegendMouseOver}
onMouseLeave={this.onLegendMouseLeave}
hoverColors={this.hoverColors}
activeColors={this.activeColors}
onClick={this.onLegendClick}
/>
)
}

render(): React.ReactElement {
const { facetFontSize, LegendClass, showLegend } = this
const { facetFontSize, showLegend } = this
return (
<React.Fragment>
{showLegend && <LegendClass manager={this} />}
{showLegend && this.renderLegend()}
{this.placedSeries.map((facetChart, index: number) => {
const ChartClass =
ChartComponentClassMap.get(this.chartTypeName) ??
Expand Down
Loading
Loading