Skip to content

Commit

Permalink
✨ (static charts) make charts mobile-friendly (#2977)
Browse files Browse the repository at this point in the history
Collection of small improvements to make static charts more mobile-friendly. Some changes apply to static+mobile charts only, others apply to all charts (interactive and static).

For all charts:
- Font size changes
    - Rather than micro-managing font sizes for the special case of static+mobile charts, I rely on the `baseFontSize` to scale all font sizes up
    - To make that work, I have adjusted some font sizes to make sure it looks good for all cases (desktop/mobile, interactive/static)
    - I compiled a list of font sizes currently in use [here](https://www.notion.so/owid/d24b12ac2a754420af4aeba26ba21cca?v=647f5989b92d46e0bd802a52597ddcda&pvs=4);  the table also denotes the changes I made
    - The biggest change I made is decreasing the font size of tick labels by a few pixels (0.9 -> 0.75, i.e. 14.4px -> 12px for `baseFontSize` = 16px)
- Updated colours in the chart to use Grapher's dark and light colours
- Updated the opacity used for bars and areas (0.8)
- Draw a white outline around value labels that are rendered into the chart area and might overlap with grid lines (DiscreteBar and StackedDiscreteBar)
- Make the detail's font size respect the base font size
- Make entity labels in ScatterPlots respect the base font size

For static+mobile charts only:
- Increased the thickness of axis lines
- Increased the thickness of lines in LineCharts and SlopeCharts
- Increased the thickness of lines in Maps
- Hide line markers in LineCharts
- Changed the text colour of the subtitle, footer and axis labels
- Remove underlines for links in the footer
- If the footer text is long, decrease its font size
- SlopeChart: Hide the vertical axes on the sides

---

- Mobile optimizations are also applied to thumbnails generated for social media:
    - chart: [old](https://ourworldindata.org/grapher/thumbnail/life-expectancy?imType=og) / [new](http://staging-site-export-charts-for-mobile/grapher/thumbnail/life-expectancy?imType=og)
    - map: [old](https://ourworldindata.org/grapher/thumbnail/burden-of-disease-who?imType=og) / [new](http://staging-site-export-charts-for-mobile/grapher/thumbnail/burden-of-disease-who?imType=og)
  • Loading branch information
sophiamersmann authored Jan 9, 2024
2 parents 635db10 + f59fec2 commit 89ddd73
Show file tree
Hide file tree
Showing 27 changed files with 455 additions and 184 deletions.
4 changes: 4 additions & 0 deletions adminSiteClient/admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions packages/@ourworldindata/grapher/src/axis/Axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand Down Expand Up @@ -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 {
Expand Down
61 changes: 47 additions & 14 deletions packages/@ourworldindata/grapher/src/axis/AxisViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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}
/>
</g>
)
Expand All @@ -147,32 +153,40 @@ interface DualAxisViewProps {
dualAxis: DualAxis
highlightValue?: { x: number; y: number }
showTickMarks?: boolean
labelColor?: string
tickColor?: string
lineWidth?: number
}

@observer
export class DualAxisComponent extends React.Component<DualAxisViewProps> {
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 : (
<VerticalAxisGridLines
verticalAxis={verticalAxis}
bounds={innerBounds}
strokeWidth={lineWidth}
/>
)

const horizontalGridlines = horizontalAxis.hideGridlines ? null : (
<HorizontalAxisGridLines
horizontalAxis={horizontalAxis}
bounds={innerBounds}
strokeWidth={lineWidth}
/>
)

const verticalAxisComponent = verticalAxis.hideAxis ? null : (
<VerticalAxisComponent
bounds={bounds}
verticalAxis={verticalAxis}
labelColor={labelColor}
tickColor={tickColor}
/>
)

Expand All @@ -182,6 +196,9 @@ export class DualAxisComponent extends React.Component<DualAxisViewProps> {
axis={horizontalAxis}
showTickMarks={showTickMarks}
preferredAxisPosition={innerBounds.bottom}
labelColor={labelColor}
tickColor={tickColor}
tickMarkWidth={lineWidth}
/>
)

Expand All @@ -200,11 +217,12 @@ export class DualAxisComponent extends React.Component<DualAxisViewProps> {
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 (
<g className="VerticalAxis">
Expand All @@ -214,6 +232,7 @@ export class VerticalAxisComponent extends React.Component<{
bounds.left,
{
transform: "rotate(-90)",
fill: labelColor || GRAPHER_DARK_TEXT,
}
)}
{tickLabels.map((label, i) => {
Expand All @@ -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}
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -286,6 +314,7 @@ export class HorizontalAxisComponent extends React.Component<{
axis.place(label.value)
)}
color={SOLID_TICK_COLOR}
width={tickMarkWidth}
/>
) : undefined

Expand All @@ -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) => {
Expand All @@ -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
)}
Expand All @@ -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) => {
Expand All @@ -340,6 +372,7 @@ export class AxisTickMarks extends React.Component<{
x2={tickMarkPosition}
y2={tickBottom}
stroke={color}
strokeWidth={width}
/>
)
})
Expand Down
55 changes: 37 additions & 18 deletions packages/@ourworldindata/grapher/src/barCharts/DiscreteBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -393,6 +398,7 @@ export class DiscreteBarChart
)

const {
manager,
series,
boundsWithoutColorLegend,
yAxis,
Expand All @@ -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 (
<g ref={this.base} className="DiscreteBarChart">
<rect
Expand All @@ -421,11 +431,14 @@ export class DiscreteBarChart
bounds={boundsWithoutColorLegend}
axis={yAxis}
preferredAxisPosition={innerBounds.bottom}
labelColor={manager.secondaryColorInStaticCharts}
tickMarkWidth={axisLineWidth}
/>
)}
<HorizontalAxisGridLines
horizontalAxis={yAxis}
bounds={innerBounds}
strokeWidth={axisLineWidth}
/>
{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
Expand Down Expand Up @@ -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" }}
/>
<text
x={0}
y={0}
transform={`translate(${
yAxis.place(series.value) +
(isNegative
? -labelToBarPadding
: labelToBarPadding)
}, 0)`}
fill="#666"
dominantBaseline="middle"
textAnchor={isNegative ? "end" : "start"}
{...this.valueLabelStyle}
>
{label.valueString}
<tspan fill="#999">{label.timeString}</tspan>
</text>
{getElementWithHalo(
series.seriesName + "-label",
<text
x={0}
y={0}
transform={`translate(${
yAxis.place(series.value) +
(isNegative
? -labelToBarPadding
: labelToBarPadding)
}, 0)`}
fill={GRAPHER_DARK_TEXT}
dominantBaseline="middle"
textAnchor={isNegative ? "end" : "start"}
{...this.valueLabelStyle}
>
{label.valueString}
<tspan fill="#999">
{label.timeString}
</tspan>
</text>
)}
</g>
)

Expand All @@ -502,6 +520,7 @@ export class DiscreteBarChart
<HorizontalAxisZeroLine
horizontalAxis={yAxis}
bounds={innerBounds}
strokeWidth={axisLineWidth}
/>
)}
</g>
Expand Down
4 changes: 4 additions & 0 deletions packages/@ourworldindata/grapher/src/chart/ChartManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,8 @@ export interface ChartManager {
disableIntroAnimation?: boolean

missingDataStrategy?: MissingDataStrategy

isNarrow?: boolean
isStaticAndSmall?: boolean
secondaryColorInStaticCharts?: string
}
Loading

0 comments on commit 89ddd73

Please sign in to comment.