diff --git a/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.scss b/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.scss index b1159762b32..b2f19a066a2 100644 --- a/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.scss +++ b/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.scss @@ -59,6 +59,10 @@ $controlRowHeight: 32px; // keep in sync with CONTROLS_ROW_HEIGHT &.GrapherComponent.optimizeForHorizontalSpace .relatedQuestion { border-top: 0; + // adds a top border that stretches across the entire page. + // since we don't know the width of the page, we use a large number (200vw) + // and offset it by another large number (-50vw) to make sure the border + // stretches across the entire page. &::before { content: ""; position: absolute; diff --git a/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.tsx b/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.tsx index 1b3aca2f4ad..40d2a7c4c52 100644 --- a/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.tsx +++ b/packages/@ourworldindata/grapher/src/captionedChart/CaptionedChart.tsx @@ -593,9 +593,13 @@ export class StaticCaptionedChart extends CaptionedChart { } } +// Although a bit unconventional, adding vertical space as a
+// makes margin collapsing impossible and makes it easier to track the +// space available for the chart area (see the CaptionedChart's `chartHeight` method) function VerticalSpace({ height }: { height: number }): JSX.Element { return (
svg { + > .icon { margin-right: 6px; - position: relative; + flex: 0 0 16px; } } } diff --git a/packages/@ourworldindata/grapher/src/controls/ShareMenu.tsx b/packages/@ourworldindata/grapher/src/controls/ShareMenu.tsx index 57000deaa1a..0909f20111e 100644 --- a/packages/@ourworldindata/grapher/src/controls/ShareMenu.tsx +++ b/packages/@ourworldindata/grapher/src/controls/ShareMenu.tsx @@ -157,7 +157,10 @@ export class ShareMenu extends React.Component { href={twitterHref} rel="noopener" > - X + + + {" "} + X/Twitter { href={facebookHref} rel="noopener" > - Facebook + + + {" "} + Facebook { data-track-note="chart_share_embed" onClick={this.onEmbed} > - Embed + + + {" "} + Embed {"share" in navigator && ( { data-track-note="chart_share_navigator" onClick={this.onNavigatorShare} > - Share via… + + + {" "} + Share via… )} - {this.state.canWriteToClipboard && ( + {true && ( - + + + {" "} {this.state.copied ? "Copied!" : "Copy link"} )} @@ -202,7 +216,10 @@ export class ShareMenu extends React.Component { href={editUrl} rel="noopener" > - Edit + + + {" "} + Edit )}
diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index 92ceb841ccb..eb88bbe061f 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -92,6 +92,7 @@ import { DEFAULT_GRAPHER_FRAME_PADDING, DEFAULT_GRAPHER_ENTITY_TYPE, DEFAULT_GRAPHER_ENTITY_TYPE_PLURAL, + GRAPHER_DARK_TEXT, } from "../core/GrapherConstants" import Cookies from "js-cookie" import { @@ -1231,7 +1232,7 @@ export class Grapher this.idealBounds.width - 2 * this.framePaddingHorizontal, lineHeight: 1.2, style: { - fill: "#5b5b5b", + fill: GRAPHER_DARK_TEXT, }, }) }) @@ -2681,6 +2682,13 @@ export class Grapher ) } + @computed get isOnCanonicalUrl(): boolean { + if (!this.canonicalUrl) return false + return ( + getWindowUrl().pathname === Url.fromURL(this.canonicalUrl).pathname + ) + } + @computed get embedUrl(): string | undefined { return this.manager?.embedDialogUrl ?? this.canonicalUrl } diff --git a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts index f316d9b0f06..91bf57d2574 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts @@ -34,6 +34,8 @@ export const DEFAULT_GRAPHER_HEIGHT = 600 export const DEFAULT_GRAPHER_FRAME_PADDING = 16 export const STATIC_EXPORT_DETAIL_SPACING = 8 +export const GRAPHER_DARK_TEXT = "#5b5b5b" + export enum CookieKey { isAdmin = "isAdmin", } diff --git a/packages/@ourworldindata/grapher/src/core/grapher.scss b/packages/@ourworldindata/grapher/src/core/grapher.scss index 9b8ea6112ff..c900ee84445 100644 --- a/packages/@ourworldindata/grapher/src/core/grapher.scss +++ b/packages/@ourworldindata/grapher/src/core/grapher.scss @@ -172,6 +172,10 @@ $zindex-controls-drawer: 140; .GrapherComponent.optimizeForHorizontalSpace { box-shadow: none; + // adds top and bottom borders that stretch across the entire page. + // since we don't know the width of the page, we use a large number (200vw) + // and offset it by another large number (-50vw) to make sure the borders + // stretch across the entire page. &::before, &::after { content: ""; diff --git a/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx b/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx index 1f3980593f6..b9be9402673 100644 --- a/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx +++ b/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx @@ -1002,7 +1002,7 @@ function SortIcon(props: { )} diff --git a/packages/@ourworldindata/grapher/src/footer/Footer.tsx b/packages/@ourworldindata/grapher/src/footer/Footer.tsx index 418e0506672..bd061caef16 100644 --- a/packages/@ourworldindata/grapher/src/footer/Footer.tsx +++ b/packages/@ourworldindata/grapher/src/footer/Footer.tsx @@ -8,11 +8,15 @@ import { DEFAULT_BOUNDS, getRelativeMouse, MarkdownTextWrap, + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, } from "@ourworldindata/utils" import { Tooltip } from "../tooltip/Tooltip" import { FooterManager } from "./FooterManager" import { ActionButtons } from "../controls/ActionButtons" -import { DEFAULT_GRAPHER_FRAME_PADDING } from "../core/GrapherConstants" +import { + DEFAULT_GRAPHER_FRAME_PADDING, + GRAPHER_DARK_TEXT, +} from "../core/GrapherConstants" /* @@ -414,7 +418,8 @@ export class Footer< onClick={action(() => { // on data pages, scroll to the "Sources and Processing" section // on grapher pages, open the sources modal - const sourcesIdOnDataPage = "sources-and-processing" + const sourcesIdOnDataPage = + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID const sourcesElement = document.getElementById(sourcesIdOnDataPage) if (sourcesElement && sourcesElement.scrollIntoView) { @@ -632,7 +637,7 @@ export class StaticFooter extends Footer { @computed protected get licenseAndOriginUrlText(): string { const { finalUrl, finalUrlText, licenseText, licenseUrl } = this - const linkStyle = "fill: #5b5b5b; text-decoration: underline;" + const linkStyle = `fill: ${GRAPHER_DARK_TEXT}; text-decoration: underline;` const licenseSvg = `${licenseText}` if (!finalUrlText) return licenseSvg const originUrlSvg = `${finalUrlText}` @@ -684,7 +689,7 @@ export class StaticFooter extends Footer { const { targetX, targetY } = this.props return ( - + {sources.renderSVG(targetX, targetY)} {note.renderSVG( targetX, diff --git a/packages/@ourworldindata/grapher/src/header/Header.tsx b/packages/@ourworldindata/grapher/src/header/Header.tsx index f4a25679b71..fa91c5af986 100644 --- a/packages/@ourworldindata/grapher/src/header/Header.tsx +++ b/packages/@ourworldindata/grapher/src/header/Header.tsx @@ -8,7 +8,10 @@ import { computed } from "mobx" import { observer } from "mobx-react" import { Logo, LogoOption } from "../captionedChart/Logos" import { HeaderManager } from "./HeaderManager" -import { DEFAULT_GRAPHER_FRAME_PADDING } from "../core/GrapherConstants" +import { + DEFAULT_GRAPHER_FRAME_PADDING, + GRAPHER_DARK_TEXT, +} from "../core/GrapherConstants" @observer export class Header extends React.Component<{ @@ -132,18 +135,20 @@ export class Header extends React.Component<{ style={{ fontFamily: "'Playfair Display', Georgia, 'Times New Roman', 'Liberation Serif', serif", - fontWeight: 500, }} target="_blank" rel="noopener" > - {title.render(x, y, { fill: "#4E4E4E" })} + {title.render(x, y, { + fill: GRAPHER_DARK_TEXT, + fontWeight: 500, + })} {subtitle.renderSVG( x, y + title.height + this.subtitleMarginTop, { - fill: "#4E4E4E", + fill: GRAPHER_DARK_TEXT, } )} @@ -153,6 +158,13 @@ export class Header extends React.Component<{ private renderTitle(): JSX.Element { const { manager } = this + // avoid linking to a grapher/data page when we're already on it + if (manager.isOnCanonicalUrl) { + return ( +

{this.title.renderHTML()}

+ ) + } + // on smaller screens, make the whole width of the header clickable if (manager.isMedium) { return ( diff --git a/packages/@ourworldindata/grapher/src/header/HeaderManager.ts b/packages/@ourworldindata/grapher/src/header/HeaderManager.ts index b028377fb8b..8ad3bfcf49f 100644 --- a/packages/@ourworldindata/grapher/src/header/HeaderManager.ts +++ b/packages/@ourworldindata/grapher/src/header/HeaderManager.ts @@ -16,4 +16,5 @@ export interface HeaderManager { isMedium?: boolean framePaddingHorizontal?: number framePaddingVertical?: number + isOnCanonicalUrl?: boolean } diff --git a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx index f1d00774cb6..756a80f8228 100644 --- a/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx +++ b/packages/@ourworldindata/grapher/src/lineCharts/LineChart.tsx @@ -1184,7 +1184,9 @@ export class LineChart : this.defaultRightPadding ) // top padding leaves room for tick labels - .padTop(6), + .padTop(6) + // bottom padding avoids axis labels to be cut off at some resolutions + .padBottom(2), verticalAxis: this.verticalAxisPart, horizontalAxis: this.horizontalAxisPart, }) diff --git a/packages/@ourworldindata/grapher/src/modal/Modal.scss b/packages/@ourworldindata/grapher/src/modal/Modal.scss index d1f4a4fa4b6..64c0a5465f0 100644 --- a/packages/@ourworldindata/grapher/src/modal/Modal.scss +++ b/packages/@ourworldindata/grapher/src/modal/Modal.scss @@ -1,5 +1,9 @@ $modal-padding: 1.5em; +// the base font size for modals is 16px and on +// smaller devices scales down to 14px. +// it is never scaled up though. + .modalOverlay { position: absolute; // -1px to hide the frame border diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx index 7b322bfba18..f4f55d35c6f 100644 --- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx +++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx @@ -62,8 +62,9 @@ export class SourcesModal extends React.Component<{ @computed private get modalBounds(): Bounds { const maxWidth = 740 - const padWidth = Math.max(16, (this.tabBounds.width - maxWidth) / 2) - return this.tabBounds.padHeight(16).padWidth(padWidth) + // using 15px instead of 16px to make sure the modal fully covers the OWID logo in the header + const padWidth = Math.max(15, (this.tabBounds.width - maxWidth) / 2) + return this.tabBounds.padHeight(15).padWidth(padWidth) } private renderSource(column: CoreColumn): JSX.Element { diff --git a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx index 174fd3edc3f..2e528a34539 100644 --- a/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx +++ b/packages/@ourworldindata/grapher/src/scatterCharts/ScatterPlotChart.tsx @@ -505,8 +505,12 @@ export class ScatterPlotChart @computed get dualAxis(): DualAxis { const { horizontalAxisPart, verticalAxisPart } = this return new DualAxis({ - // top padding leaves room for tick labels - bounds: this.bounds.padRight(this.sidebarWidth + 20).padTop(6), + bounds: this.bounds + .padRight(this.sidebarWidth + 20) + // top padding leaves room for tick labels + .padTop(6) + // bottom padding makes sure the x-axis label doesn't overflow + .padBottom(2), horizontalAxis: horizontalAxisPart, verticalAxis: verticalAxisPart, }) diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx index 072aab899c0..0da08a59baa 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/AbstractStackedChart.tsx @@ -172,8 +172,12 @@ export class AbstractStackedChart paddingForLegend, } = this return new DualAxis({ - // top padding leaves room for tick labels - bounds: bounds.padRight(paddingForLegend).padTop(6), + bounds: bounds + .padRight(paddingForLegend) + // top padding leaves room for tick labels + .padTop(6) + // bottom padding avoids axis labels to be cut off at some resolutions + .padBottom(2), horizontalAxis: horizontalAxisPart, verticalAxis: verticalAxisPart, }) diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx index 88739441c36..3d50f8a6781 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/MarimekkoChart.tsx @@ -523,7 +523,7 @@ export class MarimekkoChart Math.max(whiteSpaceOnLeft, this.longestLabelWidth) - whiteSpaceOnLeft return this.bounds - .padBottom(this.longestLabelHeight) + .padBottom(this.longestLabelHeight + 2) .padBottom(labelLinesHeight) .padTop(this.legend.height + this.legendPaddingTop) .padLeft(marginToEnsureWidestEntityLabelFitsEvenIfAtX0) diff --git a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx index 50acfa5f348..7c67cbe804e 100644 --- a/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx +++ b/packages/@ourworldindata/grapher/src/stackedCharts/StackedDiscreteBarChart.tsx @@ -164,7 +164,8 @@ export class StackedDiscreteBarChart } @computed private get bounds(): Bounds { - return (this.props.bounds ?? DEFAULT_BOUNDS).padRight(10) + // bottom padding avoids axis labels to be cut off at some resolutions + return (this.props.bounds ?? DEFAULT_BOUNDS).padRight(10).padBottom(2) } @computed private get missingDataStrategy(): MissingDataStrategy { diff --git a/packages/@ourworldindata/utils/src/SharedConstants.ts b/packages/@ourworldindata/utils/src/SharedConstants.ts new file mode 100644 index 00000000000..d95b78778fb --- /dev/null +++ b/packages/@ourworldindata/utils/src/SharedConstants.ts @@ -0,0 +1,2 @@ +export const DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID = + "sources-and-processing" as const diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 54bae698c47..a5102e06253 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -629,3 +629,5 @@ export { gdocIdRegex, detailOnDemandRegex, } from "./GdocsConstants.js" + +export { DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID } from "./SharedConstants" diff --git a/site/DataPageContent.tsx b/site/DataPageContent.tsx index 96c1a735918..15923ae49b9 100644 --- a/site/DataPageContent.tsx +++ b/site/DataPageContent.tsx @@ -8,7 +8,10 @@ import { GrapherWithFallback } from "./GrapherWithFallback.js" import { formatAuthors } from "./clientFormatting.js" import { ArticleBlocks } from "./gdocs/ArticleBlocks.js" import { RelatedCharts } from "./blocks/RelatedCharts.js" -import { DataPageContentFields } from "@ourworldindata/utils" +import { + DataPageContentFields, + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, +} from "@ourworldindata/utils" import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js" import StickyNav from "./blocks/StickyNav.js" import cx from "classnames" @@ -67,7 +70,10 @@ export const DataPageContent = ({ { text: "Related Data", target: "#related-data" }, { text: "All Charts", target: "#all-charts" }, { text: "FAQs", target: "#faqs" }, - { text: "Sources & Processing", target: "#sources-and-processing" }, + { + text: "Sources & Processing", + target: "#" + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, + }, { text: "Reuse This Work", target: REUSE_THIS_WORK_ANCHOR }, ] @@ -401,7 +407,9 @@ export const DataPageContent = ({

Sources and Processing

diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index d96d8f114f8..d819997bf95 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -13,6 +13,7 @@ import { excludeNullish, slugify, markdownToEnrichedTextBlock, + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, } from "@ourworldindata/utils" import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js" import StickyNav from "./blocks/StickyNav.js" @@ -64,7 +65,10 @@ export const DataPageV2Content = ({ { text: "Related Data", target: "#related-data" }, { text: "All Charts", target: "#all-charts" }, { text: "FAQs", target: "#faqs" }, - { text: "Sources & Processing", target: "#sources-and-processing" }, + { + text: "Sources & Processing", + target: "#" + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, + }, { text: "Reuse This Work", target: REUSE_THIS_WORK_ANCHOR }, ] @@ -243,7 +247,9 @@ export const DataPageV2Content = ({
{datapageData.attribution}
{datapageData.owidProcessingLevel && ( @@ -438,7 +444,9 @@ export const DataPageV2Content = ({

Sources and processing