diff --git a/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx
new file mode 100644
index 00000000000..db1009fb12c
--- /dev/null
+++ b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx
@@ -0,0 +1,39 @@
+import React from "react"
+import { CodeSnippet } from "../CodeSnippet/CodeSnippet.js"
+
+export const DataCitation = (props: {
+ citationShort: string
+ citationLong: string
+}) => {
+ return (
+ <>
+ {props.citationShort && (
+ <>
+
+ In-line citation
+
+ If you have limited space (e.g. in data visualizations),
+ you can use this abbreviated in-line citation:
+
+
+ >
+ )}
+ {props.citationLong && (
+ <>
+
+ Full citation
+
+
+ >
+ )}
+ >
+ )
+}
diff --git a/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss b/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss
index e2b43a3efe3..22c40e208cd 100644
--- a/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss
+++ b/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss
@@ -56,6 +56,7 @@
margin: 0;
font-size: var(--content-size);
+ ol,
ul {
margin-left: 1em;
@@ -67,6 +68,26 @@
&:last-child {
margin-bottom: 0;
}
+ p {
+ margin: 0;
+ }
+ }
+ }
+
+ ol {
+ margin-left: 1em;
+
+ li {
+ margin: 0.5em 0; // half of the default margin for ordered lists, otherwise like unordered lists above
+ &:first-child {
+ margin-top: 0;
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+ p {
+ margin: 0;
+ }
}
}
@@ -77,10 +98,6 @@
> *:last-child {
margin-bottom: 0;
}
-
- ul li p {
- margin: 0;
- }
}
a {
diff --git a/packages/@ourworldindata/components/src/index.ts b/packages/@ourworldindata/components/src/index.ts
index 35b288d6d40..d5e088b7264 100644
--- a/packages/@ourworldindata/components/src/index.ts
+++ b/packages/@ourworldindata/components/src/index.ts
@@ -38,6 +38,8 @@ export {
renderCodeSnippets,
} from "./CodeSnippet/CodeSnippet.js"
+export { DataCitation } from "./DataCitation/DataCitation.js"
+
export {
DATAPAGE_ABOUT_THIS_DATA_SECTION_ID,
DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
index c1611d1e5ea..cece9597102 100644
--- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
+++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
@@ -125,6 +125,14 @@
margin-right: $tab-gap;
}
+ .citation__paragraph {
+ color: $light-text;
+ }
+
+ .citation__type {
+ color: $dark-text;
+ }
+
.indicator-processing .indicator-processing__link {
@include body-3-medium;
display: inline-block;
diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
index 92efa2b287b..b34e2490287 100644
--- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
+++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
@@ -13,11 +13,14 @@ import {
OwidSource,
IndicatorTitleWithFragments,
joinTitleFragments,
+ getCitationShort,
+ getCitationLong,
} from "@ourworldindata/utils"
import {
IndicatorSources,
IndicatorProcessing,
SimpleMarkdownText,
+ DataCitation,
} from "@ourworldindata/components"
import React from "react"
import { action, computed } from "mobx"
@@ -282,6 +285,27 @@ export class Source extends React.Component<{
return { ...this.column.def, source: this.column.source }
}
+ @computed get citationShort(): string {
+ return getCitationShort(
+ this.def.origins ?? [],
+ getAttributionFragmentsFromVariable(this.def),
+ this.def.owidProcessingLevel
+ )
+ }
+
+ @computed get citationLong(): string {
+ return getCitationLong(
+ this.title,
+ this.def.origins ?? [],
+ this.source,
+ getAttributionFragmentsFromVariable(this.def),
+ this.def.presentation?.attributionShort,
+ this.def.presentation?.titleVariant,
+ this.def.owidProcessingLevel,
+ undefined
+ )
+ }
+
@computed private get source(): OwidSource {
return this.def.source ?? {}
}
@@ -426,6 +450,13 @@ export class Source extends React.Component<{
+
+ How to cite this data:
+
+
)
}
diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts
index 1c8395c6b17..ca8413a12c9 100644
--- a/packages/@ourworldindata/utils/src/index.ts
+++ b/packages/@ourworldindata/utils/src/index.ts
@@ -382,6 +382,8 @@ export {
splitSourceTextIntoFragments,
prepareSourcesForDisplay,
formatSourceDate,
+ getCitationLong,
+ getCitationShort,
} from "./metadataHelpers.js"
export {
diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts
index 46bcbbfd531..6d3350e8d76 100644
--- a/packages/@ourworldindata/utils/src/metadataHelpers.ts
+++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts
@@ -1,8 +1,13 @@
import { OwidOrigin } from "./OwidOrigin"
-import { OwidProcessingLevel, OwidVariableWithSource } from "./OwidVariable"
+import {
+ IndicatorTitleWithFragments,
+ OwidProcessingLevel,
+ OwidVariableWithSource,
+} from "./OwidVariable"
import { DisplaySource } from "./owidTypes"
import { compact, uniq, last, excludeUndefined } from "./Util"
import dayjs from "./dayjs.js"
+import { OwidSource } from "./OwidSource.js"
export function getOriginAttributionFragments(
origins: OwidOrigin[] | undefined
@@ -184,6 +189,82 @@ export const prepareSourcesForDisplay = (
return sourcesForDisplay
}
+const getYearSuffixFromOrigin = (o: OwidOrigin): string => {
+ const year = o.dateAccessed
+ ? dayjs(o.dateAccessed, ["YYYY-MM-DD", "YYYY"]).year()
+ : o.datePublished
+ ? dayjs(o.datePublished, ["YYYY-MM-DD", "YYYY"]).year()
+ : undefined
+ if (year) return ` (${year})`
+ else return ""
+}
+export const getCitationShort = (
+ origins: OwidOrigin[],
+ attributions: string[],
+ owidProcessingLevel: OwidProcessingLevel | undefined
+): string => {
+ const producersWithYear = uniq(
+ origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`)
+ )
+ const processingLevelPhrase =
+ getPhraseForProcessingLevel(owidProcessingLevel)
+
+ const attributionFragments = attributions ?? producersWithYear
+ const attributionPotentiallyShortened =
+ attributionFragments.length > 3
+ ? `${attributionFragments[0]} and other sources`
+ : attributionFragments.join("; ")
+
+ return `${attributionPotentiallyShortened} – ${processingLevelPhrase} by Our World in Data`
+}
+
+export const getCitationLong = (
+ indicatorTitle: IndicatorTitleWithFragments,
+ origins: OwidOrigin[],
+ source: OwidSource | undefined,
+ attributions: string[],
+ attributionShort: string | undefined,
+ titleVariant: string | undefined,
+ owidProcessingLevel: OwidProcessingLevel | undefined,
+ canonicalUrl: string | undefined
+): string => {
+ const sourceShortName =
+ attributionShort && titleVariant
+ ? `${attributionShort} – ${titleVariant}`
+ : attributionShort || titleVariant
+ const producersWithYear = uniq(
+ origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`)
+ )
+ const processingLevelPhrase =
+ getPhraseForProcessingLevel(owidProcessingLevel)
+
+ const attributionFragments = attributions ?? producersWithYear
+ const attributionUnshortened = attributionFragments.join("; ")
+ const citationLonger = `${attributionUnshortened} – ${processingLevelPhrase} by Our World in Data`
+ const titleWithOptionalFragments = excludeUndefined([
+ indicatorTitle.title,
+ sourceShortName,
+ ]).join(" – ")
+ const originsLong = uniq(
+ origins.map(
+ (o) =>
+ `${o.producer}, “${o.title ?? o.titleSnapshot}${
+ o.versionProducer ? " " + o.versionProducer : ""
+ }”`
+ )
+ ).join("; ")
+ const today = dayjs().format("MMMM D, YYYY")
+ return excludeUndefined([
+ `${citationLonger}.`,
+ `“${titleWithOptionalFragments}” [dataset].`,
+ originsLong
+ ? `${originsLong} [original data].`
+ : source?.name
+ ? `${source?.name} [original data].`
+ : undefined,
+ canonicalUrl ? `Retrieved ${today} from ${canonicalUrl}` : undefined,
+ ]).join(" ")
+}
export const formatSourceDate = (
date: string | undefined,
format: string
diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx
index 9dc392b7b45..4e7e39e8a2f 100644
--- a/site/DataPageV2Content.tsx
+++ b/site/DataPageV2Content.tsx
@@ -23,6 +23,7 @@ import {
makeUnitConversionFactor,
makeLinks,
HtmlOrSimpleMarkdownText,
+ DataCitation,
} from "@ourworldindata/components"
import ReactDOM from "react-dom"
import { GrapherWithFallback } from "./GrapherWithFallback.js"
@@ -33,13 +34,14 @@ import {
uniq,
formatAuthors,
intersection,
- getPhraseForProcessingLevel,
prepareSourcesForDisplay,
DataPageRelatedResearch,
isEmpty,
excludeUndefined,
OwidOrigin,
DataPageDataV2,
+ getCitationShort,
+ getCitationLong,
joinTitleFragments,
} from "@ourworldindata/utils"
import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js"
@@ -142,40 +144,23 @@ export const DataPageV2Content = ({
)
const attributionFragments = datapageData.attributions ?? producersWithYear
- const attributionPotentiallyShortened =
- attributionFragments.length > 3
- ? `${attributionFragments[0]} and other sources`
- : attributionFragments.join("; ")
const attributionUnshortened = attributionFragments.join("; ")
- const processingLevelPhrase = getPhraseForProcessingLevel(
+ const citationShort = getCitationShort(
+ datapageData.origins,
+ datapageData.attributions,
datapageData.owidProcessingLevel
)
- const citationShort = `${attributionPotentiallyShortened} – ${processingLevelPhrase} by Our World in Data`
- const citationLonger = `${attributionUnshortened} – ${processingLevelPhrase} by Our World in Data`
- const originsLong = uniq(
- datapageData.origins.map(
- (o) =>
- `${o.producer}, ${o.title ?? o.titleSnapshot}${
- o.versionProducer ? " " + o.versionProducer : ""
- }`
- )
- ).join("; ")
- const today = dayjs().format("MMMM D, YYYY")
const currentYear = dayjs().year()
- const titleWithOptionalFragments = excludeUndefined([
- datapageData.title.title,
- titleFragments,
- ]).join(" – ")
- const citationLong = excludeUndefined([
- `${citationLonger}.`,
- `${titleWithOptionalFragments} [dataset].`,
- originsLong
- ? `${originsLong} [original data].`
- : datapageData.source?.name
- ? `${datapageData.source?.name} [original data].`
- : undefined,
- `Retrieved ${today} from ${canonicalUrl}`,
- ]).join(" ")
+ const citationLong = getCitationLong(
+ datapageData.title,
+ datapageData.origins,
+ datapageData.source,
+ datapageData.attributions,
+ datapageData.attributionShort,
+ datapageData.titleVariant,
+ datapageData.owidProcessingLevel,
+ canonicalUrl
+ )
const {
linkedDocuments = {},
@@ -673,6 +658,7 @@ export const DataPageV2Content = ({
+
{(citationShort ||
citationLong ||
citationDatapage) && (
@@ -681,63 +667,6 @@ export const DataPageV2Content = ({
Citations
- {(citationShort ||
- citationLong) && (
-
-
- How to cite this data
-
- {citationShort && (
- <>
-
-
- In-line
- citation
-
-
- If you have
- limited space
- (e.g. in data
- visualizations,
- on social
- media), you can
- use this
- abbreviated
- in-line
- citation:
-
-
- >
- )}
- {citationLong && (
- <>
-
-
- Full
- citation
-
-
-
- >
- )}
-
- )}
{citationDatapage && (
@@ -759,6 +688,22 @@ export const DataPageV2Content = ({
/>
)}
+
+
+ How to cite this data
+
+ {(citationShort ||
+ citationLong) && (
+
+ )}
+
)}