Skip to content

Commit

Permalink
🎉 (slope) add tolerance
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Nov 22, 2024
1 parent ee16621 commit 7debb1b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 29 deletions.
17 changes: 17 additions & 0 deletions packages/@ourworldindata/core-table/src/CoreTableColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,23 @@ export abstract class AbstractCoreColumn<JS_TYPE extends PrimitiveType> {
return map
}

// todo: remove? Should not be on CoreTable
@imemo get owidRowByEntityNameAndTime(): Map<
EntityName,
Map<Time, OwidVariableRow<JS_TYPE>>
> {
const valueByEntityNameAndTime = new Map<
EntityName,
Map<Time, OwidVariableRow<JS_TYPE>>
>()
this.owidRows.forEach((row) => {
if (!valueByEntityNameAndTime.has(row.entityName))
valueByEntityNameAndTime.set(row.entityName, new Map())
valueByEntityNameAndTime.get(row.entityName)!.set(row.time, row)
})
return valueByEntityNameAndTime
}

// todo: remove? Should not be on CoreTable
// NOTE: this uses the original times, so any tolerance is effectively unapplied.
@imemo get valueByEntityNameAndOriginalTime(): Map<
Expand Down
5 changes: 4 additions & 1 deletion packages/@ourworldindata/grapher/src/core/Grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,10 @@ export class Grapher
)

if (this.isOnSlopeChartTab)
return table.filterByTargetTimes([startTime, endTime])
return table.filterByTargetTimes(
[startTime, endTime],
table.get(this.yColumnSlugs[0]).tolerance
)

return table.filterByTimeRange(startTime, endTime)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ it("filters non-numeric values", () => {
expect(chart.series.length).toEqual(1)
expect(
chart.series.every(
(series) => isNumber(series.startValue) && isNumber(series.endValue)
(series) =>
isNumber(series.start.value) && isNumber(series.end.value)
)
).toBeTruthy()
})
80 changes: 61 additions & 19 deletions packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
Time,
SeriesStrategy,
EntityName,
PrimitiveType,
RenderMode,
} from "@ourworldindata/types"
import { ChartInterface } from "../chart/ChartInterface"
Expand Down Expand Up @@ -62,6 +61,7 @@ import { ColorSchemes } from "../color/ColorSchemes"
import { LineLabelSeries, LineLegend } from "../lineLegend/LineLegend"
import {
makeTooltipRoundingNotice,
makeTooltipToleranceNotice,
Tooltip,
TooltipState,
TooltipValueRange,
Expand Down Expand Up @@ -113,6 +113,10 @@ export class SlopeChart
if (this.isLogScale)
table = table.replaceNonPositiveCellsForLogScale(this.yColumnSlugs)

this.yColumnSlugs.forEach((slug) => {
table = table.interpolateColumnWithTolerance(slug)
})

// drop all data when the author chose to hide entities with missing data and
// at least one of the variables has no data for the current entity
if (
Expand Down Expand Up @@ -326,10 +330,9 @@ export class SlopeChart
canSelectMultipleEntities,
})

const valueByTime =
column.valueByEntityNameAndOriginalTime.get(entityName)
const startValue = valueByTime?.get(startTime)
const endValue = valueByTime?.get(endTime)
const owidRowByTime = column.owidRowByEntityNameAndTime.get(entityName)
const start = owidRowByTime?.get(startTime)
const end = owidRowByTime?.get(endTime)

