Skip to content

Commit

Permalink
✨ (admin) improve support for focusing series
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Dec 13, 2024
1 parent ed93c75 commit 9a69cd3
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 93 deletions.
20 changes: 20 additions & 0 deletions adminSiteClient/AbstractChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
diffGrapherConfigs,
mergeGrapherConfigs,
PostReference,
SeriesName,
difference,
} from "@ourworldindata/utils"
import { action, computed, observable, when } from "mobx"
import { EditorFeatures } from "./EditorFeatures.js"
Expand Down Expand Up @@ -163,6 +165,24 @@ export abstract class AbstractChartEditor<
return Object.hasOwn(this.activeParentConfig, property)
}

@computed get invalidFocusedSeriesNames(): SeriesName[] {
const { grapher } = this

// if focusing is not supported, then all focused series are invalid
if (!this.features.canHighlightSeries) {
return grapher.focusArray.seriesNames
}

// find invalid focused series
const availableSeriesNames = grapher.chartSeriesNames
const focusedSeriesNames = grapher.focusArray.seriesNames
return difference(focusedSeriesNames, availableSeriesNames)
}

@action.bound removeInvalidFocusedSeriesNames(): void {
this.grapher.focusArray.remove(...this.invalidFocusedSeriesNames)
}

abstract get isNewGrapher(): boolean
abstract get availableTabs(): EditorTab[]

Expand Down
11 changes: 3 additions & 8 deletions adminSiteClient/ChartEditorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ export type FieldWithDetailReferences =
| "axisLabelX"
| "axisLabelY"

export interface DimensionErrorMessage {
displayName?: string
}
type ErrorMessageFieldName = FieldWithDetailReferences | "focusedSeriesNames"

export type ErrorMessages = Partial<Record<FieldWithDetailReferences, string>>
export type ErrorMessages = Partial<Record<ErrorMessageFieldName, string>>

export type ErrorMessagesForDimensions = Record<
DimensionProperty,
DimensionErrorMessage[]
>
export type ErrorMessagesForDimensions = Record<DimensionProperty, string[]>
13 changes: 10 additions & 3 deletions adminSiteClient/ChartEditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ export class ChartEditorView<
}
)

// add an error message if any focused series names are invalid
const { invalidFocusedSeriesNames = [] } = this.editor ?? {}
if (invalidFocusedSeriesNames.length > 0) {
const invalidNames = invalidFocusedSeriesNames.join(", ")
const message = `Invalid focus state. The following entities/indicators are not plotted: ${invalidNames}`
errorMessages.focusedSeriesNames = message
}

return errorMessages
}

Expand All @@ -287,9 +295,8 @@ export class ChartEditorView<

