From 1a89de28c8bee680c228338227aff8f5ddab3090 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Fri, 1 Mar 2024 14:42:08 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20(slope)=20replace=20custom=20y-a?= =?UTF-8?q?xis=20with=20shared=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grapher/src/axis/AxisViews.tsx | 53 ++++- .../grapher/src/slopeCharts/SlopeChart.tsx | 196 ++++++------------ .../src/stackedCharts/StackedBarChart.tsx | 4 +- 3 files changed, 110 insertions(+), 143 deletions(-) diff --git a/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx b/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx index 12dc378a726..79ce78ef866 100644 --- a/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx +++ b/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx @@ -227,15 +227,32 @@ export class DualAxisComponent extends React.Component { export class VerticalAxisComponent extends React.Component<{ bounds: Bounds verticalAxis: VerticalAxis + showTickMarks?: boolean labelColor?: string tickColor?: string detailsMarker?: DetailsMarker }> { render(): JSX.Element { - const { bounds, verticalAxis, labelColor, tickColor, detailsMarker } = - this.props + const { + bounds, + verticalAxis, + labelColor, + tickColor, + detailsMarker, + showTickMarks, + } = this.props const { tickLabels, labelTextWrap } = verticalAxis + const tickMarks = showTickMarks ? ( + + verticalAxis.place(label.value) + )} + tickMarkLeftPosition={bounds.left + verticalAxis.width} + color={SOLID_TICK_COLOR} + /> + ) : undefined + return ( {labelTextWrap && @@ -250,6 +267,7 @@ export class VerticalAxisComponent extends React.Component<{ detailsMarker, } )} + {tickMarks} {tickLabels.map((label, i) => { const { y, xAlign, yAlign, formattedValue } = label return ( @@ -325,7 +343,7 @@ export class HorizontalAxisComponent extends React.Component<{ : preferredAxisPosition ?? bounds.bottom const tickMarks = showTickMarks ? ( - axis.place(label.value) @@ -374,7 +392,7 @@ export class HorizontalAxisComponent extends React.Component<{ } } -export class AxisTickMarks extends React.Component<{ +export class HorizontalAxisTickMarks extends React.Component<{ tickMarkTopPosition: number tickMarkXPositions: number[] color: string @@ -400,3 +418,30 @@ export class AxisTickMarks extends React.Component<{ }) } } + +export class VerticalAxisTickMarks extends React.Component<{ + tickMarkLeftPosition: number + tickMarkYPositions: number[] + color: string + width?: number +}> { + render(): JSX.Element[] { + const { tickMarkYPositions, tickMarkLeftPosition, color, width } = + this.props + const tickSize = 5 + const tickRight = tickMarkLeftPosition + tickSize + return tickMarkYPositions.map((tickMarkPosition, index) => { + return ( + + ) + }) + } +} diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx index fc85110fd11..bb7ea1a38a1 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx @@ -31,7 +31,6 @@ import { GRAPHER_DARK_TEXT, GRAPHER_FONT_SCALE_9_6, GRAPHER_FONT_SCALE_10_5, - GRAPHER_FONT_SCALE_12, GRAPHER_FONT_SCALE_14, } from "../core/GrapherConstants" import { @@ -43,21 +42,22 @@ import { } from "@ourworldindata/types" import { ChartInterface } from "../chart/ChartInterface" import { ChartManager } from "../chart/ChartManager" -import { scaleLinear, scaleLog, ScaleLinear, ScaleLogarithmic } from "d3-scale" +import { scaleLinear, ScaleLinear } from "d3-scale" import { extent } from "d3-array" import { select } from "d3-selection" import { Text } from "../text/Text" import { DEFAULT_SLOPE_CHART_COLOR, LabelledSlopesProps, - SlopeAxisProps, SlopeChartSeries, SlopeChartValue, SlopeProps, } from "./SlopeChartConstants" -import { CoreColumn, OwidTable } from "@ourworldindata/core-table" +import { OwidTable } from "@ourworldindata/core-table" import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils" import { AxisConfig, AxisManager } from "../axis/AxisConfig" +import { VerticalAxis } from "../axis/Axis" +import { VerticalAxisComponent } from "../axis/AxisViews" export interface SlopeChartManager extends ChartManager { isModalOpen?: boolean @@ -486,82 +486,18 @@ export class SlopeChart } } -@observer -class SlopeChartAxis extends React.Component { - static calculateBounds( - containerBounds: Bounds, - props: { - column: CoreColumn - orient: "left" | "right" - scale: ScaleLinear - } - ) { - const { scale, column } = props - const longestTick = maxBy( - scale.ticks(6).map((tick) => column.formatValueShort(tick)), - (tick) => tick.length - ) - const axisWidth = Bounds.forText(longestTick).width - return new Bounds( - containerBounds.x, - containerBounds.y, - axisWidth, - containerBounds.height - ) - } - - static getTicks( - scale: ScaleLinear | ScaleLogarithmic, - scaleType: ScaleType - ) { - if (scaleType === ScaleType.log) { - let minPower10 = Math.ceil( - Math.log(scale.domain()[0]) / Math.log(10) - ) - if (!isFinite(minPower10)) minPower10 = 0 - let maxPower10 = Math.floor( - Math.log(scale.domain()[1]) / Math.log(10) - ) - if (maxPower10 <= minPower10) maxPower10 += 1 - - const tickValues = [] - for (let i = minPower10; i <= maxPower10; i++) { - tickValues.push(Math.pow(10, i)) - } - return tickValues - } else { - return scale.ticks(6) - } - } - - @computed get ticks() { - return SlopeChartAxis.getTicks(this.props.scale, this.props.scaleType) - } - - render() { - const { bounds, scale, orient, column, fontSize } = this.props - const { ticks } = this - - return ( - - {ticks.map((tick, i) => { - return ( - - {column.formatValueShort(tick)} - - ) - })} - - ) - } +function calculateBounds(containerBounds: Bounds, yAxis: VerticalAxis) { + const longestTick = maxBy( + yAxis.tickLabels.map((tickLabel) => tickLabel.formattedValue), + (tick) => tick.length + ) + const axisWidth = Bounds.forText(longestTick).width + return new Bounds( + containerBounds.x, + containerBounds.y, + axisWidth, + containerBounds.height + ) } @observer @@ -762,6 +698,15 @@ class LabelledSlopes return new AxisConfig(this.manager.yAxisConfig, this) } + @computed get yAxis(): VerticalAxis { + const axis = this.yAxisConfig.toVerticalAxis() + axis.domain = this.yDomain + axis.range = this.yRange + axis.formatColumn = this.yColumn + axis.label = "" + return axis + } + @computed private get yScaleType() { return this.yAxisConfig.scaleType || ScaleType.linear } @@ -798,31 +743,13 @@ class LabelledSlopes .range([factor, 4 * factor]) } - @computed get yScaleConstructor(): any { - return this.yScaleType === ScaleType.log ? scaleLog : scaleLinear - } - - @computed private get yScale(): - | ScaleLinear - | ScaleLogarithmic { - return ( - this.yScaleConstructor() - .domain(this.yDomain) - // top padding leaves room for point labels - // bottom padding leaves room for y-axis labels - .range(this.props.bounds.padTop(6).padBottom(24).yRange()) - ) + @computed get yRange(): [number, number] { + return this.props.bounds.padTop(6).padBottom(24).yRange() } @computed private get xScale(): ScaleLinear { - const { bounds, isPortrait, xDomain, yScale, yColumn } = this - const padding = isPortrait - ? 0 - : SlopeChartAxis.calculateBounds(bounds, { - orient: "left", - scale: yScale, - column: yColumn, - }).width + const { bounds, isPortrait, xDomain, yAxis } = this + const padding = isPortrait ? 0 : calculateBounds(bounds, yAxis).width return scaleLinear() .domain(xDomain) .range(bounds.padWidth(padding).xRange()) @@ -837,14 +764,14 @@ class LabelledSlopes data, isPortrait, xScale, - yScale, + yAxis, sizeScale, yColumn, + yDomain, maxLabelWidth: maxWidth, } = this const slopeData: SlopeProps[] = [] - const yDomain = yScale.domain() data.forEach((series) => { // Ensure values fit inside the chart @@ -858,7 +785,7 @@ class LabelledSlopes const text = series.seriesName const [v1, v2] = series.values const [x1, x2] = [xScale(v1.x), xScale(v2.x)] - const [y1, y2] = [yScale(v1.y), yScale(v2.y)] + const [y1, y2] = [yAxis.place(v1.y), yAxis.place(v2.y)] const fontSize = (isPortrait ? GRAPHER_FONT_SCALE_9_6 @@ -1148,23 +1075,20 @@ class LabelledSlopes render() { const { fontSize, - yScaleType, bounds, slopeData, isPortrait, xDomain, - yScale, + yAxis, + yRange, onMouseMove, - yColumn, } = this if (isEmpty(slopeData)) return const { x1, x2 } = slopeData[0] - const [y1, y2] = yScale.range() - - const tickFontSize = GRAPHER_FONT_SCALE_12 * fontSize + const [y1, y2] = yRange return ( - {SlopeChartAxis.getTicks(yScale, yScaleType).map( - (tick, i) => { - return ( + {this.yAxis.tickLabels.map((tick) => { + const y = yAxis.place(tick.value) + return ( + + {/* grid lines connecting the chart area to the axis */} + + {/* grid lines within the chart area */} - ) - } - )} + + ) + })} {!isPortrait && ( - - )} - {!isPortrait && ( - )} diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx index c59f5ac01e9..6c5edc70e0c 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedBarChart.tsx @@ -12,7 +12,7 @@ import { } from "@ourworldindata/utils" import { VerticalAxisComponent, - AxisTickMarks, + HorizontalAxisTickMarks, VerticalAxisGridLines, } from "../axis/AxisViews" import { NoDataModal } from "../noDataModal/NoDataModal" @@ -458,7 +458,7 @@ export class StackedBarChart strokeWidth={axisLineWidth} /> - tick.bounds.centerX