Skip to content

Commit

Permalink
✨ (map) refine globe implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Jun 12, 2024
1 parent 4634316 commit 9bfaa71
Show file tree
Hide file tree
Showing 28 changed files with 967 additions and 388 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ import { MapChartManager } from "../mapCharts/MapChartConstants"
import { ChartManager } from "../chart/ChartManager"
import { LoadingIndicator } from "../loadingIndicator/LoadingIndicator"
import { FacetChart } from "../facetChart/FacetChart"
import {
faEarthAmericas,
faExternalLinkAlt,
faMap,
} from "@fortawesome/free-solid-svg-icons"
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
import { FooterManager } from "../footer/FooterManager"
import { HeaderManager } from "../header/HeaderManager"
Expand All @@ -51,7 +47,6 @@ import {
ControlsRow,
ControlsRowManager,
} from "../controls/controlsRow/ControlsRow"
import { LabeledSwitch } from "../controls/LabeledSwitch.js"

export interface CaptionedChartManager
extends ChartManager,
Expand Down Expand Up @@ -337,15 +332,6 @@ export class CaptionedChart extends React.Component<CaptionedChartProps> {
height: chartHeight,
}

const globeSwitcher: React.CSSProperties = {
height: "40px",
width: "fit-content",
margin: "10px",
display: "flex",
position: "absolute",
bottom: "0",
}

