Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 (static charts) add portrait mode to Grapher's download options #2967

Merged
merged 15 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions baker/GrapherImageBaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function bakeGraphersToPngs(
optimizeSvgs = false
) {
const grapher = new Grapher({ ...jsonConfig, manuallyProvideData: true })
grapher.isExportingtoSvgOrPng = true
grapher.isExportingToSvgOrPng = true
grapher.shouldIncludeDetailsInStaticExport = false
grapher.receiveOwidData(vardata)
const outPath = path.join(outDir, grapher.slug as string)
Expand Down Expand Up @@ -120,7 +120,7 @@ export function initGrapherForSvgExport(
manuallyProvideData: true,
queryStr,
})
grapher.isExportingtoSvgOrPng = true
grapher.isExportingToSvgOrPng = true
grapher.shouldIncludeDetailsInStaticExport = false
return grapher
}
Expand Down Expand Up @@ -213,7 +213,7 @@ export async function grapherToSVG(
vardata: MultipleOwidVariableDataDimensionsMap
): Promise<string> {
const grapher = new Grapher({ ...jsonConfig, manuallyProvideData: true })
grapher.isExportingtoSvgOrPng = true
grapher.isExportingToSvgOrPng = true
grapher.shouldIncludeDetailsInStaticExport = false
grapher.receiveOwidData(vardata)
return grapher.staticSVG
Expand Down
4 changes: 2 additions & 2 deletions functions/_common/grapherRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,10 @@ async function fetchAndRenderGrapherToSvg({
bakedGrapherURL: grapherBaseUrl,
queryStr: "?" + searchParams.toString(),
bounds,
boundsForExport: bounds,
baseFontSize: options.fontSize,
})
grapher.isGeneratingThumbnail = true
grapher.shouldIncludeDetailsInStaticExport = options.details
grapher.baseFontSize = options.fontSize

grapherLogger.log("grapherInit")
const promises = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class DiscreteBarChart
}

@computed get fontSize(): number {
return this.manager.baseFontSize ?? BASE_FONT_SIZE
return this.manager.fontSize ?? BASE_FONT_SIZE
}

@computed private get labelFontSize(): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const StaticLineChartForExport = (): JSX.Element => {
<StaticCaptionedChart
manager={{
...manager,
isExportingtoSvgOrPng: true,
isExportingToSvgOrPng: true,
}}
/>
)
Expand Down
4 changes: 2 additions & 2 deletions packages/@ourworldindata/grapher/src/chart/ChartManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import { ColorScale } from "../color/ColorScale"

