Skip to content

Commit

Permalink
✨ (admin) allow axis min/max to be overwritten
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Sep 19, 2024
1 parent 3dc6b58 commit 5ed52c6
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 132 deletions.
5 changes: 5 additions & 0 deletions adminShared/patchHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
checkIsStringIndexable,
} from "@ourworldindata/utils"

export function getValueRecursive(json: unknown, pointer: string[]): any {
const p = "/" + pointer.join("/")
return jsonpointer.find(json, p)
}

export function setValueRecursiveInplace(
json: unknown,
pointer: string[],
Expand Down
14 changes: 7 additions & 7 deletions adminSiteClient/AbstractChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { action, computed, observable, when } from "mobx"
import { EditorFeatures } from "./EditorFeatures.js"
import { Admin } from "./Admin.js"
import { defaultGrapherConfig, Grapher } from "@ourworldindata/grapher"
import { getValueRecursive } from "../adminShared/patchHelper.js"

export type EditorTab =
| "basic"
Expand Down Expand Up @@ -125,20 +126,19 @@ export abstract class AbstractChartEditor<
this.grapher.updateAuthoredVersion(config)
}

// only works for top-level properties
isPropertyInherited(property: keyof GrapherInterface): boolean {
isPropertyInherited(pointer: string[]): boolean {
if (!this.isInheritanceEnabled || !this.activeParentConfigWithDefaults)
return false
return (
!Object.hasOwn(this.patchConfig, property) &&
Object.hasOwn(this.activeParentConfigWithDefaults, property)
getValueRecursive(this.patchConfig, pointer) === undefined &&
getValueRecursive(this.activeParentConfigWithDefaults, pointer) !==
undefined
)
}

// only works for top-level properties
couldPropertyBeInherited(property: keyof GrapherInterface): boolean {
couldPropertyBeInherited(pointer: string[]): boolean {
if (!this.isInheritanceEnabled || !this.activeParentConfig) return false
return Object.hasOwn(this.activeParentConfig, property)
return getValueRecursive(this.activeParentConfig, pointer) !== undefined
}

abstract get isNewGrapher(): boolean
Expand Down
211 changes: 114 additions & 97 deletions adminSiteClient/EditorCustomizeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ColorSchemeName,
FacetAxisDomain,
FacetStrategy,
GrapherInterface,
TimeBoundValueStr,

Check warning on line 9 in adminSiteClient/EditorCustomizeTab.tsx

View workflow job for this annotation

GitHub Actions / eslint

'TimeBoundValueStr' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 9 in adminSiteClient/EditorCustomizeTab.tsx

View workflow job for this annotation

GitHub Actions / eslint

'TimeBoundValueStr' is defined but never used. Allowed unused vars must match /^_/u
} from "@ourworldindata/types"
import { Grapher } from "@ourworldindata/grapher"
import {
Expand All @@ -29,6 +29,8 @@ import {
SortOrder,
SortBy,
SortConfig,
minTimeBoundFromJSONOrNegativeInfinity,
maxTimeBoundFromJSONOrPositiveInfinity,
} from "@ourworldindata/utils"
import { faPlus, faMinus } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
Expand All @@ -40,25 +42,46 @@ import { EditorColorScaleSection } from "./EditorColorScaleSection.js"
import Select from "react-select"
import { AbstractChartEditor } from "./AbstractChartEditor.js"
import { ErrorMessages } from "./ChartEditorTypes.js"
import {
getValueRecursive,
setValueRecursiveInplace,
} from "../adminShared/patchHelper.js"

@observer
class TimeField<
T extends { [field: string]: any },
K extends Extract<keyof T, string>,
> extends React.Component<{
class BindAutoFloatToParent extends React.Component<{
editor: AbstractChartEditor
field: K
store: T
pointer: string[]
label: string
defaultValue: number
defaultTextValue: TimeBoundValue
defaultValue: number | undefined
allowDecimal?: boolean
allowNegative?: boolean
parseConfigValue?: (
value: string | number | undefined
) => number | undefined
}> {
private setValue(value: number) {
this.props.store[this.props.field] = value as any
private setValue(value: number | undefined) {
setValueRecursiveInplace(
this.props.editor.grapher,
this.props.pointer,
value
)
}

@computed get parseConfigValue() {
return this.props.parseConfigValue ?? ((value) => value as number)
}

@computed get currentValue(): number | undefined {
return this.props.store[this.props.field]
return getValueRecursive(this.props.editor.grapher, this.props.pointer)
}

@computed get inheritedValue(): number | undefined {
return this.parseConfigValue(
getValueRecursive(
this.props.editor.activeParentConfig,
this.props.pointer
)
)
}

@action.bound onChange(value: number | undefined) {
Expand All @@ -72,26 +95,18 @@ class TimeField<
}

render() {
const { editor, label, defaultTextValue, defaultValue } = this.props

const field = this.props.field as keyof GrapherInterface
const { editor, label, defaultValue } = this.props

const inheritedValue = editor.activeParentConfig?.[field] as number
const autoValue =
inheritedValue === defaultTextValue
? defaultValue
: inheritedValue ?? defaultValue

const input = editor.couldPropertyBeInherited(field) ? (
const input = editor.couldPropertyBeInherited(this.props.pointer) ? (
<BindAutoFloatExt
label={label}
readFn={(store) => store[field]}
readFn={(store) => getValueRecursive(store, this.props.pointer)}
writeFn={(store, newVal) =>
(store[this.props.field] = newVal as any)
setValueRecursiveInplace(store, this.props.pointer, newVal)
}
auto={autoValue}
isAuto={editor.isPropertyInherited(field)}
store={this.props.store}
auto={this.inheritedValue}
isAuto={editor.isPropertyInherited(this.props.pointer)}
store={this.props.editor.grapher}
onBlur={this.onBlur}
tooltipText={{
auto: "Linked to parent value. Unlink by editing",
Expand All @@ -101,9 +116,10 @@ class TimeField<
) : (
<NumberField
label={label}
value={this.props.store[field]}
value={this.currentValue}
onValue={debounce(this.onChange)}
allowNegative
allowNegative={this.props.allowNegative}
allowDecimal={this.props.allowDecimal}
/>
)

Expand Down Expand Up @@ -411,45 +427,53 @@ class TimelineSection<
<Section name="Timeline selection">
<FieldsRow>
{features.timeDomain && (
<TimeField
<BindAutoFloatToParent
editor={editor}
store={grapher}
field="minTime"
pointer={["minTime"]}
label="Selection start"
defaultTextValue={TimeBoundValue.negativeInfinity}
defaultValue={-Infinity}
defaultValue={TimeBoundValue.negativeInfinity}
allowNegative
parseConfigValue={
minTimeBoundFromJSONOrNegativeInfinity
}
/>
)}
<TimeField
<BindAutoFloatToParent
editor={editor}
store={grapher}
field="maxTime"
pointer={["maxTime"]}
label={
features.timeDomain
? "Selection end"
: "Selected year"
}
defaultValue={Infinity}
defaultTextValue={TimeBoundValue.positiveInfinity}
defaultValue={TimeBoundValue.positiveInfinity}
allowNegative
parseConfigValue={
maxTimeBoundFromJSONOrPositiveInfinity
}
/>
</FieldsRow>
{features.timelineRange && (
<FieldsRow>
<TimeField
<BindAutoFloatToParent
editor={editor}
store={this.props.editor.grapher}
field="timelineMinTime"
pointer={["timelineMinTime"]}
label="Timeline min"
defaultValue={-Infinity}
defaultTextValue={TimeBoundValue.negativeInfinity}
defaultValue={TimeBoundValue.negativeInfinity}
allowNegative
parseConfigValue={
minTimeBoundFromJSONOrNegativeInfinity
}
/>
<TimeField
<BindAutoFloatToParent
editor={editor}
store={grapher}
field="timelineMaxTime"
pointer={["timelineMaxTime"]}
label="Timeline max"
defaultValue={Infinity}
defaultTextValue={TimeBoundValue.positiveInfinity}
defaultValue={TimeBoundValue.positiveInfinity}
allowNegative
parseConfigValue={
maxTimeBoundFromJSONOrPositiveInfinity
}
/>
</FieldsRow>
)}
Expand Down Expand Up @@ -549,6 +573,7 @@ export class EditorCustomizeTab<
const xAxisConfig = this.props.editor.grapher.xAxis
const yAxisConfig = this.props.editor.grapher.yAxis

const { editor } = this.props

Check warning on line 576 in adminSiteClient/EditorCustomizeTab.tsx

View workflow job for this annotation

GitHub Actions / eslint

'editor' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 576 in adminSiteClient/EditorCustomizeTab.tsx

View workflow job for this annotation

GitHub Actions / eslint

'editor' is assigned a value but never used. Allowed unused vars must match /^_/u
const { features, activeParentConfig } = this.props.editor
const { grapher } = this.props.editor

Expand All @@ -559,35 +584,31 @@ export class EditorCustomizeTab<
{features.canCustomizeYAxisScale && (
<React.Fragment>
<FieldsRow>
<NumberField
label={`Min`}
value={yAxisConfig.min}
onValue={(value) =>
(yAxisConfig.min = value)
}
onBlur={() => {
if (yAxisConfig.min === undefined) {
yAxisConfig.min =
activeParentConfig?.yAxis?.min
}
}}
<BindAutoFloatToParent
editor={this.props.editor}
pointer={["yAxis", "min"]}
label="Min"
defaultValue={undefined}
allowDecimal
allowNegative
/>
<NumberField
label={`Max`}
value={yAxisConfig.max}
onValue={(value) =>
(yAxisConfig.max = value)
parseConfigValue={(value) =>
value === "auto"
? undefined
: (value as number)
}
onBlur={() => {
if (yAxisConfig.max === undefined) {
yAxisConfig.max =
activeParentConfig?.yAxis?.max
}
}}
/>
<BindAutoFloatToParent
editor={this.props.editor}
pointer={["yAxis", "max"]}
label="Max"
defaultValue={undefined}
allowDecimal
allowNegative
parseConfigValue={(value) =>
value === "auto"
? undefined
: (value as number)
}
/>
</FieldsRow>
{features.canRemovePointsOutsideAxisDomain && (
Expand Down Expand Up @@ -644,35 +665,31 @@ export class EditorCustomizeTab<
{features.canCustomizeXAxisScale && (
<React.Fragment>
<FieldsRow>
<NumberField
label={`Min`}
value={xAxisConfig.min}
onValue={(value) =>
(xAxisConfig.min = value)
}
onBlur={() => {
if (xAxisConfig.min === undefined) {
xAxisConfig.min =
activeParentConfig?.yAxis?.min
}
}}
<BindAutoFloatToParent
editor={this.props.editor}
pointer={["xAxis", "min"]}
label="Min"
defaultValue={undefined}
allowDecimal
allowNegative
/>
<NumberField
label={`Max`}
value={xAxisConfig.max}
onValue={(value) =>
(xAxisConfig.max = value)
parseConfigValue={(value) =>
value === "auto"
? undefined
: (value as number)
}
onBlur={() => {
if (xAxisConfig.max === undefined) {
xAxisConfig.max =
activeParentConfig?.yAxis?.max
}
}}
/>
<BindAutoFloatToParent
editor={this.props.editor}
pointer={["xAxis", "max"]}
label="Max"
defaultValue={undefined}
allowDecimal
allowNegative
parseConfigValue={(value) =>
value === "auto"
? undefined
: (value as number)
}
/>
</FieldsRow>
{features.canRemovePointsOutsideAxisDomain && (
Expand Down
10 changes: 6 additions & 4 deletions adminSiteClient/EditorDataTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ export class KeysSection extends React.Component<{
const { selection } = grapher
const { unselectedEntityNames, selectedEntityNames } = selection

const isEntitySelectionInherited = editor.isPropertyInherited(
"selectedEntityNames"
)
const isEntitySelectionInherited = editor.isPropertyInherited([
"selectedEntityNames",
])

return (
<Section name="Data to show">
Expand All @@ -144,7 +144,9 @@ export class KeysSection extends React.Component<{
.concat(unselectedEntityNames)
.map((key) => ({ value: key }))}
/>
{editor.couldPropertyBeInherited("selectedEntityNames") && (
{editor.couldPropertyBeInherited([
"selectedEntityNames",
]) && (
<button
className="btn btn-outline-secondary"
type="button"
Expand Down
Loading

0 comments on commit 5ed52c6

Please sign in to comment.