// add error message if details are referenced in the display name
if (hasDetailsInDisplayName) {
errorMessages[slot.property][dimensionIndex] = {
displayName: "Detail syntax is not supported",
}
errorMessages[slot.property][dimensionIndex] =
`Detail syntax is not supported for display names of indicators: ${dimension.display.name}`
}
})
})
Expand Down
5 changes: 2 additions & 3 deletions adminSiteClient/DimensionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { observer } from "mobx-react"
import { ChartDimension } from "@ourworldindata/grapher"
import { OwidColumnDef, OwidVariableRoundingMode } from "@ourworldindata/types"
import { startCase } from "@ourworldindata/utils"
import { DimensionErrorMessage } from "./ChartEditorTypes.js"
import {
Toggle,
BindAutoString,
Expand Down Expand Up @@ -35,7 +34,7 @@ export class DimensionCard<
onChange: (dimension: ChartDimension) => void
onEdit?: () => void
onRemove?: () => void
errorMessage?: DimensionErrorMessage
errorMessage?: string
}> {
@observable.ref isExpanded: boolean = false

Expand Down Expand Up @@ -171,7 +170,7 @@ export class DimensionCard<
store={dimension.display}
auto={column.displayName}
onBlur={this.onChange}
errorMessage={this.props.errorMessage?.displayName}
errorMessage={this.props.errorMessage}
/>
<BindAutoString
label="Unit of measurement"
Expand Down
34 changes: 6 additions & 28 deletions adminSiteClient/EditorBasicTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ import {
ALL_GRAPHER_CHART_TYPES,
GrapherChartType,
GRAPHER_CHART_TYPES,
SeriesName,
} from "@ourworldindata/types"
import {
DimensionSlot,
WorldEntityName,
CONTINENTS_INDICATOR_ID,
POPULATION_INDICATOR_ID_USED_IN_ADMIN,
Grapher,
} from "@ourworldindata/grapher"
import {
DimensionProperty,
Expand All @@ -31,7 +29,6 @@ import {
sampleSize,
startCase,
OwidChartDimensionInterface,
differenceOfSets,
} from "@ourworldindata/utils"
import { FieldsRow, Section, SelectField, Toggle } from "./Forms.js"
import { VariableSelector } from "./VariableSelector.js"
Expand Down Expand Up @@ -64,6 +61,10 @@ class DimensionSlotView<

@observable.ref isSelectingVariables: boolean = false

private get editor() {
return this.props.editor
}

private get grapher() {
return this.props.editor.grapher
}
Expand Down Expand Up @@ -145,20 +146,6 @@ class DimensionSlotView<
}
}

@action.bound private validateFocusedSeriesNames(): void {
const { grapher } = this.props.editor

// focus state is only supported for line and slope charts
if (!grapher.hasLineChart && !grapher.hasSlopeChart) {
grapher.focusArray.clear()
return
}

// sync focused series names by removing invalid ones
const invalidFocusedSeriesNames = findInvalidFocusedSeriesNames(grapher)
grapher.focusArray.remove(...invalidFocusedSeriesNames)
}

componentDidMount() {
// We want to add the reaction only after the grapher is loaded,
// so we don't update the initial chart (as configured) by accident.
Expand All @@ -170,14 +157,14 @@ class DimensionSlotView<
() => this.grapher.validChartTypes,
() => {
this.updateDefaultSelection()
this.validateFocusedSeriesNames()
this.editor.removeInvalidFocusedSeriesNames()
}
),
reaction(
() => this.grapher.yColumnsFromDimensions.length,
() => {
this.updateDefaultSelection()
this.validateFocusedSeriesNames()
this.editor.removeInvalidFocusedSeriesNames()
}
)
)
Expand Down Expand Up @@ -531,12 +518,3 @@ function IndicatorChartInfo(props: { editor: IndicatorChartEditor }) {
</Section>
)
}

export function findInvalidFocusedSeriesNames(grapher: Grapher): SeriesName[] {
const availableSeriesNames = new Set(grapher.chartSeriesNames)
const focusedSeriesNames = grapher.focusArray.seriesNameSet

return Array.from(
differenceOfSets([focusedSeriesNames, availableSeriesNames])
)
}
55 changes: 42 additions & 13 deletions adminSiteClient/EditorDataTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
DropResult,
} from "react-beautiful-dnd"
import { AbstractChartEditor } from "./AbstractChartEditor.js"
import { findInvalidFocusedSeriesNames } from "./EditorBasicTab.js"