export interface ChartManager {
base?: React.RefObject<SVGGElement | HTMLDivElement>
baseFontSize?: number
fontSize?: number

table: OwidTable
transformedTable?: OwidTable

isSelectingData?: boolean
startSelectingWhenLineClicked?: boolean // used by lineLabels
isExportingtoSvgOrPng?: boolean
isExportingToSvgOrPng?: boolean
isRelativeMode?: boolean
comparisonLines?: ComparisonLineConfig[]
hideLegend?: boolean
Expand Down
132 changes: 95 additions & 37 deletions packages/@ourworldindata/grapher/src/core/Grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import {
DEFAULT_GRAPHER_ENTITY_TYPE,
DEFAULT_GRAPHER_ENTITY_TYPE_PLURAL,
GRAPHER_DARK_TEXT,
GrapherExportFormat,
} from "../core/GrapherConstants"
import Cookies from "js-cookie"
import {
Expand Down Expand Up @@ -262,6 +263,8 @@ export interface GrapherProgrammaticInterface extends GrapherInterface {
env?: string
dataApiUrlForAdmin?: string
annotation?: Annotation
boundsForExport?: Bounds
baseFontSize?: number

hideEntityControls?: boolean
hideZoomToggle?: boolean
Expand Down Expand Up @@ -785,8 +788,8 @@ export class Grapher
return this.tableAfterAllTransformsAndFilters
}

@observable.ref isExportingtoSvgOrPng = false
@observable.ref isGeneratingThumbnail = false
@observable.ref isExportingToSvgOrPng = false
@observable.ref exportFormat = GrapherExportFormat.landscape

tooltips?: TooltipManager["tooltips"] = observable.map({}, { deep: false })
@observable isPlaying = false
Expand All @@ -801,12 +804,18 @@ export class Grapher
return location.host.includes("staging")
}

private get isLocalhost(): boolean {
if (typeof location === undefined) return false
return location.host.includes("localhost")
}

@computed get editUrl(): string | undefined {
if (!this.showAdminControls && !this.isDev && !this.isStaging)
return undefined
return `${this.adminBaseUrl}/admin/${
this.manager?.editUrl ?? `charts/${this.id}/edit`
}`
if (this.showAdminControls) {
return `${this.adminBaseUrl}/admin/${
this.manager?.editUrl ?? `charts/${this.id}/edit`
}`
}
return undefined
}

/**
Expand All @@ -817,11 +826,7 @@ export class Grapher
return window.admin !== undefined
}

/**
* Whether the user viewing the chart is an admin and we should show admin controls,
* like the "Edit" option in the share menu.
*/
@computed get showAdminControls(): boolean {
@computed get isUserLoggedInAsAdmin(): boolean {
// This cookie is set by visiting ourworldindata.org/identifyadmin on the static site.
// There is an iframe on owid.cloud to trigger a visit to that page.

Expand All @@ -835,6 +840,15 @@ export class Grapher
}
}

@computed get showAdminControls(): boolean {
return (
this.isUserLoggedInAsAdmin ||
this.isDev ||
this.isLocalhost ||
this.isStaging
)
}

@action.bound
async downloadLegacyDataFromOwidVariableIds(
inputTableTransformer?: ChartTableTransformer
Expand Down Expand Up @@ -950,7 +964,7 @@ export class Grapher
@observable private _baseFontSize = BASE_FONT_SIZE

@computed get baseFontSize(): number {
if (this.isExportingtoSvgOrPng) return Math.max(this._baseFontSize, 18)
if (this.isExportingToSvgOrPng) return Math.max(this._baseFontSize, 18)
return this._baseFontSize
}

Expand Down Expand Up @@ -1068,7 +1082,7 @@ export class Grapher
@computed get shouldLinkToOwid(): boolean {
if (
this.isEmbeddedInAnOwidPage ||
this.isExportingtoSvgOrPng ||
this.isExportingToSvgOrPng ||
!this.isInIFrame
)
return false
Expand Down Expand Up @@ -1101,10 +1115,6 @@ export class Grapher
this.disposers.forEach((dispose) => dispose())
}

@computed get fontSize(): number {
return this.baseFontSize
}

// todo: can we remove this?
// I believe these states can only occur during editing.
@action.bound private ensureValidConfigWhenEditing(): void {
Expand Down Expand Up @@ -1262,7 +1272,8 @@ export class Grapher
fontSize: 12,
// leave room for padding on the left and right
maxWidth:
this.idealBounds.width - 2 * this.framePaddingHorizontal,
this.boundsForExport.width -
2 * this.framePaddingHorizontal,
lineHeight: 1.2,
style: {
fill: GRAPHER_DARK_TEXT,
Expand Down Expand Up @@ -1704,26 +1715,51 @@ export class Grapher
return new Bounds(0, 0, DEFAULT_GRAPHER_WIDTH, DEFAULT_GRAPHER_HEIGHT)
}

@computed get portraitBounds(): Bounds {
return new Bounds(0, 0, DEFAULT_GRAPHER_HEIGHT, DEFAULT_GRAPHER_WIDTH)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing a simple swap for now, but will look into what's a sensible default in subsequent PRs

}

@computed get hasYDimension(): boolean {
return this.dimensions.some((d) => d.property === DimensionProperty.y)
}

@computed get boundsForExport(): Bounds {
if (this.props.boundsForExport) return this.props.boundsForExport

switch (this.exportFormat) {
case GrapherExportFormat.landscape:
return this.idealBounds
case GrapherExportFormat.portrait:
return this.portraitBounds
default:
return this.idealBounds
}
}

generateStaticSvg(bounds: Bounds = this.idealBounds): string {
const _isExportingtoSvgOrPng = this.isExportingtoSvgOrPng
this.isExportingtoSvgOrPng = true
const _isExportingToSvgOrPng = this.isExportingToSvgOrPng
this.isExportingToSvgOrPng = true
const staticSvg = ReactDOMServer.renderToStaticMarkup(
<StaticCaptionedChart manager={this} bounds={bounds} />
)
this.isExportingtoSvgOrPng = _isExportingtoSvgOrPng
this.isExportingToSvgOrPng = _isExportingToSvgOrPng
return staticSvg
}

get staticSVG(): string {
return this.generateStaticSvg()
}

get staticSVGLandscape(): string {
return this.staticSVG
}

get staticSVGPortrait(): string {
return this.generateStaticSvg(this.portraitBounds)
}

@computed get disableIntroAnimation(): boolean {
return this.isExportingtoSvgOrPng
return this.isExportingToSvgOrPng
}

@computed get mapConfig(): MapConfig {
Expand Down Expand Up @@ -1908,7 +1944,7 @@ export class Grapher
@computed private get useIdealBounds(): boolean {
const {
isEditor,
isExportingtoSvgOrPng,
isExportingToSvgOrPng,
bounds,
widthForDeviceOrientation,
heightForDeviceOrientation,
Expand Down Expand Up @@ -1939,7 +1975,7 @@ export class Grapher
return false

// If the user is using interactive version and then goes to export chart, use current bounds to maintain WYSIWYG
if (isExportingtoSvgOrPng) return false
if (isExportingToSvgOrPng) return false

// todo: can remove this if we drop old adminSite editor
if (isEditor) return true
Expand Down Expand Up @@ -2140,6 +2176,14 @@ export class Grapher
title: `Toggle sources modal`,
category: "Chart",
},
{
combo: "d",
fn: (): void => {
this.isDownloadModalOpen = !this.isDownloadModalOpen
},
title: "Toggle download modal",
category: "Chart",
},
{
combo: "esc",
fn: (): void => this.clearErrors(),
Expand Down Expand Up @@ -2414,7 +2458,7 @@ export class Grapher
const containerClasses = classnames({
GrapherComponent: true,
GrapherPortraitClass: this.isPortrait,
isExportingToSvgOrPng: this.isExportingtoSvgOrPng,
isExportingToSvgOrPng: this.isExportingToSvgOrPng,
optimizeForHorizontalSpace: this.optimizeForHorizontalSpace,
GrapherComponentNarrow: this.isNarrow,
GrapherComponentSemiNarrow: this.isSemiNarrow,
Expand All @@ -2425,9 +2469,9 @@ export class Grapher
const containerStyle = {
width: this.renderWidth,
height: this.renderHeight,
fontSize: this.isExportingtoSvgOrPng
fontSize: this.isExportingToSvgOrPng
? 18
: Math.min(16, this.baseFontSize), // cap font size at 16px
: Math.min(16, this.fontSize), // cap font size at 16px
}

return (
Expand All @@ -2446,7 +2490,7 @@ export class Grapher
render(): JSX.Element | undefined {
// TODO how to handle errors in exports?
// TODO remove this? should have a simple toStaticSVG for exporting
if (this.isExportingtoSvgOrPng) return <CaptionedChart manager={this} />
if (this.isExportingToSvgOrPng) return <CaptionedChart manager={this} />

if (this.isInFullScreenMode) {
return (
Expand Down Expand Up @@ -2499,11 +2543,25 @@ export class Grapher
}
}

@action.bound private setBaseFontSize(): void {
// the header and footer don't rely on the base font size unless explicitly specified
@computed get useBaseFontSize(): boolean {
return this.props.baseFontSize !== undefined
}

computeBaseFontSize(): number {
const { renderWidth } = this
if (renderWidth <= 400) this.baseFontSize = 14
else if (renderWidth < 1080) this.baseFontSize = 16
else if (renderWidth >= 1080) this.baseFontSize = 18
if (renderWidth <= 400) return 14
else if (renderWidth < 1080) return 16
else if (renderWidth >= 1080) return 18
else return 16
}

@action.bound private setBaseFontSize(): void {
this.baseFontSize = this.computeBaseFontSize()
}

@computed get fontSize(): number {
return this.props.baseFontSize ?? this.baseFontSize
}

// when optimized for horizontal screen, grapher bleeds onto the edges horizontally
Expand All @@ -2518,27 +2576,27 @@ export class Grapher
}

@computed get isNarrow(): boolean {
if (this.isExportingtoSvgOrPng) return false
if (this.isExportingToSvgOrPng) 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.isExportingToSvgOrPng) 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.isExportingToSvgOrPng) 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.isExportingToSvgOrPng) return false
return this.renderWidth <= 840
}

Expand Down
5 changes: 5 additions & 0 deletions packages/@ourworldindata/grapher/src/core/GrapherConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ export enum GrapherTabOption {
table = "table",
}

export enum GrapherExportFormat {
landscape = "landscape",
portrait = "portrait",
}

export enum ScaleType {
linear = "linear",
log = "log",
Expand Down
Loading
Loading