const colorKey = getColorKey({
entityName,
Expand All @@ -347,16 +350,21 @@ export class SlopeChart
return {
seriesName,
color,
startValue,
endValue,
start,
end,
annotation,
}
}

private isSeriesValid(
series: RawSlopeChartSeries
): series is SlopeChartSeries {
return series.startValue !== undefined && series.endValue !== undefined
const { start, end } = series
return (
start?.value !== undefined &&
end?.value !== undefined &&
start.originalTime < end.originalTime
)
}

@computed get rawSeries(): RawSlopeChartSeries[] {
Expand All @@ -377,9 +385,12 @@ export class SlopeChart
return this.series.map((series) => {
const startPoint = new PointVector(
startX,
yAxis.place(series.startValue)
yAxis.place(series.start.value)
)
const endPoint = new PointVector(
endX,
yAxis.place(series.end.value)
)
const endPoint = new PointVector(endX, yAxis.place(series.endValue))
return { ...series, startPoint, endPoint }
})
}
Expand All @@ -399,8 +410,8 @@ export class SlopeChart

@computed private get allValues(): number[] {
return this.series.flatMap((series) => [
series.startValue,
series.endValue,
series.start.value,
series.end.value,
])
}

Expand Down Expand Up @@ -504,13 +515,13 @@ export class SlopeChart
// used in LineLegend
@computed get labelSeries(): LineLabelSeries[] {
return this.series.map((series) => {
const { seriesName, color, endValue, annotation } = series
const { seriesName, color, end, annotation } = series
return {
color,
seriesName,
label: seriesName,
annotation,
yValue: endValue,
yValue: end.value,
}
})
}
Expand Down Expand Up @@ -578,15 +589,26 @@ export class SlopeChart
@computed get tooltip(): React.ReactElement | undefined {
const {
tooltipState: { target, position, fading },
startTime,
endTime,
} = this

const { series } = target || {}
if (!series) return

const formatTime = (time: Time) => this.formatColumn.formatTime(time)

const isStartValueOriginal = series.start.originalTime === startTime
const isEndValueOriginal = series.end.originalTime === endTime
const actualStartTime = isStartValueOriginal
? startTime
: series.start.originalTime
const actualEndTime = isEndValueOriginal
? endTime
: series.end.originalTime

const { isRelativeMode } = this.manager,
timeRange = [this.startTime, this.endTime]
.map((t) => this.formatColumn.formatTime(t))
.join(" to "),
timeRange = `${formatTime(actualStartTime)} to ${formatTime(actualEndTime)}`,
timeLabel = timeRange + (isRelativeMode ? " (relative change)" : "")

const columns = this.yColumns
Expand All @@ -604,6 +626,25 @@ export class SlopeChart
)
)

const constructTargetYearForToleranceNotice = () => {
if (!isStartValueOriginal && !isEndValueOriginal) {
return `${formatTime(startTime)} and ${formatTime(endTime)}`
} else if (!isStartValueOriginal) {
return formatTime(startTime)
} else if (!isEndValueOriginal) {
return formatTime(endTime)
} else {
return undefined
}
}

const targetYear = constructTargetYearForToleranceNotice()
const toleranceNotice = targetYear
? {
icon: TooltipFooterIcon.notice,
text: makeTooltipToleranceNotice(targetYear),
}
: undefined
const roundingNotice = anyRoundedToSigFigs
? {
icon: allRoundedToSigFigs
Expand All @@ -614,7 +655,7 @@ export class SlopeChart
}),
}
: undefined
const footer = excludeUndefined([roundingNotice])
const footer = excludeUndefined([toleranceNotice, roundingNotice])

return (
<Tooltip
Expand All @@ -627,13 +668,14 @@ export class SlopeChart
style={{ maxWidth: "250px" }}
title={series.seriesName}
subtitle={timeLabel}
subtitleFormat={targetYear ? "notice" : undefined}
dissolve={fading}
footer={footer}
dismiss={() => (this.tooltipState.target = null)}
>
<TooltipValueRange
column={this.formatColumn}
values={[series.startValue, series.endValue]}
values={[series.start.value, series.end.value]}
/>
</Tooltip>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { PartialBy, PointVector } from "@ourworldindata/utils"
import { OwidVariableRow } from "@ourworldindata/types"
import { ChartSeries } from "../chart/ChartInterface"

export interface SlopeChartSeries extends ChartSeries {
startValue: number
endValue: number
start: Pick<OwidVariableRow<number>, "value" | "originalTime">
end: Pick<OwidVariableRow<number>, "value" | "originalTime">
annotation?: string
}

export type RawSlopeChartSeries = PartialBy<
SlopeChartSeries,
"startValue" | "endValue"
>
export type RawSlopeChartSeries = PartialBy<SlopeChartSeries, "start" | "end">

export interface PlacedSlopeChartSeries extends SlopeChartSeries {
startPoint: PointVector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,12 @@ export function IconCircledS({
)
}

export function makeTooltipToleranceNotice(targetYear: string): string {
return `Data not available for ${targetYear}. Showing closest available data point instead`
export function makeTooltipToleranceNotice(
targetYear: string,
{ plural }: { plural: boolean } = { plural: false }
): string {
const dataPoint = plural ? "data points" : "data point"
return `Data not available for ${targetYear}. Showing closest available ${dataPoint} instead`
}

export function makeTooltipRoundingNotice(
Expand Down

0 comments on commit 7debb1b

Please sign in to comment.