diff --git a/adminSiteClient/EditorBasicTab.tsx b/adminSiteClient/EditorBasicTab.tsx index cb09fea7be1..67ced5871b5 100644 --- a/adminSiteClient/EditorBasicTab.tsx +++ b/adminSiteClient/EditorBasicTab.tsx @@ -20,7 +20,6 @@ import { CONTINENTS_INDICATOR_ID, POPULATION_INDICATOR_ID_USED_IN_ADMIN, allChartTypes, - allChartTypesDisabled, } from "@ourworldindata/grapher" import { DimensionProperty, @@ -363,11 +362,11 @@ export class EditorBasicTab< const { grapher } = this.props.editor const newChartType = value as ChartTypeName - grapher.availableTabs = { - ...grapher.availableTabs, - ...allChartTypesDisabled, - [newChartType]: true, - } + const newTabs: ChartTypeName[] = (grapher.availableTabs ?? []).filter( + (tab) => tab !== ChartTypeName.WorldMap + ) + newTabs.push(newChartType) + grapher.availableTabs = newTabs if ( grapher.tab !== GrapherTabOption.Table && @@ -435,18 +434,20 @@ export class EditorBasicTab< label="Chart tab" value={grapher.hasChartTab} onValue={(value) => { - // TODO: remove false? if (value) { + // add line chart tab grapher.availableTabs = - grapher.availableTabs ?? {} - grapher.availableTabs[ + grapher.availableTabs ?? [] + grapher.availableTabs.push( ChartTypeName.LineChart - ] = true + ) } else { - grapher.availableTabs = { - ...grapher.availableTabs, - ...allChartTypesDisabled, - } + // remove all chart tabs + grapher.availableTabs = + grapher.availableTabs?.filter( + (tab) => + tab === ChartTypeName.WorldMap + ) } }} /> @@ -454,9 +455,20 @@ export class EditorBasicTab< label="Map tab" value={grapher.hasMapTab} onValue={(value) => { - grapher.availableTabs = - grapher.availableTabs ?? {} - grapher.availableTabs.WorldMap = value + if (value) { + // add map tab + grapher.availableTabs = [ + ChartTypeName.WorldMap, + ...(grapher.availableTabs ?? []), + ] + } else { + // remove map tab + grapher.availableTabs = + grapher.availableTabs?.filter( + (tab) => + tab !== ChartTypeName.WorldMap + ) + } }} /> diff --git a/adminSiteClient/VariableEditPage.tsx b/adminSiteClient/VariableEditPage.tsx index 30e8da6d42c..d9a0846d7c3 100644 --- a/adminSiteClient/VariableEditPage.tsx +++ b/adminSiteClient/VariableEditPage.tsx @@ -688,23 +688,22 @@ class VariableEditor extends React.Component<{ @computed private get grapherConfig(): GrapherInterface { const { variable } = this.props const grapherConfig = variable.grapherConfig - if (grapherConfig) + if (grapherConfig) { + let { availableTabs = [] } = grapherConfig + if (!availableTabs.includes(ChartTypeName.WorldMap)) { + availableTabs = [ChartTypeName.WorldMap, ...availableTabs] + } return { ...grapherConfig, - availableTabs: { - ...grapherConfig.availableTabs, - [ChartTypeName.WorldMap]: true, - }, + availableTabs, tab: GrapherTabOption.WorldMap, } - else + } else return { yAxis: { min: 0 }, map: { columnSlug: this.props.variable.id.toString() }, tab: GrapherTabOption.WorldMap, - availableTabs: { - [ChartTypeName.WorldMap]: true, - }, + availableTabs: [ChartTypeName.WorldMap], dimensions: [ { property: DimensionProperty.y, diff --git a/db/migration/1731316808331-AddAvailableTabsToGrapherConfigs.ts b/db/migration/1731316808331-AddAvailableTabsToGrapherConfigs.ts index d2bba74d154..60fa929da92 100644 --- a/db/migration/1731316808331-AddAvailableTabsToGrapherConfigs.ts +++ b/db/migration/1731316808331-AddAvailableTabsToGrapherConfigs.ts @@ -24,18 +24,12 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 ): Promise { // CASES: // 1. hasMapTab=false, hasChartTab=false -> availableTabs is unset - // 2. hasMapTab=false, hasChartTab=true -> availableTabs={ [chartType]: true } - // 3. hasMapTab=true, hasChartTab=false -> availableTabs={ WorldMap: true } - // 4. hasMapTab=true, hasChartTab=true -> availableTabs={ WorldMap: true, [chartType]: true } + // 2. hasMapTab=false, hasChartTab=true -> availableTabs=[chartType] + // 3. hasMapTab=true, hasChartTab=false -> availableTabs=["WorldMap"] + // 4. hasMapTab=true, hasChartTab=true -> availableTabs=["WorldMap", chartType] for (const configType of ["patch", "full"]) { - // We could run a single query that updates all configs at once, - // but then some values in `availableTabs` would be set to 'false'. - // That shouldn't be a problem (and over time we'll probably see - // configs where chart types are set to false), but for now I like - // to keep it clean. - - // CASE 2: hasMapTab=false, hasChartTab=true -> availableTabs={ [chartType]: true } + // CASE 2: hasMapTab=false, hasChartTab=true -> availableTabs=[chartType] await queryRunner.query( ` -- sql @@ -43,7 +37,7 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 SET ?? = JSON_SET( ??, '$.availableTabs', - JSON_OBJECT(COALESCE(?? ->> '$.type', 'LineChart'), TRUE) + JSON_ARRAY(COALESCE(?? ->> '$.type', 'LineChart')) ) WHERE (?? ->> '$.hasMapTab' = 'false' OR ?? ->> '$.hasMapTab' IS NULL) @@ -60,7 +54,7 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 ] ) - // CASE 3: hasMapTab=true, hasChartTab=false -> availableTabs={ WorldMap: true } + // CASE 3: hasMapTab=true, hasChartTab=false -> availableTabs=["WorldMap"] await queryRunner.query( ` -- sql @@ -68,7 +62,7 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 SET ?? = JSON_SET( ??, '$.availableTabs', - JSON_OBJECT('WorldMap', TRUE) + JSON_ARRAY('WorldMap') ) WHERE (?? ->> '$.hasMapTab' = 'true') @@ -77,18 +71,15 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 [configType, configType, configType, configType] ) - // CASE 4: hasMapTab=true, hasChartTab=true -> availableTabs={ WorldMap: true, [chartType]: true } + // CASE 4: hasMapTab=true, hasChartTab=true -> availableTabs=["WorldMap", chartType] await queryRunner.query( ` -- sql UPDATE chart_configs SET ?? = JSON_SET( - ??, + ??, '$.availableTabs', - JSON_OBJECT( - 'WorldMap', TRUE, - COALESCE(?? ->> '$.type', 'LineChart'), TRUE - ) + JSON_ARRAY('WorldMap', COALESCE(?? ->> '$.type', 'LineChart')) ) WHERE (?? ->> '$.hasMapTab' = 'true') @@ -168,17 +159,16 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 ADD COLUMN chartType VARCHAR(255) GENERATED ALWAYS AS ( CASE - WHEN full ->> '$.availableTabs' IS NULL OR JSON_LENGTH(full, '$.availableTabs') = 0 THEN NULL -- Graphers with a line and a slope chart are considered to be of type LineChart - WHEN full ->> '$.availableTabs.LineChart' = 'true' THEN 'LineChart' - WHEN full ->> '$.availableTabs.SlopeChart' = 'true' THEN 'SlopeChart' - WHEN full ->> '$.availableTabs.ScatterPlot' = 'true' THEN 'ScatterPlot' - WHEN full ->> '$.availableTabs.StackedArea' = 'true' THEN 'StackedArea' - WHEN full ->> '$.availableTabs.StackedBar' = 'true' THEN 'StackedBar' - WHEN full ->> '$.availableTabs.ScatterPlot' = 'true' THEN 'ScatterPlot' - WHEN full ->> '$.availableTabs.DiscreteBar' = 'true' THEN 'DiscreteBar' - WHEN full ->> '$.availableTabs.StackedDiscreteBar' = 'true' THEN 'StackedDiscreteBar' - WHEN full ->> '$.availableTabs.Marimekko' = 'true' THEN 'Marimekko' + WHEN JSON_CONTAINS(full, '"LineChart"', '$.availableTabs') THEN 'LineChart' + WHEN JSON_CONTAINS(full, '"SlopeChart"', '$.availableTabs') THEN 'SlopeChart' + WHEN JSON_CONTAINS(full, '"ScatterPlot"', '$.availableTabs') THEN 'ScatterPlot' + WHEN JSON_CONTAINS(full, '"StackedArea"', '$.availableTabs') THEN 'StackedArea' + WHEN JSON_CONTAINS(full, '"StackedBar"', '$.availableTabs') THEN 'StackedBar' + WHEN JSON_CONTAINS(full, '"ScatterPlot"', '$.availableTabs') THEN 'ScatterPlot' + WHEN JSON_CONTAINS(full, '"DiscreteBar"', '$.availableTabs') THEN 'DiscreteBar' + WHEN JSON_CONTAINS(full, '"StackedDiscreteBar"', '$.availableTabs') THEN 'StackedDiscreteBar' + WHEN JSON_CONTAINS(full, '"Marimekko"', '$.availableTabs') THEN 'Marimekko' ELSE NULL END ) @@ -202,7 +192,7 @@ export class AddAvailableTabsToGrapherConfigs1731316808331 await this.addAvailableTabsToGrapherConfigs(queryRunner) await this.updateTabField(queryRunner) await this.removeTypeHasMapTabAndHasChartTabFields(queryRunner) - await this.addDerivedChartTypeColumn(queryRunner) + // await this.addDerivedChartTypeColumn(queryRunner) await this.updateSchema( queryRunner, "https://files.ourworldindata.org/schemas/grapher-schema.007.json" diff --git a/packages/@ourworldindata/explorer/src/Explorer.sample.tsx b/packages/@ourworldindata/explorer/src/Explorer.sample.tsx index d7646e8a5ac..9b534653f8b 100644 --- a/packages/@ourworldindata/explorer/src/Explorer.sample.tsx +++ b/packages/@ourworldindata/explorer/src/Explorer.sample.tsx @@ -1,6 +1,6 @@ import React from "react" import { DimensionProperty } from "@ourworldindata/utils" -import { GrapherTabOption } from "@ourworldindata/types" +import { GrapherTabOption, ChartTypeName } from "@ourworldindata/types" import { GrapherProgrammaticInterface } from "@ourworldindata/grapher" import { Explorer, ExplorerProps } from "./Explorer.js" @@ -54,7 +54,7 @@ export const SampleExplorerOfGraphers = (props?: Partial) => { property: DimensionProperty.y, }, ], - // TODO + availableTabs: [ChartTypeName.LineChart], tab: GrapherTabOption.LineChart, owidDataset: new Map([ [ diff --git a/packages/@ourworldindata/explorer/src/ExplorerProgram.ts b/packages/@ourworldindata/explorer/src/ExplorerProgram.ts index 400519a3c9a..ada1f87bd89 100644 --- a/packages/@ourworldindata/explorer/src/ExplorerProgram.ts +++ b/packages/@ourworldindata/explorer/src/ExplorerProgram.ts @@ -409,6 +409,19 @@ export class ExplorerProgram extends GridProgram { // assume config is valid against the latest schema mergedConfig.$schema = defaultGrapherConfig.$schema + // construct the available tabs from type, hasChartTab and hasMapTab + const { + type = ChartTypeName.LineChart, + hasChartTab = true, + hasMapTab = false, + } = this.explorerGrapherConfig + if (hasMapTab || hasChartTab) { + const availableTabs = [] + if (hasMapTab) availableTabs.push(ChartTypeName.WorldMap) + if (hasChartTab) availableTabs.push(type) + mergedConfig.availableTabs = availableTabs + } + return mergedConfig } diff --git a/packages/@ourworldindata/explorer/src/GrapherGrammar.ts b/packages/@ourworldindata/explorer/src/GrapherGrammar.ts index 10aebe48313..1d1bdbe5860 100644 --- a/packages/@ourworldindata/explorer/src/GrapherGrammar.ts +++ b/packages/@ourworldindata/explorer/src/GrapherGrammar.ts @@ -24,7 +24,6 @@ import { IndicatorIdOrEtlPathCellDef, GrapherCellDef, } from "./gridLang/GridLangConstants.js" -import { allChartTypesDisabled } from "@ourworldindata/grapher" const toTerminalOptions = (keywords: string[]): CellDef[] => { return keywords.map((keyword) => ({ @@ -66,7 +65,7 @@ export const GrapherGrammar: Grammar = { keyword: "type", description: `The type of chart to show such as LineChart or ScatterPlot.`, terminalOptions: toTerminalOptions(Object.values(ChartTypeName)), - toGrapherObject: (value) => ({ availableTabs: { [value]: true } }), + toGrapherObject: () => ({}), // handled in code }, grapherId: { ...IntegerCellDef, @@ -85,9 +84,7 @@ export const GrapherGrammar: Grammar = { ...BooleanCellDef, keyword: "hasMapTab", description: "Show the map tab?", - toGrapherObject: (value) => ({ - availableTabs: { [ChartTypeName.WorldMap]: value }, - }), + toGrapherObject: () => ({}), // handled in code }, tab: { ...EnumCellDef, @@ -101,7 +98,7 @@ export const GrapherGrammar: Grammar = { keyword: "hasChartTab", description: "Show the chart tab?", // overwrites the given chart type if provided - toGrapherObject: (value) => ({ availableTabs: allChartTypesDisabled }), + toGrapherObject: () => ({}), // handled in code }, xSlug: { ...SlugDeclarationCellDef, diff --git a/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx b/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx index 9192274a072..7268d57d33f 100644 --- a/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx +++ b/packages/@ourworldindata/grapher/src/chart/ChartUtils.tsx @@ -113,14 +113,3 @@ export function getShortNameForEntity(entityName: string): string | undefined { const country = getCountryByName(entityName) return country?.shortName } - -export function getTabPosition(tab: ChartTypeName): number { - switch (tab) { - case ChartTypeName.WorldMap: - return 1 - case ChartTypeName.LineChart: - return 2 - default: - return 3 - } -} diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.jsdom.test.ts b/packages/@ourworldindata/grapher/src/core/Grapher.jsdom.test.ts index 6e0a3e7850a..52952ab5b70 100755 --- a/packages/@ourworldindata/grapher/src/core/Grapher.jsdom.test.ts +++ b/packages/@ourworldindata/grapher/src/core/Grapher.jsdom.test.ts @@ -45,7 +45,7 @@ const TestGrapherConfig = (): { property: DimensionProperty variableId: any }[] - availableTabs: ChartTypeNameRecord + availableTabs: ChartTypeName[] } => { const table = SynthesizeGDPTable({ entityCount: 10 }) return { @@ -58,7 +58,7 @@ const TestGrapherConfig = (): { variableId: SampleColumnSlugs.GDP as any, }, ], - availableTabs: { [ChartTypeName.LineChart]: true }, + availableTabs: [ChartTypeName.LineChart], } } @@ -72,9 +72,12 @@ it("regression fix: container options are not serialized", () => { it("can get dimension slots", () => { const grapher = new Grapher() + expect(grapher.dimensionSlots.length).toBe(1) + + grapher.availableTabs = [ChartTypeName.LineChart] expect(grapher.dimensionSlots.length).toBe(2) - grapher.availableTabs[ChartTypeName.ScatterPlot] = true + grapher.availableTabs = [ChartTypeName.ScatterPlot] expect(grapher.dimensionSlots.length).toBe(4) }) @@ -88,9 +91,7 @@ it("an empty Grapher serializes to an object that includes only the schema", () }) it("a bad chart type does not crash grapher", () => { - const input = { - availableTabs: { fff: true } as any, - } + const input = { availableTabs: ["invalid" as any] } expect(new Grapher(input).toObject()).toEqual({ ...input, $schema: defaultGrapherConfig.$schema, @@ -217,25 +218,25 @@ it("can generate a url with country selection even if there is no entity code", describe("hasTimeline", () => { it("charts with timeline", () => { const grapher = new Grapher(legacyConfig) - grapher.availableTabs = { [GrapherTabOption.LineChart]: true } + grapher.availableTabs = [ChartTypeName.LineChart] expect(grapher.hasTimeline).toBeTruthy() - grapher.availableTabs = { [GrapherTabOption.SlopeChart]: true } + grapher.availableTabs = [ChartTypeName.SlopeChart] expect(grapher.hasTimeline).toBeTruthy() - grapher.availableTabs = { [GrapherTabOption.StackedArea]: true } + grapher.availableTabs = [ChartTypeName.StackedArea] expect(grapher.hasTimeline).toBeTruthy() - grapher.availableTabs = { [GrapherTabOption.StackedBar]: true } + grapher.availableTabs = [ChartTypeName.StackedBar] expect(grapher.hasTimeline).toBeTruthy() - grapher.availableTabs = { [GrapherTabOption.DiscreteBar]: true } + grapher.availableTabs = [ChartTypeName.DiscreteBar] expect(grapher.hasTimeline).toBeTruthy() }) it("map tab has timeline even if chart doesn't", () => { const grapher = new Grapher(legacyConfig) grapher.hideTimeline = true - grapher.availableTabs = { - [GrapherTabOption.WorldMap]: true, - [GrapherTabOption.LineChart]: true, - } + grapher.availableTabs = [ + ChartTypeName.WorldMap, + ChartTypeName.LineChart, + ] expect(grapher.hasTimeline).toBeFalsy() grapher.tab = GrapherTabOption.WorldMap expect(grapher.hasTimeline).toBeTruthy() @@ -298,6 +299,7 @@ const getGrapher = (): Grapher => ]), minTime: -5000, maxTime: 5000, + // availableTabs: [ChartTypeName.LineChart], }) function fromQueryParams( @@ -388,7 +390,7 @@ describe("authors can use maxTime", () => { const table = SynthesizeGDPTable({ timeRange: [2000, 2010] }) const grapher = new Grapher({ table, - availableTabs: { [ChartTypeName.DiscreteBar]: true }, + availableTabs: [ChartTypeName.DiscreteBar], selectedEntityNames: table.availableEntityNames, maxTime: 2005, ySlugs: "GDP", @@ -806,6 +808,7 @@ it("canChangeEntity reflects all available entities before transforms", () => { addCountryMode: EntitySelectionMode.SingleEntity, table, selectedEntityNames: table.sampleEntityName(1), + availableTabs: [ChartTypeName.LineChart], }) expect(grapher.canChangeEntity).toBe(true) }) @@ -927,7 +930,7 @@ it("correctly identifies activeColumnSlugs", () => { `) const grapher = new Grapher({ table, - availableTabs: { [ChartTypeName.ScatterPlot]: true }, + availableTabs: [ChartTypeName.ScatterPlot], xSlug: "gdp", ySlugs: "child_mortality", colorSlug: "continent", @@ -964,9 +967,7 @@ it("considers map tolerance before using column tolerance", () => { const grapher = new Grapher({ table, - availableTabs: { - [GrapherTabOption.WorldMap]: true, - }, + availableTabs: [ChartTypeName.WorldMap, ChartTypeName.LineChart], ySlugs: "gdp", tab: GrapherTabOption.WorldMap, map: new MapConfig({ timeTolerance: 1, columnSlug: "gdp", time: 2002 }), @@ -1027,7 +1028,7 @@ describe("tableForSelection", () => { const grapher = new Grapher({ table, - availableTabs: { [ChartTypeName.ScatterPlot]: true }, + availableTabs: [ChartTypeName.ScatterPlot], excludedEntities: [3], xSlug: "x", ySlugs: "y", @@ -1063,7 +1064,7 @@ it("handles tolerance when there are gaps in ScatterPlot data", () => { const grapher = new Grapher({ table, - availableTabs: { [ChartTypeName.ScatterPlot]: true }, + availableTabs: [ChartTypeName.ScatterPlot], xSlug: "x", ySlugs: "y", minTime: 1999, diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index 7e3bea5f546..4ed573848a1 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -65,6 +65,7 @@ import { sortBy, extractDetailsFromSyntax, omit, + getTabPosition, } from "@ourworldindata/utils" import { MarkdownTextWrap, @@ -192,7 +193,6 @@ import { ScatterPlotManager } from "../scatterCharts/ScatterPlotChartConstants" import { autoDetectSeriesStrategy, autoDetectYColumnSlugs, - getTabPosition, } from "../chart/ChartUtils" import classnames from "classnames" import { GrapherAnalytics } from "./GrapherAnalytics" @@ -364,7 +364,7 @@ export class Grapher @observable.ref internalNotes?: string = undefined @observable.ref originUrl?: string = undefined - @observable availableTabs: ChartTypeNameRecord = {} + @observable availableTabs?: ChartTypeName[] @observable.ref tab?: GrapherTabOption @observable hideAnnotationFieldsInTitle?: AnnotationFieldsInTitle = @@ -718,15 +718,7 @@ export class Grapher } @computed get availableTabsSet(): Set { - const { availableTabs = {} } = this - - const availableTabsSet = new Set() - for (const tab of Object.keys(availableTabs)) { - const isEnabled = availableTabs[tab as ChartTypeName] - if (isEnabled) availableTabsSet.add(tab as ChartTypeName) - } - - return availableTabsSet + return new Set(this.availableTabs ?? []) } /** diff --git a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts index b8d4cedae7c..4d20a0d213b 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts @@ -97,7 +97,3 @@ export const grapherInterfaceWithHiddenControlsOnly: GrapherProgrammaticInterfac export const allChartTypes = Object.values(ChartTypeName).filter( (type) => type !== ChartTypeName.WorldMap ) - -export const allChartTypesDisabled = Object.fromEntries( - allChartTypes.map((chartType) => [chartType, false]) -) diff --git a/packages/@ourworldindata/grapher/src/core/GrapherWithChartTypes.jsdom.test.tsx b/packages/@ourworldindata/grapher/src/core/GrapherWithChartTypes.jsdom.test.tsx index b1624d98adc..dac33672993 100755 --- a/packages/@ourworldindata/grapher/src/core/GrapherWithChartTypes.jsdom.test.tsx +++ b/packages/@ourworldindata/grapher/src/core/GrapherWithChartTypes.jsdom.test.tsx @@ -50,7 +50,7 @@ const basicGrapherConfig: GrapherProgrammaticInterface = { describe("grapher and discrete bar charts", () => { const grapher = new Grapher({ - availableTabs: { [ChartTypeName.DiscreteBar]: true }, + availableTabs: [ChartTypeName.DiscreteBar], ...basicGrapherConfig, }) expect(grapher.chartInstance.series.length).toBeGreaterThan(0) diff --git a/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.test.ts b/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.test.ts index 438844935aa..94dd893a162 100755 --- a/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.test.ts +++ b/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.test.ts @@ -466,7 +466,7 @@ describe(legacyToOwidTableAndDimensions, () => { it("joins targetTime", () => { const scatterLegacyGrapherConfig = { ...legacyGrapherConfig, - availableTabs: { [ChartTypeName.ScatterPlot]: true }, + availableTabs: [ChartTypeName.ScatterPlot], } const { table } = legacyToOwidTableAndDimensions( diff --git a/packages/@ourworldindata/grapher/src/dataTable/DataTable.jsdom.test.tsx b/packages/@ourworldindata/grapher/src/dataTable/DataTable.jsdom.test.tsx index 44a5b6d1ccd..e0ece919f36 100755 --- a/packages/@ourworldindata/grapher/src/dataTable/DataTable.jsdom.test.tsx +++ b/packages/@ourworldindata/grapher/src/dataTable/DataTable.jsdom.test.tsx @@ -70,7 +70,7 @@ describe("when you select a range of years", () => { let view: ReactWrapper beforeAll(() => { const grapher = childMortalityGrapher({ - availableTabs: { [GrapherTabOption.LineChart]: true }, + availableTabs: [ChartTypeName.LineChart], tab: GrapherTabOption.Table, }) grapher.timelineHandleTimeBounds = [1950, 2019] @@ -158,7 +158,7 @@ describe("when the table has no filter toggle", () => { const grapher = LifeExpectancyGrapher({ selectedEntityNames: ["World"], hideEntityControls: true, // no filter toggle - availableTabs: { [ChartTypeName.WorldMap]: true }, + availableTabs: [ChartTypeName.WorldMap], }) const view = Enzyme.mount() const rows = view.find("tbody tr:not(.title)") diff --git a/packages/@ourworldindata/grapher/src/index.ts b/packages/@ourworldindata/grapher/src/index.ts index 78ae041ace4..deb58f14f7a 100644 --- a/packages/@ourworldindata/grapher/src/index.ts +++ b/packages/@ourworldindata/grapher/src/index.ts @@ -26,7 +26,6 @@ export { CONTINENTS_INDICATOR_ID, POPULATION_INDICATOR_ID_USED_IN_ADMIN, allChartTypes, - allChartTypesDisabled, } from "./core/GrapherConstants" export { getVariableDataRoute, diff --git a/packages/@ourworldindata/grapher/src/mapCharts/MapChart.sample.ts b/packages/@ourworldindata/grapher/src/mapCharts/MapChart.sample.ts index ddb64819867..b741d73d328 100644 --- a/packages/@ourworldindata/grapher/src/mapCharts/MapChart.sample.ts +++ b/packages/@ourworldindata/grapher/src/mapCharts/MapChart.sample.ts @@ -4,7 +4,7 @@ import { ChartTypeName, GrapherTabOption } from "@ourworldindata/types" // TODO: rewrite export const legacyMapGrapher: GrapherProgrammaticInterface = { - availableTabs: { [ChartTypeName.WorldMap]: true }, + availableTabs: [ChartTypeName.WorldMap], tab: GrapherTabOption.WorldMap, map: { timeTolerance: 5, diff --git a/packages/@ourworldindata/grapher/src/schema/defaultGrapherConfig.ts b/packages/@ourworldindata/grapher/src/schema/defaultGrapherConfig.ts index 4dc501d0df1..a208acf6d25 100644 --- a/packages/@ourworldindata/grapher/src/schema/defaultGrapherConfig.ts +++ b/packages/@ourworldindata/grapher/src/schema/defaultGrapherConfig.ts @@ -16,17 +16,6 @@ export const outdatedSchemaVersions = [ export const defaultGrapherConfig = { $schema: "https://files.ourworldindata.org/schemas/grapher-schema.007.json", - availableTabs: { - WorldMap: false, - LineChart: false, - ScatterPlot: false, - StackedArea: false, - StackedBar: false, - DiscreteBar: false, - StackedDiscreteBar: false, - SlopeChart: false, - Marimekko: false, - }, map: { projection: "World", hideTimeline: false, diff --git a/packages/@ourworldindata/grapher/src/schema/grapher-schema.007.yaml b/packages/@ourworldindata/grapher/src/schema/grapher-schema.007.yaml index 89133e19eb1..f14e75a1a25 100644 --- a/packages/@ourworldindata/grapher/src/schema/grapher-schema.007.yaml +++ b/packages/@ourworldindata/grapher/src/schema/grapher-schema.007.yaml @@ -26,37 +26,20 @@ properties: description: Internal DB id. Useful internally for OWID but not required if just using grapher directly. minimum: 0 availableTabs: - type: object - description: The tabs that are available for selection. The "Table" tab can't be selected because it is always available. - properties: - WorldMap: - type: boolean - default: false - LineChart: - type: boolean - default: false - ScatterPlot: - type: boolean - default: false - StackedArea: - type: boolean - default: false - StackedBar: - type: boolean - default: false - DiscreteBar: - type: boolean - default: false - StackedDiscreteBar: - type: boolean - default: false - SlopeChart: - type: boolean - default: false - Marimekko: - type: boolean - default: false - additionalProperties: false + type: array + description: The tabs that are available for selection. The Table tab is always present. + items: + type: string + enum: + - WorldMap + - LineChart + - ScatterPlot + - StackedArea + - DiscreteBar + - StackedDiscreteBar + - SlopeChart + - StackedBar + - Marimekko tab: type: string description: The tab that is shown initially. Defaults to the chart tab if available. diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.jsdom.test.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.jsdom.test.tsx index e41ae68f44c..8205950952f 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.jsdom.test.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.jsdom.test.tsx @@ -23,7 +23,7 @@ it("can filter years correctly", () => { // TODO: why is it ySlugs and xSlug here instead of yColumnSlugs and xColumnSlug? Unify when we have config migrations? const manager = { - availableTabs: { [ChartTypeName.Marimekko]: true }, + availableTabs: [ChartTypeName.Marimekko], table, selection: table.availableEntityNames, ySlugs: "percentBelow2USD", @@ -133,7 +133,7 @@ it("shows no data points at the end", () => { // TODO: why is it ySlugs and xSlug here instead of yColumnSlugs and xColumnSlug? Unify when we have config migrations? const manager = { - availableTabs: { [ChartTypeName.Marimekko]: true }, + availableTabs: [ChartTypeName.Marimekko], table, selection: table.availableEntityNames, ySlugs: "percentBelow2USD", @@ -233,7 +233,7 @@ test("interpolation works as expected", () => { // TODO: why is it ySlugs and xSlug here instead of yColumnSlugs and xColumnSlug? Unify when we have config migrations? const manager = { - availableTabs: { [ChartTypeName.Marimekko]: true }, + availableTabs: [ChartTypeName.Marimekko], table, selection: table.availableEntityNames, ySlugs: "percentBelow2USD", @@ -344,7 +344,7 @@ it("can deal with y columns with missing values", () => { // TODO: why is it ySlugs and xSlug here instead of yColumnSlugs and xColumnSlug? Unify when we have config migrations? const manager = { - availableTabs: { [ChartTypeName.Marimekko]: true }, + availableTabs: [ChartTypeName.Marimekko], table, selection: table.availableEntityNames, ySlugs: "percentBelow2USD percentBelow10USD", diff --git a/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts b/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts index c4204df1562..1094a13253b 100644 --- a/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts +++ b/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts @@ -547,7 +547,7 @@ export interface GrapherInterface extends SortConfig { subtitle?: string sourceDesc?: string note?: string - availableTabs?: ChartTypeNameRecord + availableTabs?: ChartTypeName[] tab?: GrapherTabOption hideAnnotationFieldsInTitle?: AnnotationFieldsInTitle minTime?: TimeBound | TimeBoundValueStr diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 159ef9f82af..00dc19f5bb7 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1964,64 +1964,6 @@ export function traverseObjects>( return result } -function toSortedAvailableTabs( - availableTabs: Record -): ChartTypeName[] { - const enabledTabs = Object.entries(availableTabs) - .filter(([_, enabled]) => enabled) - .map(([tab]) => tab as ChartTypeName) - // TODO: actually sort - return enabledTabs - // return sortBy(enabledTabs, (chartType) => availableTabs[chartType]) -} - -export function hasChartTabFromConfig(config: GrapherInterface): boolean { - const { availableTabs } = config - if (!availableTabs) return false - - const allChartTypes = Object.values(ChartTypeName).filter( - (type) => type !== ChartTypeName.WorldMap - ) - for (const type of allChartTypes) { - if (availableTabs[type]) return true - } - return false -} - -export function getMainChartTypeFromConfig( - config: GrapherInterface -): ChartTypeName | undefined { - const { availableTabs } = config - - if (!availableTabs) return undefined - - const enabledChartTypes = Object.entries(availableTabs) - .filter(([tab, enabled]) => tab !== ChartTypeName.WorldMap && enabled) - .map(([tab]) => tab as ChartTypeName) - const sortedTabs = enabledChartTypes // TODO - - return sortedTabs[0] -} - -export function getParentVariableIdFromChartConfig( - config: GrapherInterface -): number | undefined { - const { dimensions } = config - - const chartType = getMainChartTypeFromConfig(config) - if (chartType === ChartTypeName.ScatterPlot) return undefined - - if (!dimensions) return undefined - - const yVariableIds = dimensions - .filter((d) => d.property === DimensionProperty.y) - .map((d) => d.variableId) - - if (yVariableIds.length !== 1) return undefined - - return yVariableIds[0] -} - // Page numbers are 0-indexed - you'll have to +1 to them when rendering export function getPaginationPageNumbers( currentPageNumber: number, diff --git a/packages/@ourworldindata/utils/src/grapherConfigUtils.ts b/packages/@ourworldindata/utils/src/grapherConfigUtils.ts index 2a8db8e0120..df5779295c0 100644 --- a/packages/@ourworldindata/utils/src/grapherConfigUtils.ts +++ b/packages/@ourworldindata/utils/src/grapherConfigUtils.ts @@ -1,4 +1,8 @@ -import { GrapherInterface } from "@ourworldindata/types" +import { + ChartTypeName, + DimensionProperty, + GrapherInterface, +} from "@ourworldindata/types" import { isEqual, mergeWith, @@ -10,6 +14,7 @@ import { omitEmptyObjectsRecursive, traverseObjects, isEmpty, + sortBy, } from "./Util" const REQUIRED_KEYS = ["$schema", "dimensions"] @@ -91,3 +96,52 @@ export function diffGrapherConfigs( return { ...diffed, ...keep } } + +export function getTabPosition(tab: ChartTypeName): number { + switch (tab) { + case ChartTypeName.WorldMap: + return 1 + case ChartTypeName.LineChart: + return 2 + default: + return 3 + } +} + +export function hasChartTabFromConfig(config: GrapherInterface): boolean { + const { availableTabs = [] } = config + const firstChartType = availableTabs.find( + (tab) => tab !== ChartTypeName.WorldMap + ) + return firstChartType !== undefined +} + +export function getMainChartTypeFromConfig( + config: GrapherInterface +): ChartTypeName | undefined { + const { availableTabs = [] } = config + const sortedTabs = sortBy(availableTabs, (tab) => getTabPosition(tab)) + const firstChartType = sortedTabs.find( + (tab) => tab !== ChartTypeName.WorldMap + ) + return firstChartType +} + +export function getParentVariableIdFromChartConfig( + config: GrapherInterface +): number | undefined { + const { dimensions } = config + + const chartType = getMainChartTypeFromConfig(config) + if (chartType === ChartTypeName.ScatterPlot) return undefined + + if (!dimensions) return undefined + + const yVariableIds = dimensions + .filter((d) => d.property === DimensionProperty.y) + .map((d) => d.variableId) + + if (yVariableIds.length !== 1) return undefined + + return yVariableIds[0] +} diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 5f8160309e6..6a33af8bd4e 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -123,9 +123,6 @@ export { createTagGraph, formatInlineList, lazy, - getMainChartTypeFromConfig, - getParentVariableIdFromChartConfig, - hasChartTabFromConfig, } from "./Util.js" export { @@ -338,6 +335,10 @@ export { isAndroid, isIOS } from "./BrowserUtils.js" export { diffGrapherConfigs, mergeGrapherConfigs, + getMainChartTypeFromConfig, + getParentVariableIdFromChartConfig, + hasChartTabFromConfig, + getTabPosition, } from "./grapherConfigUtils.js" export {