return (
<div style={containerStyle}>
<svg
Expand Down
1 change: 0 additions & 1 deletion packages/@ourworldindata/grapher/src/chart/ChartManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export interface ChartManager {
table: OwidTable
transformedTable?: OwidTable

isGlobe?: boolean
isExportingToSvgOrPng?: boolean
isRelativeMode?: boolean
comparisonLines?: ComparisonLineConfig[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ export class ContentSwitchers extends React.Component<{
}
}

render(): React.ReactElement {
render(): React.ReactElement | null {
const { manager } = this
if (!this.showTabs) return null
return (
<ul
className={classnames({
Expand Down
4 changes: 4 additions & 0 deletions packages/@ourworldindata/grapher/src/controls/Dropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
display: grid;
grid-template-columns: minmax(0, 1fr);

.placeholder {
white-space: nowrap;
}

.control {
min-height: auto;
font: $medium 13px/16px $lato;
Expand Down
1 change: 1 addition & 0 deletions packages/@ourworldindata/grapher/src/controls/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function Dropdown(props: Props): React.ReactElement {
})
},
menu: () => "menu",
placeholder: () => "placeholder",
}}
{...props}
/>
Expand Down
56 changes: 33 additions & 23 deletions packages/@ourworldindata/grapher/src/controls/MapProjectionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MapProjectionName } from "@ourworldindata/types"
import { MapProjectionLabels } from "../mapCharts/MapProjections"
import { Dropdown } from "./Dropdown"
import { DEFAULT_BOUNDS } from "@ourworldindata/utils"
import { GlobeController } from "../mapCharts/GlobeController"

export { AbsRelToggle } from "./settings/AbsRelToggle"
export { FacetStrategySelector } from "./settings/FacetStrategySelector"
Expand All @@ -18,6 +19,8 @@ export interface MapProjectionMenuManager {
mapConfig?: MapConfig
isOnMapTab?: boolean
hideMapProjectionMenu?: boolean
globeController?: GlobeController
isGlobe?: boolean
}

interface MapProjectionMenuItem {
Expand All @@ -30,54 +33,61 @@ export class MapProjectionMenu extends React.Component<{
manager: MapProjectionMenuManager
maxWidth?: number
}> {
static shouldShow(manager: MapProjectionMenuManager): boolean {
const menu = new MapProjectionMenu({ manager })
return menu.showMenu
}

@computed get showMenu(): boolean {
const { hideMapProjectionMenu, isOnMapTab, mapConfig } =
this.props.manager,
{ projection } = mapConfig ?? {}
return !hideMapProjectionMenu && !!(isOnMapTab && projection)
}

@computed private get maxWidth(): number {
return this.props.maxWidth ?? DEFAULT_BOUNDS.width
}

@action.bound onChange(selected: unknown): void {
const { mapConfig } = this.props.manager
if (selected && mapConfig)
mapConfig.projection = (selected as MapProjectionMenuItem).value
if (selected && mapConfig) {
const projection = (selected as MapProjectionMenuItem).value
mapConfig.projection = projection

void this.props.manager.globeController?.rotateToProjection(
projection
)
}
}

@computed get options(): MapProjectionMenuItem[] {
return Object.values(MapProjectionName).map((projectName) => {
return {
value: projectName,
label: MapProjectionLabels[projectName],
}
})
return Object.values(MapProjectionName)
.filter((projectionName) =>
this.props.manager.isGlobe
? projectionName !== MapProjectionName.World
: true
)
.map((projectionName) => {
return {
value: projectionName,
label: MapProjectionLabels[projectionName],
}
})
}

@computed get value(): MapProjectionMenuItem | null {
const { projection } = this.props.manager.mapConfig ?? {}
return this.options.find((opt) => projection === opt.value) ?? null
const option =
this.options.find((opt) => projection === opt.value) ?? null
if (this.props.manager.isGlobe) return option
const world =
this.options.find((opt) => opt.value === MapProjectionName.World) ??
null
return option ?? world
}

render(): React.ReactElement | null {
return this.showMenu ? (
return (
<div
className="map-projection-menu"
style={{ maxWidth: this.maxWidth }}
>
<Dropdown
placeholder="Select continent..."
options={this.options}
onChange={this.onChange}
value={this.value}
/>
</div>
) : null
)
}
}
125 changes: 92 additions & 33 deletions packages/@ourworldindata/grapher/src/controls/SettingsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ import {
TableFilterToggle,
TableFilterToggleManager,
} from "./settings/TableFilterToggle"
import { GlobeToggle, GlobeToggleManager } from "./settings/GlobeToggle"
import {
MapProjectionMenu,
MapProjectionMenuManager,
} from "./MapProjectionMenu"

import { OverlayHeader } from "../core/OverlayHeader"
import { DEFAULT_GRAPHER_ENTITY_TYPE_PLURAL } from "../core/GrapherConstants"

Expand All @@ -48,7 +54,9 @@ export interface SettingsMenuManager
FacetYDomainToggleManager,
ZoomToggleManager,
TableFilterToggleManager,
FacetStrategySelectionManager {
FacetStrategySelectionManager,
GlobeToggleManager,
MapProjectionMenuManager {
// ArchieML directives
hideFacetControl?: boolean
hideRelativeToggle?: boolean
Expand All @@ -59,6 +67,8 @@ export interface SettingsMenuManager
hideXScaleToggle?: boolean
hideYScaleToggle?: boolean
hideTableFilterToggle?: boolean
hideMapProjectionMenu?: boolean
hideGlobeToggle?: boolean

// chart state
type: ChartTypeName
Expand Down Expand Up @@ -95,7 +105,7 @@ export class SettingsMenu extends React.Component<{

static shouldShow(manager: SettingsMenuManager): boolean {
const test = new SettingsMenu({ manager, top: 0, bottom: 0, right: 0 })
return test.showSettingsMenuToggle
return test.showSettings
}

@computed get maxWidth(): number {
Expand Down Expand Up @@ -202,9 +212,19 @@ export class SettingsMenu extends React.Component<{
)
}

@computed get showSettingsMenuToggle(): boolean {
if (this.manager.isOnMapTab) return false
@computed get showGlobeToggle(): boolean {
return !this.manager.hideGlobeToggle
}

@computed get showMapProjectionMenu(): boolean {
return !this.manager.hideMapProjectionMenu
}

@computed get showSettings(): boolean {
if (this.manager.isOnTableTab) return this.showTableFilterToggle
if (this.manager.isOnMapTab) {
return this.showGlobeToggle || this.showMapProjectionMenu
}

return !!(
this.showYScaleToggle ||
Expand Down Expand Up @@ -265,11 +285,6 @@ export class SettingsMenu extends React.Component<{
return makeSelectionArray(this.manager.selection)
}

@computed get shouldRenderTableControlsIntoPopup(): boolean {
const tableFilterToggleWidth = TableFilterToggle.width(this.manager)
return tableFilterToggleWidth > this.maxWidth
}

@computed get layout(): {
maxHeight: string
maxWidth: string
Expand All @@ -282,6 +297,11 @@ export class SettingsMenu extends React.Component<{
return { maxHeight, maxWidth, top, right }
}

@computed private get shouldRenderTableToggleIntoPopup(): boolean {
const toggleWidth = TableFilterToggle.width(this.manager)
return toggleWidth > this.maxWidth
}

@computed get menuContentsChart(): React.ReactElement {
const {
manager,
Expand Down Expand Up @@ -378,17 +398,23 @@ export class SettingsMenu extends React.Component<{
)
}

@computed get menuContentsMap(): JSX.Element {
return (
<SettingsGroup title="Projection" active={true}>
<GlobeToggle manager={this.manager} />
</SettingsGroup>
)
}

@computed get menu(): JSX.Element | void {
if (this.active) {
return this.menuContents
}
if (this.active) return this.menuContents
}

@computed get menuContents(): JSX.Element {
const { manager, chartType } = this
const { isOnTableTab } = manager
const { isOnTableTab, isOnMapTab } = manager

const menuTitle = `${isOnTableTab ? "Table" : chartType} settings`
const menuTitle = `${isOnTableTab ? "Table" : isOnMapTab ? "Map" : chartType} settings`

return (
<div className="settings-menu-contents" ref={this.contentRef}>
Expand All @@ -410,15 +436,31 @@ export class SettingsMenu extends React.Component<{
<div className="settings-menu-controls">
{isOnTableTab
? this.menuContentsTable
: this.menuContentsChart}
: isOnMapTab
? this.menuContentsMap
: this.menuContentsChart}
</div>
</div>
</div>
)
}

renderSettingsButtonAndPopup(): JSX.Element {
const { active } = this
renderSettingsButtonAndPopup(): React.ReactElement | null {
const {
manager: { isOnTableTab, isOnMapTab },
active,
showSettings,
showGlobeToggle,
shouldRenderTableToggleIntoPopup,
} = this

if (
!showSettings ||
(isOnTableTab && !shouldRenderTableToggleIntoPopup) ||
(isOnMapTab && !showGlobeToggle)
)
return null

return (
<div className="settings-menu">
<button
Expand All @@ -437,28 +479,45 @@ export class SettingsMenu extends React.Component<{
)
}

renderTableControls(): React.ReactElement {
// Since tables only have a single control, display it inline rather than
// placing it in the settings menu
return <TableFilterToggle manager={this.manager} showTooltip={true} />
}

render(): React.ReactElement | null {
renderInlineSettings(): React.ReactElement | null {
const {
manager: { isOnChartTab, isOnTableTab },
showSettingsMenuToggle,
manager: { isOnTableTab, isOnMapTab },
showTableFilterToggle,
showMapProjectionMenu,
shouldRenderTableToggleIntoPopup,
} = this

if (isOnTableTab && showTableFilterToggle) {
return this.shouldRenderTableControlsIntoPopup
? this.renderSettingsButtonAndPopup()
: this.renderTableControls()
if (
isOnTableTab &&
showTableFilterToggle &&
!shouldRenderTableToggleIntoPopup
) {
return (
<TableFilterToggle manager={this.manager} showTooltip={true} />
)
}

if (isOnMapTab && showMapProjectionMenu) {
const settingsButtonWidth = 32
const dropdownMaxWidth = this.maxWidth - settingsButtonWidth - 8
return (
<MapProjectionMenu
manager={this.manager}
maxWidth={dropdownMaxWidth}
/>
)
}

return isOnChartTab && showSettingsMenuToggle
? this.renderSettingsButtonAndPopup()
: null
return null
}

render(): React.ReactElement | null {
return (
<>
{this.renderInlineSettings()}
{this.renderSettingsButtonAndPopup()}
</>
)
}
}

Expand Down
Loading

0 comments on commit 9bfaa71

Please sign in to comment.