Skip to content

Commit

Permalink
🎉 (grapher) round to significant figures
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Jun 17, 2024
1 parent 79e3106 commit 3904976
Show file tree
Hide file tree
Showing 27 changed files with 659 additions and 100 deletions.
2 changes: 1 addition & 1 deletion adminSiteClient/DimensionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"
import { observable, computed, action } from "mobx"
import { observer } from "mobx-react"
import { ChartDimension } from "@ourworldindata/grapher"
import { OwidVariableId, OwidVariableRoundingMode } from "@ourworldindata/types"
import { OwidVariableRoundingMode } from "@ourworldindata/types"
import { startCase } from "@ourworldindata/utils"
import { ChartEditor, DimensionErrorMessage } from "./ChartEditor.js"
import {
Expand Down
38 changes: 33 additions & 5 deletions adminSiteClient/VariableEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
FieldsRow,
BindDropdown,
Toggle,
SelectField,
} from "./Forms.js"
import {
OwidVariableWithDataAndSource,
Expand All @@ -38,7 +39,11 @@ import { OriginList } from "./OriginList.js"
import { SourceList } from "./SourceList.js"
import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js"
import { Base64 } from "js-base64"
import { GrapherTabOption, GrapherInterface } from "@ourworldindata/types"
import {
GrapherTabOption,
GrapherInterface,
OwidVariableRoundingMode,
} from "@ourworldindata/types"
import { Grapher } from "@ourworldindata/grapher"
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
Expand Down Expand Up @@ -250,19 +255,42 @@ class VariableEditor extends React.Component<{ variable: VariablePageData }> {
store={newVariable.display}
placeholder={newVariable.shortUnit}
/>
<BindFloat
label="Unit conversion factor"
field="conversionFactor"
store={newVariable.display}
helpText={`Multiply all values by this amount`}
/>
</FieldsRow>
<FieldsRow>
<SelectField
label="Rounding mode"
value={variable.display?.roundingMode}
onValue={(value) => {
const roundingMode =
value as OwidVariableRoundingMode
newVariable.display.roundingMode =
roundingMode !==
OwidVariableRoundingMode.fixed
? roundingMode
: undefined
}}
options={Object.keys(
OwidVariableRoundingMode
).map((key) => ({
value: key,
label: key,
}))}
/>
<BindFloat
label="Number of decimal places"
field="numDecimalPlaces"
store={newVariable.display}
helpText={`A negative number here will round integers`}
/>
<BindFloat
label="Unit conversion factor"
field="conversionFactor"
label="Number of significant figures"
field="numSignificantFigures"
store={newVariable.display}
helpText={`Multiply all values by this amount`}
/>
</FieldsRow>
<FieldsRow>
Expand Down
11 changes: 11 additions & 0 deletions devTools/schemaProcessor/columns.json
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,22 @@
"pointer": "/dimensions/0/display/unit",
"editor": "textfield"
},
{
"type": "string",
"pointer": "/dimensions/0/display/roundingMode",
"editor": "dropdown",
"enumOptions": ["fixed", "significant"]
},
{
"type": "integer",
"pointer": "/dimensions/0/display/numDecimalPlaces",
"editor": "numeric"
},
{
"type": "integer",
"pointer": "/dimensions/0/display/numSignificantFigures",
"editor": "numeric"
},
{
"type": "string",
"pointer": "/dimensions/0/display/zeroDay",
Expand Down
23 changes: 20 additions & 3 deletions packages/@ourworldindata/core-table/src/CoreTableColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
EntityName,
OwidVariableRow,
ErrorValue,
OwidVariableRoundingMode,
} from "@ourworldindata/types"
import { ErrorValueTypes, isNotErrorValue } from "./ErrorValues.js"
import {
Expand Down Expand Up @@ -223,13 +224,26 @@ export abstract class AbstractCoreColumn<JS_TYPE extends PrimitiveType> {
return this.originalTimeColumn.formatValue(time)
}

@imemo get roundingMode(): OwidVariableRoundingMode {
return this.display?.roundingMode ?? OwidVariableRoundingMode.fixed
}

@imemo get isRoundingToFixedDecimals(): boolean {
return this.roundingMode === OwidVariableRoundingMode.fixed
}

@imemo get isRoundingToSignificantFigures(): boolean {
return this.roundingMode === OwidVariableRoundingMode.significant
}

@imemo get numDecimalPlaces(): number {
return this.display?.numDecimalPlaces ?? 2
return this.isRoundingToFixedDecimals
? this.display?.numDecimalPlaces ?? 2
: 2
}

@imemo get numSignificantFigures(): number {
// TODO: what should the default be?
return this.display?.numSignificantFigures ?? 4
return this.display?.numSignificantFigures ?? 3
}

@imemo get unit(): string | undefined {
Expand Down Expand Up @@ -617,7 +631,9 @@ abstract class AbstractColumnWithNumberFormatting<
formatValue(value: unknown, options?: TickFormattingOptions): string {
if (isNumber(value)) {
return formatValue(value, {
roundingMode: this.roundingMode,
numDecimalPlaces: this.numDecimalPlaces,
numSignificantFigures: this.numSignificantFigures,
...options,
})
}
Expand Down Expand Up @@ -736,6 +752,7 @@ class IntegerColumn extends NumericColumn {
class CurrencyColumn extends NumericColumn {
formatValue(value: unknown, options?: TickFormattingOptions): string {
return super.formatValue(value, {
roundingMode: OwidVariableRoundingMode.fixed,
numDecimalPlaces: 0,
unit: this.shortUnit,
...options,
Expand Down
2 changes: 2 additions & 0 deletions packages/@ourworldindata/grapher/src/axis/Axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Tickmark,
ValueRange,
cloneDeep,
OwidVariableRoundingMode,
} from "@ourworldindata/utils"
import { AxisConfig, AxisManager } from "./AxisConfig"
import { MarkdownTextWrap } from "@ourworldindata/components"
Expand Down Expand Up @@ -323,6 +324,7 @@ abstract class AbstractAxis {
private getTickFormattingOptions(): TickFormattingOptions {
const options: TickFormattingOptions = {
...this.config.tickFormattingOptions,
roundingMode: OwidVariableRoundingMode.fixed,
}

// The chart's tick formatting function is used by default to format axis ticks. This means
Expand Down
10 changes: 8 additions & 2 deletions packages/@ourworldindata/grapher/src/color/ColorScale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
BinningStrategy,
Color,
CoreValueType,
OwidVariableRoundingMode,
} from "@ourworldindata/types"
import { CoreColumn } from "@ourworldindata/core-table"

Expand Down Expand Up @@ -271,10 +272,15 @@ export class ColorScale {
const color = customNumericColors[index] ?? baseColor
const label = customNumericLabels[index]

const roundingOptions = {
roundingMode: OwidVariableRoundingMode.fixed,
}
const displayMin =
this.colorScaleColumn?.formatValueShort(min) ?? min.toString()
this.colorScaleColumn?.formatValueShort(min, roundingOptions) ??
min.toString()
const displayMax =
this.colorScaleColumn?.formatValueShort(max) ?? max.toString()
this.colorScaleColumn?.formatValueShort(max, roundingOptions) ??
max.toString()

const currentMin = min
const isFirst = index === 0
Expand Down
2 changes: 2 additions & 0 deletions packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Time,
EntityName,
OwidTableSlugs,
OwidVariableRoundingMode,
} from "@ourworldindata/types"
import {
BlankOwidTable,
Expand Down Expand Up @@ -724,6 +725,7 @@ export class DataTable extends React.Component<{
return value === undefined
? value
: column.formatValueShort(value, {
roundingMode: OwidVariableRoundingMode.fixed,
numberAbbreviation: false,
trailingZeroes: true,
useNoBreakSpace: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,8 @@ export class EntitySelector extends React.Component<{
if (!isFiniteWithGuard(value)) return { formattedValue: "No data" }

return {
formattedValue: displayColumn.formatValueShort(value),
formattedValue:
displayColumn.formatValueShortWithAbbreviations(value),
width: clamp(barScale(value), 0, 1),
}
}
Expand Down
25 changes: 21 additions & 4 deletions packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ import {
LineLegendManager,
} from "../lineLegend/LineLegend"
import { ComparisonLine } from "../scatterCharts/ComparisonLine"
import { Tooltip, TooltipState, TooltipTable } from "../tooltip/Tooltip"
import { TooltipFooterIcon } from "../tooltip/TooltipProps.js"
import {
Tooltip,
TooltipState,
TooltipTable,
makeTooltipRoundingNotice,
} from "../tooltip/Tooltip"
import { NoDataModal } from "../noDataModal/NoDataModal"
import { extent } from "d3-array"
import {
Expand Down Expand Up @@ -564,9 +570,21 @@ export class LineChart
? `% change since ${formatColumn.formatTime(startTime)}`
: unitLabel
const subtitleFormat = subtitle === unitLabel ? "unit" : undefined
const footer = sortedData.some((series) => series.isProjection)
? `Projected data`

const projectionNotice = sortedData.some(
(series) => series.isProjection
)
? { icon: TooltipFooterIcon.stripes, text: "Projected data" }
: undefined
const roundingNotice = formatColumn.isRoundingToSignificantFigures
? {
icon: TooltipFooterIcon.none,
text: makeTooltipRoundingNotice(
formatColumn.numSignificantFigures
),
}
: undefined
const footer = excludeUndefined([projectionNotice, roundingNotice])

return (
<Tooltip
Expand All @@ -582,7 +600,6 @@ export class LineChart
subtitle={subtitle}
subtitleFormat={subtitleFormat}
footer={footer}
footerFormat="stripes"
dissolve={fading}
>
<TooltipTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export class MapChart

@action.bound onMapMouseLeave(): void {
this.focusEntity = undefined
this.tooltipState.target = null
// this.tooltipState.target = null
}

@computed get manager(): MapChartManager {
Expand Down
55 changes: 46 additions & 9 deletions packages/@ourworldindata/grapher/src/mapCharts/MapTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import React from "react"
import { computed } from "mobx"
import { observer } from "mobx-react"
import { Tooltip, TooltipValue, TooltipState } from "../tooltip/Tooltip"
import { TooltipFooterIcon } from "../tooltip/TooltipProps.js"
import {
Tooltip,
TooltipValue,
TooltipState,
makeTooltipToleranceNotice,
makeTooltipRoundingNotice,
} from "../tooltip/Tooltip"
import { MapChartManager } from "./MapChartConstants"
import { ColorScale, ColorScaleManager } from "../color/ColorScale"
import { Time, EntityName, OwidVariableRow } from "@ourworldindata/types"
import { OwidTable } from "@ourworldindata/core-table"
import {
Time,
EntityName,
OwidVariableRow,
OwidVariableRoundingMode,
} from "@ourworldindata/types"
import { CoreColumn, OwidTable } from "@ourworldindata/core-table"
import { LineChart } from "../lineCharts/LineChart"
import {
Bounds,
Expand All @@ -18,6 +30,7 @@ import {
last,
min,
max,
excludeUndefined,
} from "@ourworldindata/utils"
import { LineChartManager } from "../lineCharts/LineChartConstants"
import { darkenColorForHighContrastText } from "../color/ColorUtils"
Expand All @@ -42,6 +55,10 @@ export class MapTooltip extends React.Component<MapTooltipProps> {
return this.props.manager.mapColumnSlug
}

@computed private get mapColumn(): CoreColumn {
return this.mapTable.get(this.mapColumnSlug)
}

@computed private get mapAndYColumnAreTheSame(): boolean {
const { yColumnSlug, yColumnSlugs, mapColumnSlug } = this.props.manager
return yColumnSlugs && mapColumnSlug !== undefined
Expand Down Expand Up @@ -75,7 +92,7 @@ export class MapTooltip extends React.Component<MapTooltipProps> {
@computed private get datum():
| OwidVariableRow<number | string>
| undefined {
return this.mapTable.get(this.mapColumnSlug).owidRows[0]
return this.mapColumn.owidRows[0]
}

@computed private get hasTimeSeriesData(): boolean {
Expand Down Expand Up @@ -198,7 +215,7 @@ export class MapTooltip extends React.Component<MapTooltipProps> {
}

render(): React.ReactElement {
const { mapTable, datum, lineColorScale } = this
const { mapTable, mapColumn, datum, lineColorScale } = this
const {
targetTime,
formatValue,
Expand Down Expand Up @@ -231,14 +248,34 @@ export class MapTooltip extends React.Component<MapTooltipProps> {
useCustom = isString(minCustom) && isString(maxCustom),
minLabel = useCustom
? minCustom
: yColumn.formatValueShort(minVal ?? 0),
: yColumn.formatValueShort(minVal ?? 0, {
roundingMode: OwidVariableRoundingMode.fixed,
}),
maxLabel = useCustom
? maxCustom
: yColumn.formatValueShort(maxVal ?? 0)
: yColumn.formatValueShort(maxVal ?? 0, {
roundingMode: OwidVariableRoundingMode.fixed,
})
const { innerBounds: axisBounds } = this.sparklineChart.dualAxis

const notice =
datum && datum.time !== targetTime ? displayTime : undefined
const toleranceNotice = notice
? {
icon: TooltipFooterIcon.notice,
text: makeTooltipToleranceNotice(notice),
}
: undefined
const roundingNotice = mapColumn.isRoundingToSignificantFigures
? {
icon: TooltipFooterIcon.dagger,
text: makeTooltipRoundingNotice(
mapColumn.numSignificantFigures,
{ multipleValues: false }
),
}
: undefined
const footer = excludeUndefined([toleranceNotice, roundingNotice])

const labelX = axisBounds.right - SPARKLINE_NUDGE
const labelTop = axisBounds.top - SPARKLINE_NUDGE
Expand All @@ -258,14 +295,14 @@ export class MapTooltip extends React.Component<MapTooltipProps> {
title={target?.featureId}
subtitle={datum ? displayDatumTime : displayTime}
subtitleFormat={notice ? "notice" : undefined}
footer={notice}
footerFormat="notice"
footer={footer}
dissolve={fading}
>
<TooltipValue
column={yColumn}
value={valueLabel}
color={valueColor}
dagger={!!roundingNotice}
/>
{this.showSparkline && (
<div
Expand Down
Loading

0 comments on commit 3904976

Please sign in to comment.