diff --git a/adminSiteClient/admin.scss b/adminSiteClient/admin.scss index 1aca429315a..c7654e66187 100644 --- a/adminSiteClient/admin.scss +++ b/adminSiteClient/admin.scss @@ -335,6 +335,10 @@ $nav-height: 45px; border-bottom: 1px solid #ccc; } } + + figure[data-grapher-src] { + line-height: 0; // remove extra space on the bottom + } } .list-group { diff --git a/packages/@ourworldindata/grapher/src/axis/Axis.ts b/packages/@ourworldindata/grapher/src/axis/Axis.ts index 5ea5b84f8a3..c57f8fdb42d 100644 --- a/packages/@ourworldindata/grapher/src/axis/Axis.ts +++ b/packages/@ourworldindata/grapher/src/axis/Axis.ts @@ -387,7 +387,7 @@ abstract class AbstractAxis { } @computed get tickFontSize(): number { - return 0.9 * this.fontSize + return 0.75 * this.fontSize } @computed protected get baseTicks(): Tickmark[] { @@ -434,7 +434,7 @@ abstract class AbstractAxis { } @computed get labelFontSize(): number { - return 0.7 * this.fontSize + return 0.75 * this.fontSize } @computed get labelTextWrap(): TextWrap | undefined { diff --git a/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx b/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx index 03d0efb3b4a..962f190863f 100644 --- a/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx +++ b/packages/@ourworldindata/grapher/src/axis/AxisViews.tsx @@ -12,7 +12,7 @@ import { } from "@ourworldindata/utils" import { VerticalAxis, HorizontalAxis, DualAxis } from "./Axis" import classNames from "classnames" -import { ScaleType } from "../core/GrapherConstants" +import { GRAPHER_DARK_TEXT, ScaleType } from "../core/GrapherConstants" const dasharrayFromFontSize = (fontSize: number): string => { const dashLength = Math.round((fontSize / 16) * 3) @@ -28,9 +28,10 @@ const SOLID_TICK_COLOR = "#999" export class VerticalAxisGridLines extends React.Component<{ verticalAxis: VerticalAxis bounds: Bounds + strokeWidth?: number }> { render(): JSX.Element { - const { bounds, verticalAxis } = this.props + const { bounds, verticalAxis, strokeWidth } = this.props const axis = verticalAxis.clone() axis.range = bounds.yRange() @@ -51,6 +52,7 @@ export class VerticalAxisGridLines extends React.Component<{ x2={bounds.right.toFixed(2)} y2={axis.place(t.value)} stroke={color} + strokeWidth={strokeWidth} strokeDasharray={ t.solid ? undefined @@ -70,13 +72,14 @@ export class VerticalAxisGridLines extends React.Component<{ export class HorizontalAxisGridLines extends React.Component<{ horizontalAxis: HorizontalAxis bounds?: Bounds + strokeWidth?: number }> { @computed get bounds(): Bounds { return this.props.bounds ?? DEFAULT_BOUNDS } render(): JSX.Element { - const { horizontalAxis } = this.props + const { horizontalAxis, strokeWidth } = this.props const { bounds } = this const axis = horizontalAxis.clone() axis.range = bounds.xRange() @@ -98,6 +101,7 @@ export class HorizontalAxisGridLines extends React.Component<{ x2={axis.place(t.value)} y2={bounds.top.toFixed(2)} stroke={color} + strokeWidth={strokeWidth} strokeDasharray={ t.solid ? undefined @@ -117,9 +121,10 @@ export class HorizontalAxisGridLines extends React.Component<{ export class HorizontalAxisZeroLine extends React.Component<{ horizontalAxis: HorizontalAxis bounds: Bounds + strokeWidth?: number }> { render(): JSX.Element { - const { bounds, horizontalAxis } = this.props + const { bounds, horizontalAxis, strokeWidth } = this.props const axis = horizontalAxis.clone() axis.range = bounds.xRange() @@ -137,6 +142,7 @@ export class HorizontalAxisZeroLine extends React.Component<{ x2={axis.place(0)} y2={bounds.top.toFixed(2)} stroke={SOLID_TICK_COLOR} + strokeWidth={strokeWidth} /> ) @@ -147,18 +153,23 @@ interface DualAxisViewProps { dualAxis: DualAxis highlightValue?: { x: number; y: number } showTickMarks?: boolean + labelColor?: string + tickColor?: string + lineWidth?: number } @observer export class DualAxisComponent extends React.Component { render(): JSX.Element { - const { dualAxis, showTickMarks } = this.props + const { dualAxis, showTickMarks, labelColor, tickColor, lineWidth } = + this.props const { bounds, horizontalAxis, verticalAxis, innerBounds } = dualAxis const verticalGridlines = verticalAxis.hideGridlines ? null : ( ) @@ -166,6 +177,7 @@ export class DualAxisComponent extends React.Component { ) @@ -173,6 +185,8 @@ export class DualAxisComponent extends React.Component { ) @@ -182,6 +196,9 @@ export class DualAxisComponent extends React.Component { axis={horizontalAxis} showTickMarks={showTickMarks} preferredAxisPosition={innerBounds.bottom} + labelColor={labelColor} + tickColor={tickColor} + tickMarkWidth={lineWidth} /> ) @@ -200,11 +217,12 @@ export class DualAxisComponent extends React.Component { export class VerticalAxisComponent extends React.Component<{ bounds: Bounds verticalAxis: VerticalAxis + labelColor?: string + tickColor?: string }> { render(): JSX.Element { - const { bounds, verticalAxis } = this.props + const { bounds, verticalAxis, labelColor, tickColor } = this.props const { tickLabels, labelTextWrap } = verticalAxis - const textColor = "#666" return ( @@ -214,6 +232,7 @@ export class VerticalAxisComponent extends React.Component<{ bounds.left, { transform: "rotate(-90)", + fill: labelColor || GRAPHER_DARK_TEXT, } )} {tickLabels.map((label, i) => { @@ -231,7 +250,7 @@ export class VerticalAxisComponent extends React.Component<{ textAnchor={textAnchorFromAlign( xAlign ?? HorizontalAlign.right )} - fill={textColor} + fill={tickColor || GRAPHER_DARK_TEXT} fontSize={verticalAxis.tickFontSize} > {formattedValue} @@ -248,6 +267,9 @@ export class HorizontalAxisComponent extends React.Component<{ axis: HorizontalAxis showTickMarks?: boolean preferredAxisPosition?: number + labelColor?: string + tickColor?: string + tickMarkWidth?: number }> { @computed get scaleType(): ScaleType { return this.props.axis.scaleType @@ -266,11 +288,17 @@ export class HorizontalAxisComponent extends React.Component<{ } render(): JSX.Element { - const { bounds, axis, showTickMarks, preferredAxisPosition } = - this.props + const { + bounds, + axis, + showTickMarks, + preferredAxisPosition, + labelColor, + tickColor, + tickMarkWidth, + } = this.props const { tickLabels, labelTextWrap: label, labelOffset, orient } = axis const horizontalAxisLabelsOnTop = orient === Position.top - const textColor = "#666" const labelYPosition = horizontalAxisLabelsOnTop ? bounds.top : bounds.bottom - (label?.height ?? 0) @@ -286,6 +314,7 @@ export class HorizontalAxisComponent extends React.Component<{ axis.place(label.value) )} color={SOLID_TICK_COLOR} + width={tickMarkWidth} /> ) : undefined @@ -297,7 +326,8 @@ export class HorizontalAxisComponent extends React.Component<{ {label && label.render( axis.rangeCenter - label.width / 2, - labelYPosition + labelYPosition, + { fill: labelColor || GRAPHER_DARK_TEXT } )} {tickMarks} {tickLabels.map((label, i) => { @@ -307,7 +337,7 @@ export class HorizontalAxisComponent extends React.Component<{ key={i} x={x} y={tickLabelYPlacement} - fill={textColor} + fill={tickColor || GRAPHER_DARK_TEXT} textAnchor={textAnchorFromAlign( xAlign ?? HorizontalAlign.center )} @@ -326,9 +356,11 @@ export class AxisTickMarks extends React.Component<{ tickMarkTopPosition: number tickMarkXPositions: number[] color: string + width?: number }> { render(): JSX.Element[] { - const { tickMarkTopPosition, tickMarkXPositions, color } = this.props + const { tickMarkTopPosition, tickMarkXPositions, color, width } = + this.props const tickSize = 5 const tickBottom = tickMarkTopPosition + tickSize return tickMarkXPositions.map((tickMarkPosition, index) => { @@ -340,6 +372,7 @@ export class AxisTickMarks extends React.Component<{ x2={tickMarkPosition} y2={tickBottom} stroke={color} + strokeWidth={width} /> ) }) diff --git a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx index f9ba057e00e..4d8b6169da4 100644 --- a/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx @@ -25,6 +25,10 @@ import { BASE_FONT_SIZE, SeriesStrategy, FacetStrategy, + GRAPHER_DARK_TEXT, + GRAPHER_AXIS_LINE_WIDTH_THICK, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, + GRAPHER_AREA_OPACITY_DEFAULT, } from "../core/GrapherConstants" import { HorizontalAxisComponent, @@ -67,6 +71,7 @@ import { import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin" import { HorizontalNumericColorLegend } from "../horizontalColorLegend/HorizontalColorLegends" import { BaseType, Selection } from "d3" +import { getElementWithHalo } from "../scatterCharts/Halos.js" const labelToTextPadding = 10 const labelToBarPadding = 5 @@ -393,6 +398,7 @@ export class DiscreteBarChart ) const { + manager, series, boundsWithoutColorLegend, yAxis, @@ -403,6 +409,10 @@ export class DiscreteBarChart let yOffset = innerBounds.top + barHeight / 2 + barSpacing / 2 + const axisLineWidth = manager.isStaticAndSmall + ? GRAPHER_AXIS_LINE_WIDTH_THICK + : GRAPHER_AXIS_LINE_WIDTH_DEFAULT + return ( )} {series.map((series) => { // Todo: add a "placedSeries" getter to get the transformed series, then just loop over the placedSeries and render a bar for each @@ -471,26 +484,31 @@ export class DiscreteBarChart width={barWidth} height={barHeight} fill={barColor} - opacity={0.85} + opacity={GRAPHER_AREA_OPACITY_DEFAULT} style={{ transition: "height 200ms ease" }} /> - - {label.valueString} - {label.timeString} - + {getElementWithHalo( + series.seriesName + "-label", + + {label.valueString} + + {label.timeString} + + + )} ) @@ -502,6 +520,7 @@ export class DiscreteBarChart )} diff --git a/packages/@ourworldindata/grapher/src/chart/ChartManager.ts b/packages/@ourworldindata/grapher/src/chart/ChartManager.ts index 7bf2ec354e2..5085961109a 100644 --- a/packages/@ourworldindata/grapher/src/chart/ChartManager.ts +++ b/packages/@ourworldindata/grapher/src/chart/ChartManager.ts @@ -88,4 +88,8 @@ export interface ChartManager { disableIntroAnimation?: boolean missingDataStrategy?: MissingDataStrategy + + isNarrow?: boolean + isStaticAndSmall?: boolean + secondaryColorInStaticCharts?: string } diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index d487025ac11..5084072f893 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -99,6 +99,7 @@ import { GRAPHER_DARK_TEXT, GrapherStaticFormat, STATIC_EXPORT_DETAIL_SPACING, + GRAPHER_LIGHT_TEXT, } from "../core/GrapherConstants" import Cookies from "js-cookie" import { @@ -979,17 +980,6 @@ export class Grapher this.selection.setSelectedEntities(this.selectedEntityNames) } - @observable private _baseFontSize = BASE_FONT_SIZE - - @computed get baseFontSize(): number { - if (this.isExportingToSvgOrPng) return Math.max(this._baseFontSize, 18) - return this._baseFontSize - } - - set baseFontSize(val: number) { - this._baseFontSize = val - } - // Ready to go iff we have retrieved data for every variable associated with the chart @computed get isReady(): boolean { return this.whatAreWeWaitingFor === "" @@ -1286,15 +1276,21 @@ export class Grapher text += `${plainText.join(" ")}` } + + // can't use the computed property here because Grapher might not currently be in static mode + const baseFontSize = this.areStaticBoundsSmall + ? this.computeBaseFontSizeFromHeight(this.staticBounds) + : 18 + return new MarkdownTextWrap({ text, - fontSize: 12, + fontSize: (11 / BASE_FONT_SIZE) * baseFontSize, // leave room for padding on the left and right maxWidth: this.staticBounds.width - 2 * this.framePaddingHorizontal, lineHeight: 1.2, style: { - fill: GRAPHER_DARK_TEXT, + fill: this.secondaryColorInStaticCharts, }, }) }) @@ -2611,21 +2607,40 @@ export class Grapher } } + @observable private _baseFontSize = BASE_FONT_SIZE + + @computed get baseFontSize(): number { + if (this.isStaticAndSmall) { + return this.computeBaseFontSizeFromHeight(this.staticBounds) + } + if (this.isStatic) return 18 + return this._baseFontSize + } + + set baseFontSize(val: number) { + this._baseFontSize = val + } + // the header and footer don't rely on the base font size unless explicitly specified @computed get useBaseFontSize(): boolean { - return this.props.baseFontSize !== undefined + return this.props.baseFontSize !== undefined || this.isStatic } - computeBaseFontSize(): number { - const { renderWidth } = this - if (renderWidth <= 400) return 14 - else if (renderWidth < 1080) return 16 - else if (renderWidth >= 1080) return 18 + private computeBaseFontSizeFromHeight(bounds: Bounds): number { + const squareBounds = this.getStaticBounds(GrapherStaticFormat.square) + const factor = squareBounds.height / 21 + return Math.max(10, bounds.height / factor) + } + + private computeBaseFontSizeFromWidth(bounds: Bounds): number { + if (bounds.width <= 400) return 14 + else if (bounds.width < 1080) return 16 + else if (bounds.width >= 1080) return 18 else return 16 } @action.bound private setBaseFontSize(): void { - this.baseFontSize = this.computeBaseFontSize() + this.baseFontSize = this.computeBaseFontSizeFromWidth(this.tabBounds) } @computed get fontSize(): number { @@ -2644,30 +2659,46 @@ export class Grapher } @computed get isNarrow(): boolean { - if (this.isExportingToSvgOrPng) return false + if (this.isStatic) return false return this.renderWidth <= 400 } // SemiNarrow charts shorten their button labels to fit within the controls row @computed get isSemiNarrow(): boolean { - if (this.isExportingToSvgOrPng) return false + if (this.isStatic) return false return this.renderWidth <= 550 } // Small charts are rendered into 6 or 7 columns in a 12-column grid layout // (e.g. side-by-side charts or charts in the All Charts block) @computed get isSmall(): boolean { - if (this.isExportingToSvgOrPng) return false + if (this.isStatic) return false return this.renderWidth <= 740 } // Medium charts are rendered into 8 columns in a 12-column grid layout // (e.g. stand-alone charts in the main text of an article) @computed get isMedium(): boolean { - if (this.isExportingToSvgOrPng) return false + if (this.isStatic) return false return this.renderWidth <= 840 } + @computed get isStaticAndSmall(): boolean { + if (!this.isStatic) return false + return this.areStaticBoundsSmall + } + + @computed get areStaticBoundsSmall(): boolean { + const { idealBounds, staticBounds } = this + const idealPixelCount = idealBounds.width * idealBounds.height + const staticPixelCount = staticBounds.width * staticBounds.height + return staticPixelCount < 0.66 * idealPixelCount + } + + @computed get secondaryColorInStaticCharts(): string { + return this.isStaticAndSmall ? GRAPHER_LIGHT_TEXT : GRAPHER_DARK_TEXT + } + // Binds chart properties to global window title and URL. This should only // ever be invoked from top-level JavaScript. private bindToWindow(): void { diff --git a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts index abc1cd0845d..99b04046d83 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts @@ -37,6 +37,12 @@ export const DEFAULT_GRAPHER_FRAME_PADDING = 16 export const STATIC_EXPORT_DETAIL_SPACING = 8 export const GRAPHER_DARK_TEXT = "#5b5b5b" +export const GRAPHER_LIGHT_TEXT = "#858585" + +export const GRAPHER_AXIS_LINE_WIDTH_DEFAULT = 1 +export const GRAPHER_AXIS_LINE_WIDTH_THICK = 2 + +export const GRAPHER_AREA_OPACITY_DEFAULT = 0.8 export enum CookieKey { isAdmin = "isAdmin", diff --git a/packages/@ourworldindata/grapher/src/footer/Footer.tsx b/packages/@ourworldindata/grapher/src/footer/Footer.tsx index 3f82052c93a..fc37a176c98 100644 --- a/packages/@ourworldindata/grapher/src/footer/Footer.tsx +++ b/packages/@ourworldindata/grapher/src/footer/Footer.tsx @@ -189,20 +189,20 @@ export class Footer< ) } - @computed private get lineHeight(): number { + @computed protected get lineHeight(): number { return this.manager.isSmall ? 1.1 : 1.2 } @computed protected get fontSize(): number { if (this.useBaseFontSize) { - return (12 / BASE_FONT_SIZE) * this.baseFontSize + return (11 / BASE_FONT_SIZE) * this.baseFontSize } return this.manager.isMedium ? 11 : 12 } @computed protected get sourcesFontSize(): number { if (this.useBaseFontSize) { - return (13 / BASE_FONT_SIZE) * this.baseFontSize + return (12 / BASE_FONT_SIZE) * this.baseFontSize } return this.manager.isSmall ? 12 : 13 } @@ -642,6 +642,10 @@ export class StaticFooter extends Footer { // eslint-disable-next-line @typescript-eslint/no-empty-function componentWillUnmount(): void {} + @computed private get textColor(): string { + return this.manager.secondaryColorInStaticCharts ?? GRAPHER_DARK_TEXT + } + @computed protected get showLicenseNextToSources(): boolean { return ( this.maxWidth - this.sources.width - HORIZONTAL_PADDING > @@ -672,8 +676,12 @@ export class StaticFooter extends Footer { } @computed protected get licenseAndOriginUrlText(): string { - const { finalUrl, finalUrlText, licenseText, licenseUrl } = this - const linkStyle = `fill: ${GRAPHER_DARK_TEXT}; text-decoration: underline;` + const { finalUrl, finalUrlText, licenseText, licenseUrl, textColor } = + this + const textDecoration = this.manager.isStaticAndSmall + ? "none" + : "underline" + const linkStyle = `fill: ${textColor}; text-decoration: ${textDecoration};` const licenseSvg = `${licenseText}` if (!finalUrlText) return licenseSvg const originUrlSvg = `${finalUrlText}` @@ -686,8 +694,34 @@ export class StaticFooter extends Footer { @computed protected get fontSize(): number { if (this.useBaseFontSize) { - return (13 / BASE_FONT_SIZE) * this.baseFontSize + let fontSize = (12 / BASE_FONT_SIZE) * this.baseFontSize + + // for small charts, reduce the font size if the footer text is long + if (this.manager.isStaticAndSmall) { + const sources = new MarkdownTextWrap({ + text: this.sourcesText, + maxWidth: this.sourcesMaxWidth, + lineHeight: this.lineHeight, + fontSize, + }) + const note = new MarkdownTextWrap({ + text: this.markdownNoteText, + maxWidth: this.noteMaxWidth, + lineHeight: this.lineHeight, + fontSize, + }) + + const lineCount = + sources.svgLines.length + + (this.showNote ? note.svgLines.length : 0) + if (lineCount > 2) { + fontSize = (10 / BASE_FONT_SIZE) * this.baseFontSize + } + } + + return fontSize } + return 13 } @@ -724,11 +758,17 @@ export class StaticFooter extends Footer { licenseAndOriginUrl, showLicenseNextToSources, maxWidth, + textColor, } = this const { targetX, targetY } = this.props return ( - + {sources.renderSVG(targetX, targetY)} {this.showNote && note.renderSVG( diff --git a/packages/@ourworldindata/grapher/src/footer/FooterManager.ts b/packages/@ourworldindata/grapher/src/footer/FooterManager.ts index d09df17cc1b..fe0a0db3091 100644 --- a/packages/@ourworldindata/grapher/src/footer/FooterManager.ts +++ b/packages/@ourworldindata/grapher/src/footer/FooterManager.ts @@ -21,4 +21,6 @@ export interface FooterManager extends TooltipManager, ActionButtonsManager { isEmbeddedInADataPage?: boolean hideNote?: boolean hideOriginUrl?: boolean + secondaryColorInStaticCharts?: string + isStaticAndSmall?: boolean } diff --git a/packages/@ourworldindata/grapher/src/header/Header.tsx b/packages/@ourworldindata/grapher/src/header/Header.tsx index f927e681243..8bcf9005e5e 100644 --- a/packages/@ourworldindata/grapher/src/header/Header.tsx +++ b/packages/@ourworldindata/grapher/src/header/Header.tsx @@ -71,6 +71,7 @@ export class Header< return new Logo({ logo: manager.logo as any, isLink: !!manager.shouldLinkToOwid, + // if it's the OWID logo, use the small version; otherwise, decrease the size heightScale: manager.isSmall && !isOwidLogo ? 0.775 : 1, useSmallVersion: manager.isSmall && isOwidLogo, }) @@ -86,17 +87,21 @@ export class Header< @computed get titleFontSize(): number { if (this.useBaseFontSize) { - return (24 / BASE_FONT_SIZE) * this.baseFontSize + return (22 / BASE_FONT_SIZE) * this.baseFontSize } return this.manager.isNarrow ? 18 : this.manager.isMedium ? 20 : 24 } + @computed get titleLineHeight(): number { + return this.manager.isSmall ? 1.1 : 1.2 + } + @computed get title(): TextWrap { const { logoWidth } = this return new TextWrap({ maxWidth: this.maxWidth - logoWidth - 24, fontWeight: 600, - lineHeight: this.manager.isSmall ? 1.1 : 1.2, + lineHeight: this.titleLineHeight, fontSize: this.titleFontSize, text: this.titleText, }) @@ -115,18 +120,21 @@ export class Header< @computed get subtitleFontSize(): number { if (this.useBaseFontSize) { - return (14 / BASE_FONT_SIZE) * this.baseFontSize + return (13 / BASE_FONT_SIZE) * this.baseFontSize } return this.manager.isSmall ? 12 : this.manager.isMedium ? 13 : 14 } + @computed get subtitleLineHeight(): number { + return this.manager.isMedium ? 1.2 : 1.28571 + } + @computed get subtitle(): MarkdownTextWrap { - const lineHeight = this.manager.isMedium ? 1.2 : 1.28571 return new MarkdownTextWrap({ maxWidth: this.subtitleWidth, fontSize: this.subtitleFontSize, text: this.subtitleText, - lineHeight, + lineHeight: this.subtitleLineHeight, detailsOrderedByReference: this.manager .shouldIncludeDetailsInStaticExport ? this.manager.detailsOrderedByReference @@ -238,12 +246,12 @@ export class StaticHeader extends Header { maxWidth: this.maxWidth - logoWidth - 24, fontSize, fontWeight: 500, - lineHeight: 1.2, + lineHeight: this.manager.isStaticAndSmall ? 1.1 : 1.2, }) // try to fit the title into a single line if possible-- but not if it would make the text too small const initialFontSize = this.useBaseFontSize - ? (24 / BASE_FONT_SIZE) * this.baseFontSize + ? (22 / BASE_FONT_SIZE) * this.baseFontSize : 24 let title = makeTitle(initialFontSize) @@ -268,6 +276,10 @@ export class StaticHeader extends Header { return title } + @computed get subtitleLineHeight(): number { + return 1.2 + } + render(): JSX.Element { const { targetX: x, targetY: y } = this.props const { title, logo, subtitle, manager, maxWidth } = this @@ -299,7 +311,7 @@ export class StaticHeader extends Header { ? title.height + this.subtitleMarginTop : 0), { - fill: GRAPHER_DARK_TEXT, + fill: manager.secondaryColorInStaticCharts, } )} diff --git a/packages/@ourworldindata/grapher/src/header/HeaderManager.ts b/packages/@ourworldindata/grapher/src/header/HeaderManager.ts index 198f14b4c2b..cf7d733252a 100644 --- a/packages/@ourworldindata/grapher/src/header/HeaderManager.ts +++ b/packages/@ourworldindata/grapher/src/header/HeaderManager.ts @@ -22,4 +22,6 @@ export interface HeaderManager { fontSize?: number hideTitle?: boolean hideSubtitle?: boolean + secondaryColorInStaticCharts?: string + isStaticAndSmall?: boolean } diff --git a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx index 93910645319..0dd6f609abe 100644 --- a/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx +++ b/packages/@ourworldindata/grapher/src/horizontalColorLegend/HorizontalColorLegends.tsx @@ -357,7 +357,7 @@ export class HorizontalNumericColorLegend extends HorizontalColorLegend { } @computed private get legendTitleFontSize(): number { - return this.fontSize * 0.85 + return this.fontSize * 0.875 } @computed private get legendTitle(): TextWrap | undefined { diff --git a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx index 008967545ad..ab15524f53f 100644 --- a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx +++ b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx @@ -48,6 +48,8 @@ import { SeriesStrategy, FacetStrategy, MissingDataStrategy, + GRAPHER_AXIS_LINE_WIDTH_THICK, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, } from "../core/GrapherConstants" import { ColorSchemes } from "../color/ColorSchemes" import { AxisConfig, FontSizeManager } from "../axis/AxisConfig" @@ -423,12 +425,11 @@ export class LineChart } @computed private get lineStrokeWidth(): number { - return ( - this.manager.lineStrokeWidth ?? - (this.hasColorScale - ? VARIABLE_COLOR_STROKE_WIDTH - : DEFAULT_STROKE_WIDTH) - ) + if (this.manager.lineStrokeWidth) return this.manager.lineStrokeWidth + const factor = this.manager.isStaticAndSmall ? 2 : 1 + return this.hasColorScale + ? factor * VARIABLE_COLOR_STROKE_WIDTH + : factor * DEFAULT_STROKE_WIDTH } @computed private get lineOutlineWidth(): number { @@ -438,6 +439,8 @@ export class LineChart } @computed private get markerRadius(): number { + // hide markers but don't remove them from the DOM + if (this.manager.isStaticAndSmall) return 0 return this.hasColorScale ? VARIABLE_COLOR_MARKER_RADIUS : DEFAULT_MARKER_RADIUS @@ -779,7 +782,16 @@ export class LineChart {this.hasColorLegend && ( )} - + {comparisonLines.map((line, index) => ( @observer -class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { +class ChoroplethMap extends React.Component<{ + manager: ChoroplethMapManager +}> { base: React.RefObject = React.createRef() @computed private get uid(): number { @@ -640,6 +646,10 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { return this.props.manager } + @computed private get strokeWidth(): number { + return this.manager.strokeWidth ?? 1 + } + @computed.struct private get bounds(): Bounds { return this.manager.choroplethMapBounds } @@ -843,8 +853,10 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { featuresWithData, } = this const focusStrokeColor = "#111" - const focusStrokeWidth = 1.5 - const selectedStrokeWidth = 1 + const defaultStrokeWidth = this.strokeWidth * 0.3 + const focusStrokeWidth = this.strokeWidth * 1.5 + const selectedStrokeWidth = this.strokeWidth * 1 + const patternStrokeWidth = this.strokeWidth * 0.7 const blurFillOpacity = 0.2 const blurStrokeOpacity = 0.5 @@ -883,7 +895,9 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { @@ -912,7 +926,7 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { @@ -935,7 +949,9 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { key={feature.id} d={feature.path} strokeWidth={ - (isFocus ? focusStrokeWidth : 0.3) / + (isFocus + ? focusStrokeWidth + : defaultStrokeWidth) / viewportScale } stroke={stroke} @@ -991,7 +1007,8 @@ class ChoroplethMap extends React.Component<{ manager: ChoroplethMapManager }> { ? focusStrokeWidth : showSelectedStyle ? selectedStrokeWidth - : 0.3) / viewportScale + : defaultStrokeWidth) / + viewportScale } stroke={stroke} strokeOpacity={strokeOpacity} diff --git a/packages/@ourworldindata/grapher/src/mapCharts/MapChartConstants.ts b/packages/@ourworldindata/grapher/src/mapCharts/MapChartConstants.ts index d22934776e7..e415f0be860 100644 --- a/packages/@ourworldindata/grapher/src/mapCharts/MapChartConstants.ts +++ b/packages/@ourworldindata/grapher/src/mapCharts/MapChartConstants.ts @@ -41,6 +41,7 @@ export interface ChoroplethMapManager { onClick: (d: GeoFeature, ev: React.MouseEvent) => void onMapMouseOver: (d: GeoFeature) => void onMapMouseLeave: () => void + strokeWidth?: number } export interface RenderFeature { diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.test.ts b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.test.ts index a393e9dc309..c2e6532954c 100755 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.test.ts +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.test.ts @@ -15,9 +15,6 @@ import { } from "@ourworldindata/core-table" import { ScatterPlotManager, - SCATTER_LABEL_DEFAULT_FONT_SIZE, - SCATTER_LABEL_MAX_FONT_SIZE, - SCATTER_LABEL_MIN_FONT_SIZE, SCATTER_POINT_DEFAULT_RADIUS, SCATTER_POINT_MAX_RADIUS, SCATTER_POINT_MIN_RADIUS, @@ -1022,6 +1019,7 @@ describe("correct bubble sizes", () => { dualAxis: chart["dualAxis"], sizeScale: chart["sizeScale"], fontScale: chart["fontScale"], + baseFontSize: chart["fontSize"], focusedSeriesNames: chart["focusedEntityNames"], hoveredSeriesNames: chart["hoveredSeriesNames"], onMouseEnter: chart["onScatterMouseEnter"], @@ -1037,24 +1035,16 @@ describe("correct bubble sizes", () => { expect(sortedRenderSeries[0].seriesName).toEqual("SWE") expect(sortedRenderSeries[0].size).toEqual(SCATTER_POINT_MIN_RADIUS) - expect(sortedRenderSeries[0].fontSize).toEqual( - SCATTER_LABEL_MIN_FONT_SIZE - ) + expect(sortedRenderSeries[0].fontSize).toEqual(10) expect(sortedRenderSeries[1].seriesName).toEqual("UK") expect(sortedRenderSeries[1].size).toEqual(SCATTER_POINT_MIN_RADIUS) - expect(sortedRenderSeries[1].fontSize).toEqual( - SCATTER_LABEL_MIN_FONT_SIZE - ) + expect(sortedRenderSeries[1].fontSize).toEqual(10) expect(sortedRenderSeries[2].seriesName).toEqual("USA") expect(sortedRenderSeries[2].size).toEqual(SCATTER_POINT_MAX_RADIUS) - expect(sortedRenderSeries[2].fontSize).toEqual( - SCATTER_LABEL_MAX_FONT_SIZE - ) + expect(sortedRenderSeries[2].fontSize).toEqual(13) expect(sortedRenderSeries[3].seriesName).toEqual("ZZZ") expect(sortedRenderSeries[3].size).toEqual(SCATTER_POINT_MIN_RADIUS) - expect(sortedRenderSeries[3].fontSize).toEqual( - SCATTER_LABEL_MIN_FONT_SIZE - ) + expect(sortedRenderSeries[3].fontSize).toEqual(10) }) it("without column", () => { @@ -1095,6 +1085,7 @@ describe("correct bubble sizes", () => { dualAxis: chart["dualAxis"], sizeScale: chart["sizeScale"], fontScale: chart["fontScale"], + baseFontSize: chart["fontSize"], focusedSeriesNames: chart["focusedEntityNames"], hoveredSeriesNames: chart["hoveredSeriesNames"], onMouseEnter: chart["onScatterMouseEnter"], @@ -1110,13 +1101,9 @@ describe("correct bubble sizes", () => { expect(sortedRenderSeries[0].seriesName).toEqual("SWE") expect(sortedRenderSeries[0].size).toEqual(SCATTER_POINT_DEFAULT_RADIUS) - expect(sortedRenderSeries[0].fontSize).toEqual( - SCATTER_LABEL_DEFAULT_FONT_SIZE - ) + expect(sortedRenderSeries[0].fontSize).toEqual(10.5) expect(sortedRenderSeries[1].seriesName).toEqual("UK") expect(sortedRenderSeries[1].size).toEqual(SCATTER_POINT_DEFAULT_RADIUS) - expect(sortedRenderSeries[1].fontSize).toEqual( - SCATTER_LABEL_DEFAULT_FONT_SIZE - ) + expect(sortedRenderSeries[1].fontSize).toEqual(10.5) }) }) diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx index 95c0c44231b..5ae4207d276 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx @@ -41,6 +41,8 @@ import { EntitySelectionMode, ScatterPointLabelStrategy, SeriesName, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_THICK, } from "../core/GrapherConstants" import { Color, @@ -73,9 +75,9 @@ import { ChartInterface } from "../chart/ChartInterface" import { ScatterPlotManager, ScatterSeries, - SCATTER_LABEL_DEFAULT_FONT_SIZE, - SCATTER_LABEL_MAX_FONT_SIZE, - SCATTER_LABEL_MIN_FONT_SIZE, + SCATTER_LABEL_DEFAULT_FONT_SIZE_FACTOR, + SCATTER_LABEL_MAX_FONT_SIZE_FACTOR, + SCATTER_LABEL_MIN_FONT_SIZE_FACTOR, SCATTER_LINE_DEFAULT_WIDTH, SCATTER_LINE_MAX_WIDTH, SCATTER_POINT_DEFAULT_RADIUS, @@ -655,6 +657,7 @@ export class ScatterPlotChart } sizeScale={this.sizeScale} fontScale={this.fontScale} + baseFontSize={this.fontSize} focusedSeriesNames={this.focusedEntityNames} hoveredSeriesNames={this.hoveredSeriesNames} tooltipSeriesName={this.tooltipSeries?.seriesName} @@ -713,16 +716,17 @@ export class ScatterPlotChart } @computed get fontScale(): ScaleLinear { + const defaultFontSize = + SCATTER_LABEL_DEFAULT_FONT_SIZE_FACTOR * this.fontSize + const minFontSize = SCATTER_LABEL_MIN_FONT_SIZE_FACTOR * this.fontSize + const maxFontSize = SCATTER_LABEL_MAX_FONT_SIZE_FACTOR * this.fontSize return scaleSqrt() .domain(this.sizeDomain) .range( this.sizeColumn.isMissing ? // if the size column is missing, we want all labels to have the same font size - [ - SCATTER_LABEL_DEFAULT_FONT_SIZE, - SCATTER_LABEL_DEFAULT_FONT_SIZE, - ] - : [SCATTER_LABEL_MIN_FONT_SIZE, SCATTER_LABEL_MAX_FONT_SIZE] + [defaultFontSize, defaultFontSize] + : [minFontSize, maxFontSize] ) } @@ -751,6 +755,7 @@ export class ScatterPlotChart ) const { + manager, bounds, dualAxis, arrowLegend, @@ -770,7 +775,16 @@ export class ScatterPlotChart return ( - + {comparisonLines && comparisonLines.map((line, i) => ( fontScale: ScaleLinear + baseFontSize: number onMouseEnter: (series: ScatterSeries) => void onMouseLeave: () => void onClick: () => void diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx index b24c5122087..bb9701c9a9c 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPointsWithLabels.tsx @@ -29,7 +29,7 @@ import { SCATTER_POINT_MIN_RADIUS, SCATTER_POINT_HOVER_TARGET_RANGE, ScatterRenderPoint, - SCATTER_LABEL_MIN_FONT_SIZE, + SCATTER_LABEL_MIN_FONT_SIZE_FACTOR, } from "./ScatterPlotChartConstants" import { ScatterLine, ScatterPoint } from "./ScatterPoints" import { @@ -127,7 +127,10 @@ export class ScatterPointsWithLabels extends React.Component { let priority = label.fontSize @@ -20,18 +21,19 @@ export const labelPriority = (label: ScatterLabel): number => { export const makeStartLabel = ( series: ScatterRenderSeries, isSubtleForeground: boolean, - hideConnectedScatterLines: boolean + hideConnectedScatterLines: boolean, + baseFontSize: number ): ScatterLabel | undefined => { // No room to label the year if it's a single point if (!series.isForeground || series.points.length <= 1) return undefined const fontSize = hideConnectedScatterLines - ? SCATTER_LABEL_FONT_SIZE_WHEN_HIDDEN_LINES + ? SCATTER_LABEL_FONT_SIZE_FACTOR_WHEN_HIDDEN_LINES * baseFontSize : series.isForeground ? isSubtleForeground - ? 8 - : 9 - : 7 + ? (8 / BASE_FONT_SIZE) * baseFontSize + : (9 / BASE_FONT_SIZE) * baseFontSize + : (7 / BASE_FONT_SIZE) * baseFontSize const firstValue = series.points[0] const nextValue = series.points[1] const nextSegment = nextValue.position.subtract(firstValue.position) @@ -73,7 +75,8 @@ export const makeStartLabel = ( export const makeMidLabels = ( series: ScatterRenderSeries, isSubtleForeground: boolean, - hideConnectedScatterLines: boolean + hideConnectedScatterLines: boolean, + baseFontSize: number ): ScatterLabel[] => { if ( !series.isForeground || @@ -83,12 +86,12 @@ export const makeMidLabels = ( return [] const fontSize = hideConnectedScatterLines - ? SCATTER_LABEL_FONT_SIZE_WHEN_HIDDEN_LINES + ? SCATTER_LABEL_FONT_SIZE_FACTOR_WHEN_HIDDEN_LINES * baseFontSize : series.isForeground ? isSubtleForeground - ? 8 - : 9 - : 7 + ? (8 / BASE_FONT_SIZE) * baseFontSize + : (9 / BASE_FONT_SIZE) * baseFontSize + : (7 / BASE_FONT_SIZE) * baseFontSize const fontWeight = 400 // label all the way to the end for the tooltip series, otherwise to n-1 @@ -158,12 +161,13 @@ export const makeMidLabels = ( export const makeEndLabel = ( series: ScatterRenderSeries, isSubtleForeground: boolean, - hideConnectedScatterLines: boolean + hideConnectedScatterLines: boolean, + baseFontSize: number ): ScatterLabel => { const lastValue = last(series.points) as ScatterRenderPoint const lastPos = lastValue.position const fontSize = hideConnectedScatterLines - ? SCATTER_LABEL_FONT_SIZE_WHEN_HIDDEN_LINES + ? SCATTER_LABEL_FONT_SIZE_FACTOR_WHEN_HIDDEN_LINES * baseFontSize : series.fontSize * (series.isForeground ? (isSubtleForeground ? 1.2 : 1.3) : 1.1) const fontWeight = diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx index 0f70873f6b3..79b9839c650 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx @@ -30,6 +30,7 @@ import { ScaleType, EntitySelectionMode, SeriesName, + GRAPHER_DARK_TEXT, } from "../core/GrapherConstants" import { ChartInterface } from "../chart/ChartInterface" import { ChartManager } from "../chart/ChartManager" @@ -495,21 +496,21 @@ class SlopeChartAxis extends React.Component { } render() { - const { bounds, scale, orient, column } = this.props + const { bounds, scale, orient, column, fontSize } = this.props const { ticks } = this - const textColor = "#666" return ( - + {ticks.map((tick, i) => { return ( {column.formatValueShort(tick)} @@ -691,7 +692,7 @@ class LabelledSlopes } @computed private get isPortrait() { - return this.bounds.width < 400 + return this.manager.isNarrow || this.manager.isStaticAndSmall } @computed private get allValues() { @@ -734,6 +735,7 @@ class LabelledSlopes } @computed get sizeScale(): ScaleLinear { + const factor = this.manager.isStaticAndSmall ? 1.2 : 1 return scaleLinear() .domain( extent(this.props.seriesArr.map((series) => series.size)) as [ @@ -741,7 +743,7 @@ class LabelledSlopes number, ] ) - .range([1, 4]) + .range([factor, 4 * factor]) } @computed get yScaleConstructor(): any { @@ -1069,6 +1071,8 @@ class LabelledSlopes const { x1, x2 } = slopeData[0] const [y1, y2] = yScale.range() + const tickFontSize = 0.75 * fontSize + return ( )} {!isPortrait && ( @@ -1120,6 +1125,7 @@ class LabelledSlopes scale={yScale} scaleType={yScaleType} bounds={bounds} + fontSize={tickFontSize} /> )} @@ -1128,7 +1134,7 @@ class LabelledSlopes x={x1} y={y1 + 10} textAnchor="middle" - fill="#666" + fill={GRAPHER_DARK_TEXT} fontSize={0.875 * fontSize} > {xDomain[0].toString()} @@ -1137,7 +1143,7 @@ class LabelledSlopes x={x2} y={y1 + 10} textAnchor="middle" - fill="#666" + fill={GRAPHER_DARK_TEXT} fontSize={0.875 * fontSize} > {xDomain[1].toString()} diff --git a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChartConstants.ts b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChartConstants.ts index 6fc475dce50..d2fe9a27e51 100644 --- a/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChartConstants.ts +++ b/packages/@ourworldindata/grapher/src/slopeCharts/SlopeChartConstants.ts @@ -57,4 +57,5 @@ export interface SlopeAxisProps { column: CoreColumn scale: any scaleType: ScaleType + fontSize: number } diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx index ef363ad3e2f..5165c68531a 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx @@ -25,6 +25,8 @@ import { observer } from "mobx-react" import { BASE_FONT_SIZE, EntitySelectionMode, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_THICK, Patterns, } from "../core/GrapherConstants" import { DualAxisComponent } from "../axis/AxisViews" @@ -905,6 +907,7 @@ export class MarimekkoChart ) const { + manager, bounds, dualAxis, tooltipItem, @@ -956,7 +959,16 @@ export class MarimekkoChart opacity={0} fill="rgba(255,255,255,0)" /> - + {this.renderBars()} {target && ( @@ -1007,6 +1019,7 @@ export class MarimekkoChart labelLines, placedItems, tooltipState, + fontSize, } = this const selectionSet = this.selectionArray.selectedSet const labelYOffset = 0 @@ -1052,7 +1065,7 @@ export class MarimekkoChart fontWeight={700} fill="#666" opacity={1} - fontSize="0.8em" + fontSize={0.75 * fontSize} textAnchor="middle" dominantBaseline="middle" style={{ pointerEvents: "none" }} @@ -1157,13 +1170,13 @@ export class MarimekkoChart private static labelCandidateFromItem( item: EntityWithSize, - baseFontSize: number, + fontSize: number, isSelected: boolean ): LabelCandidate { return { item: item, bounds: Bounds.forText(item.entityName, { - fontSize: 0.7 * baseFontSize, + fontSize, }), isPicked: isSelected, isSelected, @@ -1204,7 +1217,6 @@ export class MarimekkoChart yColumnsAtLastTimePoint, selectedItems, xRange, - baseFontSize, sortConfig, paddingInPixels, } = this @@ -1240,7 +1252,7 @@ export class MarimekkoChart : 1, ySortValue: ySizeMap.get(row.entityName), }, - baseFontSize, + this.entityLabelFontSize, selectedItemsSet.has(row.entityName) ) ) @@ -1576,6 +1588,10 @@ export class MarimekkoChart return Math.max(this.fontSize, rotatedLabelWidth) } + @computed private get entityLabelFontSize(): number { + return 0.75 * this.fontSize + } + @computed private get labels(): LabelCandidateWithElement[] { const { labelAngleInDegrees, series, domainColorForEntityMap } = this return this.pickedLabelCandidates.map((candidate) => { @@ -1598,7 +1614,7 @@ export class MarimekkoChart fill={color} transform={`rotate(${labelAngleInDegrees}, 0, 0)`} opacity={1} - fontSize="0.7em" + fontSize={this.entityLabelFontSize} textAnchor="right" dominantBaseline="middle" onMouseOver={(): void => diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedAreaChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedAreaChart.tsx index aa4d05cef22..cc944a80aca 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedAreaChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedAreaChart.tsx @@ -14,7 +14,12 @@ import { lastOfNonEmptyArray, } from "@ourworldindata/utils" import { computed, action, observable } from "mobx" -import { SeriesName } from "../core/GrapherConstants" +import { + GRAPHER_AREA_OPACITY_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_THICK, + SeriesName, +} from "../core/GrapherConstants" import { observer } from "mobx-react" import { DualAxisComponent } from "../axis/AxisViews" import { DualAxis } from "../axis/Axis" @@ -152,9 +157,9 @@ class Areas extends React.Component { } const points = [...placedPoints, ...reverse(clone(prevPoints))] const opacity = !hoveredAreaName - ? 0.7 // normal opacity + ? GRAPHER_AREA_OPACITY_DEFAULT // normal opacity : hoveredAreaName === series.seriesName - ? 0.7 // hovered + ? GRAPHER_AREA_OPACITY_DEFAULT // hovered : 0.2 // non-hovered return ( @@ -500,7 +505,7 @@ export class StackedAreaChart /> ) - const { bounds, dualAxis, renderUid, series } = this + const { manager, bounds, dualAxis, renderUid, series } = this const { target } = this.tooltipState const showLegend = !this.manager.hideLegend @@ -528,7 +533,16 @@ export class StackedAreaChart whole charting area, including the axis, the entity labels, and the whitespace next to them. We need these to be able to show the tooltip for the first/last year even if the mouse is outside the charting area. */} - + {showLegend && } tick.bounds.centerX )} - color={textColor} + color="#666" + width={axisLineWidth} /> @@ -463,7 +475,7 @@ export class StackedBarChart key={i} x={tick.bounds.x} y={tick.bounds.y} - fill={textColor} + fill={GRAPHER_DARK_TEXT} fontSize={this.tickFontSize} onMouseOver={(): void => { this.onLabelMouseOver(tick) @@ -483,7 +495,7 @@ export class StackedBarChart ) const opacity = isLegendHovered || this.hoverKeys.length === 0 - ? 0.8 + ? GRAPHER_AREA_OPACITY_DEFAULT : 0.2 return ( diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx index 3f24373f854..4606d9940ce 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx @@ -24,6 +24,9 @@ import { observer } from "mobx-react" import { BASE_FONT_SIZE, FacetStrategy, + GRAPHER_AREA_OPACITY_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_DEFAULT, + GRAPHER_AXIS_LINE_WIDTH_THICK, SeriesName, } from "../core/GrapherConstants" import { @@ -57,6 +60,7 @@ import { HashMap, NodeGroup } from "react-move" import { easeQuadOut } from "d3-ease" import { bind } from "decko" import { CategoricalColorAssigner } from "../color/CategoricalColorAssigner.js" +import { getElementWithHalo } from "../scatterCharts/Halos.js" const labelToBarPadding = 5 @@ -488,7 +492,7 @@ export class StackedDiscreteBarChart /> ) - const { bounds, yAxis, innerBounds } = this + const { manager, bounds, yAxis, innerBounds } = this const chartContext: StackedBarChartContext = { yAxis, @@ -553,21 +557,27 @@ export class StackedDiscreteBarChart onMouseLeave={this.onEntityMouseLeave} /> ))} - {this.showTotalValueLabel && ( - - {totalLabel} - - )} + {this.showTotalValueLabel && + getElementWithHalo( + label + "-value-label", + + {totalLabel} + + )} ) } + const axisLineWidth = manager.isStaticAndSmall + ? GRAPHER_AXIS_LINE_WIDTH_THICK + : GRAPHER_AXIS_LINE_WIDTH_DEFAULT + return ( )} {this.showLegend && ( @@ -609,6 +622,7 @@ export class StackedDiscreteBarChart {this.Tooltip} @@ -637,7 +651,7 @@ export class StackedDiscreteBarChart yAxis.place(bar.point.value) - yAxis.place(chartContext.x0) const barLabel = formatValueForLabel(bar.point.value) - const labelFontSize = 0.7 * chartContext.baseFontSize + const labelFontSize = 0.75 * chartContext.baseFontSize const labelBounds = Bounds.forText(barLabel, { fontSize: labelFontSize, }) @@ -662,7 +676,13 @@ export class StackedDiscreteBarChart width={barWidth} height={barHeight} fill={bar.color} - opacity={isHover ? 1 : isFaint ? 0.1 : 0.8} + opacity={ + isHover + ? 1 + : isFaint + ? 0.1 + : GRAPHER_AREA_OPACITY_DEFAULT + } style={{ transition: "height 200ms ease", }}