interface EntityListItemProps extends React.HTMLProps<HTMLDivElement> {
grapher: Grapher
Expand Down Expand Up @@ -143,12 +142,12 @@ export class EntitySelectionSection extends React.Component<{

@action.bound onAddKey(entityName: EntityName) {
this.editor.grapher.selection.selectEntity(entityName)
this.syncFocusedSeriesNames()
this.editor.removeInvalidFocusedSeriesNames()
}

@action.bound onRemoveKey(entityName: EntityName) {
this.editor.grapher.selection.deselectEntity(entityName)
this.syncFocusedSeriesNames()
this.editor.removeInvalidFocusedSeriesNames()
}

@action.bound onDragEnd(result: DropResult) {
Expand All @@ -171,12 +170,7 @@ export class EntitySelectionSection extends React.Component<{
grapher.selection.setSelectedEntities(
activeParentConfig.selectedEntityNames
)
}

@action.bound private syncFocusedSeriesNames(): void {
const { grapher } = this.props.editor
const invalidFocusedSeriesNames = findInvalidFocusedSeriesNames(grapher)
grapher.focusArray.remove(...invalidFocusedSeriesNames)
this.editor.removeInvalidFocusedSeriesNames()
}

render() {
Expand Down Expand Up @@ -287,10 +281,23 @@ export class FocusSection extends React.Component<{
this.editor.grapher.focusArray.remove(seriesName)
}

@action.bound setFocusedSeriesNamesToParentValue() {
const { grapher, activeParentConfig } = this.editor
if (!activeParentConfig || !activeParentConfig.focusedSeriesNames)
return
grapher.focusArray.clearAllAndAdd(
...activeParentConfig.focusedSeriesNames
)
this.editor.removeInvalidFocusedSeriesNames()
}

render() {
const { editor } = this
const { grapher } = editor

const isFocusInherited =
editor.isPropertyInherited("focusedSeriesNames")

const focusedSeriesNameSet = grapher.focusArray.seriesNameSet
const focusedSeriesNames = grapher.focusArray.seriesNames

Expand All @@ -300,13 +307,19 @@ export class FocusSection extends React.Component<{
seriesNameSet,
focusedSeriesNameSet,
])

// focusing only makes sense for two or more plotted series
if (focusedSeriesNameSet.size === 0 && availableSeriesNameSet.size < 2)
return null

const availableSeriesNames: SeriesName[] = sortBy(
Array.from(availableSeriesNameSet)
)

const invalidFocusedSeriesNames = new Set(
findInvalidFocusedSeriesNames(grapher)
)
const invalidFocusedSeriesNames = differenceOfSets([
focusedSeriesNameSet,
seriesNameSet,
])

return (
<Section name="Data to highlight">
Expand All @@ -318,6 +331,20 @@ export class FocusSection extends React.Component<{
.concat(availableSeriesNames)
.map((key) => ({ value: key }))}
/>
{editor.couldPropertyBeInherited("focusedSeriesNames") && (
<button
className="btn btn-outline-secondary"
type="button"
style={{ maxWidth: "min-content" }}
title="Reset to parent focus"
onClick={this.setFocusedSeriesNamesToParentValue}
disabled={isFocusInherited}
>
<FontAwesomeIcon
icon={isFocusInherited ? faLink : faUnlink}
/>
</button>
)}
</FieldsRow>
{focusedSeriesNames.map((seriesName) => (
<SeriesListItem
Expand Down Expand Up @@ -450,7 +477,9 @@ export class EditorDataTab<
</div>
</Section>
<EntitySelectionSection editor={editor} />
<FocusSection editor={editor} />
{features.canHighlightSeries && (
<FocusSection editor={editor} />
)}
{features.canSpecifyMissingDataStrategy && (
<MissingDataSection editor={this.props.editor} />
)}
Expand Down
1 change: 1 addition & 0 deletions adminSiteClient/EditorExportTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export class EditorExportTab<
staticFormat: format,
selectedEntityNames:
this.grapher.selection.selectedEntityNames,
focusedSeriesNames: this.grapher.focusedSeriesNames,
isSocialMediaExport,
})
}
Expand Down
7 changes: 7 additions & 0 deletions adminSiteClient/EditorFeatures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,11 @@ export class EditorFeatures {
)
)
}

@computed get canHighlightSeries() {
return (
(this.grapher.hasLineChart || this.grapher.hasSlopeChart) &&
this.grapher.isOnChartTab
)
}
}
Loading

0 comments on commit 9a69cd3

Please sign in to comment.