Skip to content

Commit

Permalink
✨ (stacked discrete bar) optimise static version for Figma
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Oct 14, 2024
1 parent c37b77f commit 5d44213
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend {

return (
<rect
id={makeIdForHumanConsumption(mark.label.text)}
key={`${mark.label}-${index}`}
x={this.legendX + mark.x}
y={this.categoryLegendY + mark.y}
Expand Down
4 changes: 4 additions & 0 deletions packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ export class MapChart
return 0
}

@computed get isStatic(): boolean {
return this.manager.isStatic ?? false
}

renderMapLegend(): React.ReactElement {
const { numericLegend, categoryLegend } = this

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export class SlopeChart
return this.props.bounds ?? DEFAULT_BOUNDS
}

@computed get isStatic(): boolean {
return this.manager.isStatic ?? false
}

@computed get fontSize() {
return this.manager.fontSize ?? BASE_FONT_SIZE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
ColorSchemeName,
EntitySelectionMode,
makeIdForHumanConsumption,
getCountryByName,
dyFromAlign,
} from "@ourworldindata/utils"
import { action, computed, observable } from "mobx"
Expand Down Expand Up @@ -566,6 +565,10 @@ export class MarimekkoChart
.padLeft(marginToEnsureWidestEntityLabelFitsEvenIfAtX0)
}

@computed get isStatic(): boolean {
return this.manager.isStatic ?? false
}

@computed private get baseFontSize(): number {
return this.manager.fontSize ?? BASE_FONT_SIZE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ import { HashMap, NodeGroup } from "react-move"
import { easeQuadOut } from "d3-ease"
import { bind } from "decko"
import { CategoricalColorAssigner } from "../color/CategoricalColorAssigner.js"
import { Halo } from "../halo/Halo"
import { TextWrap } from "@ourworldindata/components"

// if an entity name exceeds this width, we use the short name instead (if available)
Expand Down Expand Up @@ -606,10 +605,14 @@ export class StackedDiscreteBarChart
/>
)

const { manager, bounds, yAxis, innerBounds } = this
return this.manager.isStatic
? this.renderStatic()
: this.renderInteractive()
}

