From f13a47e0b8f952cd6c6ea3b6c6dc9a5986170aef Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 29 May 2024 11:14:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20(scatter)=20feedback=20for=20sel?= =?UTF-8?q?ected=20data=20without=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grapher/src/chart/ChartManager.ts | 1 + .../grapher/src/core/Grapher.tsx | 2 +- .../src/scatterCharts/NoDataSection.tsx | 68 ++++++++++++++ .../src/scatterCharts/ScatterPlotChart.tsx | 91 +++++++++++++------ .../scatterCharts/ScatterPointsWithLabels.tsx | 6 +- .../grapher/src/slopeCharts/SlopeChart.tsx | 62 ++----------- 6 files changed, 146 insertions(+), 84 deletions(-) create mode 100644 packages/@ourworldindata/grapher/src/scatterCharts/NoDataSection.tsx diff --git a/packages/@ourworldindata/grapher/src/chart/ChartManager.ts b/packages/@ourworldindata/grapher/src/chart/ChartManager.ts index 50e7cf8df4c..a693d126593 100644 --- a/packages/@ourworldindata/grapher/src/chart/ChartManager.ts +++ b/packages/@ourworldindata/grapher/src/chart/ChartManager.ts @@ -91,6 +91,7 @@ export interface ChartManager { missingDataStrategy?: MissingDataStrategy isNarrow?: boolean + isStatic?: boolean isStaticAndSmall?: boolean secondaryColorInStaticCharts?: string diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index ac54c4a2203..eff9daca378 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -811,7 +811,7 @@ export class Grapher @observable.ref isDownloadModalOpen = false @observable.ref isEmbedModalOpen = false - @computed private get isStatic(): boolean { + @computed get isStatic(): boolean { return this.renderToStatic || this.isExportingToSvgOrPng } diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/NoDataSection.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/NoDataSection.tsx new file mode 100644 index 00000000000..ca7342a4ec6 --- /dev/null +++ b/packages/@ourworldindata/grapher/src/scatterCharts/NoDataSection.tsx @@ -0,0 +1,68 @@ +import React from "react" +import { Bounds } from "@ourworldindata/utils" +import { + GRAPHER_DARK_TEXT, + GRAPHER_FONT_SCALE_11_2, +} from "../core/GrapherConstants" + +export function NoDataSection({ + entityNames, + bounds, + baseFontSize = 16, +}: { + entityNames: string[] + bounds: Bounds + baseFontSize?: number +}): JSX.Element { + { + const displayedEntities = entityNames.slice(0, 5) + const numRemainingEntities = Math.max( + 0, + entityNames.length - displayedEntities.length + ) + + return ( + +
+ No data +
+ + {numRemainingEntities > 0 && ( +
+ &{" "} + {numRemainingEntities === 1 + ? "one" + : numRemainingEntities}{" "} + more +
+ )} +
+ ) + } +} diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx index 200bc92a0cf..f6cab7eda70 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx @@ -43,6 +43,7 @@ import { DEFAULT_BOUNDS, isTouchDevice, round, + difference, } from "@ourworldindata/utils" import { observer } from "mobx-react" import { NoDataModal } from "../noDataModal/NoDataModal" @@ -104,6 +105,7 @@ import { ScatterSizeLegendManager, } from "./ScatterSizeLegend" import { Tooltip, TooltipState, TooltipValueRange } from "../tooltip/Tooltip" +import { NoDataSection } from "./NoDataSection" @observer export class ScatterPlotChart @@ -741,6 +743,14 @@ export class ScatterPlotChart return new ScatterSizeLegend(this) } + @computed + private get selectedEntitiesWithoutData(): string[] { + return difference( + this.selectedEntityNames, + this.series.map((s) => s.seriesName) + ) + } + componentDidMount(): void { exposeInstanceOnWindow(this) } @@ -766,13 +776,44 @@ export class ScatterPlotChart legendDimensions, } = this - let sizeLegendY = bounds.top - if (this.legendItems.length > 0) { - sizeLegendY = bounds.top + legendDimensions.height + 16 - } - const arrowLegendY = sizeLegend - ? sizeLegendY + sizeLegend.height + 15 - : sizeLegendY + const hasLegendItems = this.legendItems.length > 0 + const verticalLegendHeight = hasLegendItems + ? legendDimensions.height + : 0 + const sizeLegendHeight = sizeLegend?.height ?? 0 + const arrowLegendHeight = arrowLegend?.height ?? 0 + + const legendPadding = 16 + const ySizeLegend = + bounds.top + + verticalLegendHeight + + (verticalLegendHeight > 0 ? legendPadding : 0) + const yArrowLegend = + ySizeLegend + + sizeLegendHeight + + (sizeLegendHeight > 0 ? legendPadding : 0) + const yNoDataSection = + yArrowLegend + + arrowLegendHeight + + (arrowLegendHeight > 0 ? legendPadding : 0) + + const noDataSectionBounds = new Bounds( + this.legendX, + yNoDataSection, + sidebarWidth, + bounds.height - yNoDataSection + ) + + const separatorLine = (y: number): JSX.Element | null => + y > bounds.top ? ( + + ) : null return ( @@ -800,38 +841,32 @@ export class ScatterPlotChart {sizeLegend && ( <> - {this.legendItems.length > 0 && ( - - )} - {sizeLegend.render(this.legendX, sizeLegendY)} + {separatorLine(ySizeLegend)} + {sizeLegend.render(this.legendX, ySizeLegend)} )} {arrowLegend && ( <> - + {separatorLine(yArrowLegend)} - {arrowLegend.render( - bounds.right - sidebarWidth, - arrowLegendY - )} + {arrowLegend.render(this.legendX, yArrowLegend)} )} + {this.selectedEntitiesWithoutData.length > 0 && ( + <> + {!this.manager.isStatic && + separatorLine(noDataSectionBounds.top)} + + + )} {this.tooltip} ) diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx index 9c0ae873e69..b2afb680bb6 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx @@ -76,7 +76,11 @@ export class ScatterPointsWithLabels extends React.Component 0 || - this.hoveredSeriesNames.length > 0 + this.hoveredSeriesNames.length > 0 || + // if the user has selected entities that are not in the chart, + // we want to move all entities into the background + (this.props.focusedSeriesNames.length > 0 && + this.focusedSeriesNames.length === 0) ) } diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx index 8e8695c7a19..28630892840 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx @@ -34,7 +34,6 @@ import { GRAPHER_DARK_TEXT, GRAPHER_FONT_SCALE_9_6, GRAPHER_FONT_SCALE_10_5, - GRAPHER_FONT_SCALE_11_2, GRAPHER_TIMELINE_CLASS, GRAPHER_SIDE_PANEL_CLASS, } from "../core/GrapherConstants" @@ -66,6 +65,7 @@ import { HorizontalColorLegendManager, } from "../horizontalColorLegend/HorizontalColorLegends" import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" +import { NoDataSection } from "../scatterCharts/NoDataSection" export interface SlopeChartManager extends ChartManager { isModalOpen?: boolean @@ -343,64 +343,18 @@ export class SlopeChart } @computed private get noDataSection(): JSX.Element { - const { selectedEntitiesWithoutData } = this - - const displayedEntities = selectedEntitiesWithoutData.slice(0, 5) - const numRemainingEntities = Math.max( - 0, - selectedEntitiesWithoutData.length - displayedEntities.length - ) - const bounds = new Bounds( this.legendX, - this.legendY + this.legendHeight, + this.legendY + this.legendHeight + 12, this.sidebarWidth, - this.bounds.height - this.legendHeight + this.bounds.height - this.legendHeight - 12 ) - return ( - -
- No data -
-
    - {displayedEntities.map((entityName) => ( -
  • - {entityName} -
  • - ))} -
- {numRemainingEntities > 0 && ( -
- &{" "} - {numRemainingEntities === 1 - ? "one" - : numRemainingEntities}{" "} - more -
- )} -
+ ) }