Skip to content

Commit

Permalink
feat: NarrativeChart component
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelgerber committed Dec 12, 2024
1 parent 0c3f18e commit 6c82c49
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 0 deletions.
4 changes: 4 additions & 0 deletions db/model/Gdoc/GdocBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@ export class GdocBase implements OwidGdocBaseInterface {
"key-indicator-collection",
"list",
"missing-data",

// Open question: there's not a direct link to a chart here, but there is a chart and also a parent chart
"narrative-chart",

"numbered-list",
"people",
"people-rows",
Expand Down
11 changes: 11 additions & 0 deletions db/model/Gdoc/enrichedToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ ${items}
exportComponents
)
)
.with({ type: "narrative-chart" }, (b): string | undefined =>
markdownComponent(
"NarrativeChart",
{
name: b.name,
caption: b.caption ? spansToMarkdown(b.caption) : undefined,
// Note: truncated
},
exportComponents
)
)
.with({ type: "code" }, (b): string | undefined => {
return (
"```\n" + b.text.map((text) => text.value).join("\n") + "\n```"
Expand Down
15 changes: 15 additions & 0 deletions db/model/Gdoc/enrichedToRaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
RawBlockPeople,
RawBlockPeopleRows,
RawBlockPerson,
RawBlockNarrativeChart,
RawBlockCode,
} from "@ourworldindata/types"
import { spanToHtmlString } from "./gdocUtils.js"
Expand Down Expand Up @@ -123,6 +124,20 @@ export function enrichedBlockToRawBlock(
},
})
)
.with(
{ type: "narrative-chart" },
(b): RawBlockNarrativeChart => ({
type: b.type,
value: {
name: b.name,
height: b.height,
row: b.row,
column: b.column,
position: b.position,
caption: b.caption ? spansToHtmlText(b.caption) : undefined,
},
})
)
.with(
{ type: "code" },
(b): RawBlockCode => ({
Expand Down
10 changes: 10 additions & 0 deletions db/model/Gdoc/exampleEnrichedBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ export const enrichedBlockExamples: Record<
caption: boldLinkExampleText,
parseErrors: [],
},
"narrative-chart": {
type: "narrative-chart",
name: "world-has-become-less-democratic",
height: "400",
row: "1",
column: "1",
position: "featured",
caption: boldLinkExampleText,
parseErrors: [],
},
code: {
type: "code",
text: [
Expand Down
1 change: 1 addition & 0 deletions db/model/Gdoc/gdocUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export function extractFilenamesFromBlock(
"latest-data-insights",
"list",
"missing-data",
"narrative-chart",
"numbered-list",
"people",
"people-rows",
Expand Down
20 changes: 20 additions & 0 deletions db/model/Gdoc/rawToArchie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
RawBlockPeople,
RawBlockPeopleRows,
RawBlockPerson,
RawBlockNarrativeChart,
RawBlockCode,
} from "@ourworldindata/types"
import { isArray } from "@ourworldindata/utils"
Expand Down Expand Up @@ -128,6 +129,21 @@ function* rawBlockChartToArchieMLString(
yield "{}"
}

function* rawBlockNarrativeChartToArchieMLString(
block: RawBlockNarrativeChart
): Generator<string, void, undefined> {
yield "{.narrative-chart}"
if (typeof block.value !== "string") {
yield* propertyToArchieMLString("name", block.value)
yield* propertyToArchieMLString("height", block.value)
yield* propertyToArchieMLString("row", block.value)
yield* propertyToArchieMLString("column", block.value)
yield* propertyToArchieMLString("position", block.value)
yield* propertyToArchieMLString("caption", block.value)
}
yield "{}"
}

function* rawBlockCodeToArchieMLString(
block: RawBlockCode
): Generator<string, void, undefined> {
Expand Down Expand Up @@ -830,6 +846,10 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator(
.with({ type: "all-charts" }, rawBlockAllChartsToArchieMLString)
.with({ type: "aside" }, rawBlockAsideToArchieMLString)
.with({ type: "chart" }, rawBlockChartToArchieMLString)
.with(
{ type: "narrative-chart" },
rawBlockNarrativeChartToArchieMLString
)
.with({ type: "code" }, rawBlockCodeToArchieMLString)
.with({ type: "donors" }, rawBlockDonorListToArchieMLString)
.with({ type: "scroller" }, rawBlockScrollerToArchieMLString)
Expand Down
64 changes: 64 additions & 0 deletions db/model/Gdoc/rawToEnriched.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ import {
EnrichedBlockPerson,
RawBlockPeopleRows,
EnrichedBlockPeopleRows,
RawBlockNarrativeChart,
EnrichedBlockNarrativeChart,
RawBlockCode,
EnrichedBlockCode,
} from "@ourworldindata/types"
Expand Down Expand Up @@ -172,6 +174,7 @@ export function parseRawBlocksToEnrichedBlocks(
.with({ type: "blockquote" }, parseBlockquote)
.with({ type: "callout" }, parseCallout)
.with({ type: "chart" }, parseChart)
.with({ type: "narrative-chart" }, parseNarrativeChart)
.with({ type: "code" }, parseCode)
.with({ type: "donors" }, parseDonorList)
.with({ type: "scroller" }, parseScroller)
Expand Down Expand Up @@ -496,6 +499,67 @@ const parseChart = (raw: RawBlockChart): EnrichedBlockChart => {
}
}

const parseNarrativeChart = (
raw: RawBlockNarrativeChart
): EnrichedBlockNarrativeChart => {
const createError = (
error: ParseError,
name: string,
caption: Span[] = []
): EnrichedBlockNarrativeChart => ({
type: "narrative-chart",
name,
caption,
parseErrors: [error],
})

const val = raw.value

if (typeof val === "string") {
return {
type: "narrative-chart",
name: val,
parseErrors: [],
}
} else {
if (!val.name)
return createError(
{
message: "name property is missing",
},
""
)

const warnings: ParseError[] = []

const height = val.height
const row = val.row
const column = val.column
// This property is currently unused, a holdover from @mathisonian's gdocs demo.
// We will decide soon™️ if we want to use it for something
let position: ChartPositionChoice | undefined = undefined
if (val.position)
if (val.position === "featured") position = val.position
else {
warnings.push({
message: "position must be 'featured' or unset",
})
}
const caption = val.caption ? htmlToSpans(val.caption) : []

return omitUndefinedValues({
type: "narrative-chart",
name: val.name,
height,
row,
column,
position,
caption: caption.length > 0 ? caption : undefined,
parseErrors: [],
}) as EnrichedBlockNarrativeChart
}
}

const parseCode = (raw: RawBlockCode): EnrichedBlockCode => {
return {
type: "code",
Expand Down
27 changes: 27 additions & 0 deletions packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,31 @@ export type EnrichedBlockChart = {
tabs?: ChartTabKeyword[]
} & EnrichedBlockWithParseErrors

export type RawBlockNarrativeChartValue = {
name?: string
height?: string
row?: string
column?: string
// TODO: position is used as a classname apparently? Should be renamed or split
position?: string
caption?: string
}

export type RawBlockNarrativeChart = {
type: "narrative-chart"
value: RawBlockNarrativeChartValue | string
}

export type EnrichedBlockNarrativeChart = {
type: "narrative-chart"
name: string
height?: string
row?: string
column?: string
position?: ChartPositionChoice
caption?: Span[]
} & EnrichedBlockWithParseErrors

export type RawBlockCode = {
type: "code"
value: RawBlockText[]
Expand Down Expand Up @@ -945,6 +970,7 @@ export type OwidRawGdocBlock =
| RawBlockAside
| RawBlockCallout
| RawBlockChart
| RawBlockNarrativeChart
| RawBlockCode
| RawBlockDonorList
| RawBlockScroller
Expand Down Expand Up @@ -996,6 +1022,7 @@ export type OwidEnrichedGdocBlock =
| EnrichedBlockAside
| EnrichedBlockCallout
| EnrichedBlockChart
| EnrichedBlockNarrativeChart
| EnrichedBlockCode
| EnrichedBlockDonorList
| EnrichedBlockScroller
Expand Down
3 changes: 3 additions & 0 deletions packages/@ourworldindata/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ export {
SocialLinkType,
type RawSocialLink,
type EnrichedSocialLink,
type RawBlockNarrativeChart,
type EnrichedBlockNarrativeChart,
} from "./gdocTypes/ArchieMlComponents.js"
export {
ChartConfigType,
Expand Down Expand Up @@ -330,6 +332,7 @@ export {
type OwidGdocContent,
type OwidGdocIndexItem,
extractGdocIndexItem,
type ChartViewMetadata,
} from "./gdocTypes/Gdoc.js"

export {
Expand Down
1 change: 1 addition & 0 deletions packages/@ourworldindata/utils/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,7 @@ export function traverseEnrichedBlock(
type: P.union(
"chart-story",
"chart",
"narrative-chart",
"code",
"donors",
"horizontal-rule",
Expand Down
10 changes: 10 additions & 0 deletions site/gdocs/components/ArticleBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { HomepageSearch } from "./HomepageSearch.js"
import LatestDataInsightsBlock from "./LatestDataInsightsBlock.js"
import { Socials } from "./Socials.js"
import Person from "./Person.js"
import NarrativeChart from "./NarrativeChart.js"

export type Container =
| "default"
Expand Down Expand Up @@ -245,6 +246,15 @@ export default function ArticleBlock({
/>
)
})
.with({ type: "narrative-chart" }, (block) => {
return (
<NarrativeChart
className={getLayout("chart", containerType)}
d={block}
fullWidthOnMobile={true}
/>
)
})
.with({ type: "code" }, (block) => (
<CodeSnippet
className={getLayout("code-snippet", containerType)}
Expand Down
68 changes: 68 additions & 0 deletions site/gdocs/components/NarrativeChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useContext, useRef } from "react"
import { useEmbedChart } from "../../hooks.js"
import { EnrichedBlockNarrativeChart } from "@ourworldindata/types"
import { renderSpans } from "../utils.js"
import cx from "classnames"
import { GRAPHER_PREVIEW_CLASS } from "../../SiteConstants.js"
import { AttachmentsContext } from "../OwidGdoc.js"
import { BlockErrorFallback } from "./BlockErrorBoundary.js"

export default function NarrativeChart({
d,
className,
fullWidthOnMobile = false,
}: {
d: EnrichedBlockNarrativeChart
className?: string
fullWidthOnMobile?: boolean
}) {
const refChartContainer = useRef<HTMLDivElement>(null)
useEmbedChart(0, refChartContainer)

const attachments = useContext(AttachmentsContext)

const viewMetadata = attachments.chartViewMetadata?.[d.name]

if (!viewMetadata)
return (
<BlockErrorFallback
className={className}
error={{
name: "Narrative view not found",
message: `Narrative view with name "${d.name}" couldn't be found.`,
}}
/>
)

const metadataStringified = JSON.stringify(viewMetadata)

return (
<div
className={cx(d.position, className, {
"full-width-on-mobile": fullWidthOnMobile,
})}
style={{ gridRow: d.row, gridColumn: d.column }}
ref={refChartContainer}
>
<figure
key={metadataStringified}
className={cx(GRAPHER_PREVIEW_CLASS, "chart")}
data-grapher-view-config={metadataStringified}
// data-grapher-src={isExplorer ? undefined : resolvedUrl}
style={{
width: "100%",
border: "0px none",
height: d.height,
}}
>
{/* <a href={resolvedUrl} target="_blank" rel="noopener">
<GrapherImage slug={resolvedSlug} alt={d.title} />
<InteractionNotice />
</a> */}
</figure>
{d.caption ? (
<figcaption>{renderSpans(d.caption)}</figcaption>
) : null}
</div>
)
}

0 comments on commit 6c82c49

Please sign in to comment.