const chartContext: StackedBarChartContext = {
yAxis,
@computed get chartContext(): StackedBarChartContext {
return {
yAxis: this.yAxis,
targetTime: this.manager.endTime,
timeColumn: this.inputTable.timeColumn,
formatColumn: this.formatColumn,
Expand All @@ -621,98 +624,76 @@ export class StackedDiscreteBarChart
x0: this.x0,
baseFontSize: this.baseFontSize,
}
}

const handlePositionUpdate = (d: PlacedItem): HashMap => ({
translateY: [d.yPosition],
timing: { duration: 350, ease: easeQuadOut },
})

const renderRow = ({
data,
state,
}: {
data: PlacedItem
state: { translateY: number }
}): React.ReactElement => {
const { entityName, label, bars, totalValue } = data
renderRow({
data,
state,
}: {
data: PlacedItem
state: { translateY: number }
}): React.ReactElement {
const { yAxis } = this
const { entityName, label, bars, totalValue } = data

const totalLabel = this.formatValueForLabel(totalValue)
const showLabelInsideBar = bars.length > 1
const totalLabel = this.formatValueForLabel(totalValue)
const showLabelInsideBar = bars.length > 1

return (
<g
key={entityName}
id={makeIdForHumanConsumption("bar", entityName)}
className="bar"
transform={`translate(0, ${state.translateY})`}
>
{label.render(
yAxis.place(this.x0) - labelToBarPadding,
-label.height / 2,
{
textProps: {
textAnchor: "end",
fill: "#555",
onMouseEnter: (): void =>
this.onEntityMouseEnter(label.text),
onMouseLeave: this.onEntityMouseLeave,
},
}
)}
return (
<g
key={entityName}
id={makeIdForHumanConsumption(entityName)}
className="bar"
transform={`translate(0, ${state.translateY ?? 0})`}
>
{bars.map((bar) => (
<StackedDiscreteBarChart.Bar
key={bar.seriesName}
entity={entityName}
bar={bar}
chartContext={this.chartContext}
showLabelInsideBar={showLabelInsideBar}
onMouseEnter={this.onEntityMouseEnter}
onMouseLeave={this.onEntityMouseLeave}
/>
))}
{label.render(
yAxis.place(this.x0) - labelToBarPadding,
-label.height / 2,
{
textProps: {
textAnchor: "end",
fill: "#555",
onMouseEnter: (): void =>
this.onEntityMouseEnter(label.text),
onMouseLeave: this.onEntityMouseLeave,
},
}
)}
{this.showTotalValueLabel && (
<text
transform={`translate(${
yAxis.place(totalValue) + labelToBarPadding
}, 0)`}
dy={dyFromAlign(VerticalAlign.middle)}
{...this.totalValueLabelStyle}
>
{totalLabel}
</text>
)}
</g>
)
}

{bars.map((bar) => (
<StackedDiscreteBarChart.Bar
key={bar.seriesName}
entity={entityName}
bar={bar}
chartContext={chartContext}
showLabelInsideBar={showLabelInsideBar}
onMouseEnter={this.onEntityMouseEnter}
onMouseLeave={this.onEntityMouseLeave}
/>
))}
{this.showTotalValueLabel && (
<Halo
id={entityName + "-value-label"}
background={this.manager.backgroundColor}
>
<text
id={makeIdForHumanConsumption(
"total",
entityName
)}
transform={`translate(${
yAxis.place(totalValue) + labelToBarPadding
}, 0)`}
dy={dyFromAlign(VerticalAlign.middle)}
{...this.totalValueLabelStyle}
>
{totalLabel}
</text>
</Halo>
)}
</g>
)
}
renderAxis(): React.ReactElement {
const { manager, bounds, yAxis, innerBounds } = this

const axisLineWidth = manager.isStaticAndSmall
? GRAPHER_AXIS_LINE_WIDTH_THICK
: GRAPHER_AXIS_LINE_WIDTH_DEFAULT

return (
<g
ref={this.base}
className="StackedDiscreteBarChart"
onMouseMove={this.onMouseMove}
>
<rect
x={bounds.left}
y={bounds.top}
width={bounds.width}
height={bounds.height}
opacity={0}
fill="rgba(255,255,255,0)"
/>
<>
{this.showHorizontalAxis && (
<>
<HorizontalAxisComponent
Expand All @@ -737,33 +718,75 @@ export class StackedDiscreteBarChart
// overlap with the bars
align={HorizontalAlign.right}
/>
{this.showLegend && (
<HorizontalCategoricalColorLegend manager={this} />
)}
</>
)
}

renderLegend(): React.ReactElement | void {
if (!this.showLegend) return
return <HorizontalCategoricalColorLegend manager={this} />
}

renderStatic(): React.ReactElement {
return (
<>
{this.renderAxis()}
{this.renderLegend()}
<g id={makeIdForHumanConsumption("bars")}>
{this.placedItems.map((item) =>
this.renderRow({
data: item,
state: { translateY: item.yPosition },
})
)}
</g>
</>
)
}

renderInteractive(): React.ReactElement {
const { bounds } = this

const handlePositionUpdate = (d: PlacedItem): HashMap => ({
translateY: [d.yPosition],
timing: { duration: 350, ease: easeQuadOut },
})

// needs to be referenced here, otherwise it's not updated in the renderRow function
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.focusSeriesName

return (
<g
ref={this.base}
className="StackedDiscreteBarChart"
onMouseMove={this.onMouseMove}
>
<rect
x={bounds.left}
y={bounds.top}
width={bounds.width}
height={bounds.height}
opacity={0}
fill="rgba(255,255,255,0)"
/>
{this.renderAxis()}
{this.renderLegend()}
<NodeGroup
data={this.placedItems}
keyAccessor={(d: PlacedItem): string => d.entityName}
start={handlePositionUpdate}
update={handlePositionUpdate}
>
{(nodes): React.ReactElement => (
<g>{nodes.map((node) => renderRow(node))}</g>
<g>{nodes.map((node) => this.renderRow(node))}</g>
)}
</NodeGroup>
{this.Tooltip}
{this.tooltip}
</g>
)
}

@computed get activeColors(): string[] | undefined {
const { focusSeriesName } = this
if (!focusSeriesName) return undefined
const activeColors = this.series
.filter((series) => series.seriesName === focusSeriesName)
.map((series) => series.color)
return activeColors.length === 0 ? undefined : activeColors
}

private static Bar(props: {
bar: Bar
entity: string
Expand Down Expand Up @@ -799,13 +822,14 @@ export class StackedDiscreteBarChart

return (
<g
id={makeIdForHumanConsumption("stacked-bar", bar.seriesName)}
id={makeIdForHumanConsumption(bar.seriesName)}
onMouseEnter={(): void =>
props?.onMouseEnter(entity, bar.seriesName)
}
onMouseLeave={props?.onMouseLeave}
>
<rect
id={makeIdForHumanConsumption("bar")}
x={0}
y={0}
transform={`translate(${barX}, ${-barHeight / 2})`}
Expand Down Expand Up @@ -842,7 +866,7 @@ export class StackedDiscreteBarChart
)
}

@computed private get Tooltip(): React.ReactElement | undefined {
@computed private get tooltip(): React.ReactElement | undefined {
const {
tooltipState: { target, position, fading },
formatColumn: { unit, shortUnit },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export class VerticalColorLegend extends React.Component<{

return (
<rect
id={makeIdForHumanConsumption(series.textWrap.text)}
key={index}
x={this.legendX}
y={renderedTextPosition[1] - rectSize}
Expand Down

0 comments on commit 5d44213

Please sign in to comment.