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

Component | LeafletMap: POC of having an object of CSS variable names #171

Merged
merged 1 commit into from
Apr 26, 2023
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
1 change: 1 addition & 0 deletions packages/ts/src/components/leaflet-map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {

export class LeafletMap<Datum extends GenericDataRecord> extends ComponentCore<Datum[], LeafletMapConfig<Datum>, LeafletMapConfigInterface<Datum>> {
static selectors = s
static cssVariables = s.variables
type = ComponentType.HTML
element: HTMLElement
config: LeafletMapConfig<Datum> = new LeafletMapConfig()
Expand Down
10 changes: 6 additions & 4 deletions packages/ts/src/components/leaflet-map/modules/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { estimateTextSize, trimTextMiddle } from 'utils/text'
import { clamp, getString } from 'utils/data'
import { getCSSVariableValueInPixels, rectIntersect } from 'utils/misc'
import { hexToBrightness } from 'utils/color'
import { cssvar } from 'utils/style'
import { getPointPos } from './utils'


// Local Types
import { LeafletMapPointDatum, LeafletMapClusterDatum, LeafletMapPoint, LeafletMapPointShape } from '../types'

Expand Down Expand Up @@ -122,14 +124,14 @@ export function updateNodes<D extends GenericDataRecord> (

const brightness = hexToBrightness(hex)
return brightness > 0.5
? (isCluster ? 'var(--vis-map-cluster-inner-label-text-color-dark)' : 'var(--vis-map-point-inner-label-text-color-dark)')
: (isCluster ? 'var(--vis-map-cluster-inner-label-text-color-light)' : 'var(--vis-map-point-inner-label-text-color-light)')
? (isCluster ? cssvar(s.variables.mapClusterInnerLabelTextColorDark) : cssvar(s.variables.mapPointInnerLabelTextColorDark))
: (isCluster ? cssvar(s.variables.mapClusterInnerLabelTextColorLight) : cssvar(s.variables.mapPointInnerLabelTextColorLight))
})

const bottomLabelTextTrimmed = trimTextMiddle(bottomLabelText, 15)
bottomLabel
.text(bottomLabelTextTrimmed)
.attr('font-size', getCSSVariableValueInPixels('var(--vis-map-point-bottom-label-font-size)', selection.node()))
.attr('font-size', getCSSVariableValueInPixels(cssvar(s.variables.mapPointBottomLabelFontSize), selection.node()))
.attr('visibility', fromExpandedCluster ? 'hidden' : null)
})
}
Expand All @@ -146,7 +148,7 @@ export function collideLabels<D extends GenericDataRecord> (
group1HTMLNode['labelVisible'] = true

// Calculate bounding rect of point's bottom label
const bottomLabelFontSizePx = getCSSVariableValueInPixels('var(--vis-map-point-bottom-label-font-size)', selection.node())
const bottomLabelFontSizePx = getCSSVariableValueInPixels(cssvar(s.variables.mapPointBottomLabelFontSize), selection.node())
const p1Pos = getPointPos(datum1, leafletMap)
const label1Size = estimateTextSize(label1, bottomLabelFontSizePx, 0.32, true, 0.6)
const label1BoundingRect: Rect = {
Expand Down
147 changes: 65 additions & 82 deletions packages/ts/src/components/leaflet-map/style.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,77 @@
import { css, injectGlobal } from '@emotion/css'

// Utils
import { getCssVarNames, injectGlobalCssVariables } from 'utils/style'

// Inject Leaflet global style
// eslint-disable-next-line
import leafletCSS from './leaflet.css'
injectGlobal(leafletCSS)

export const root = css`
label: leaflet-map-component;

width: 100%;
height: 100%;
position: absolute;
background-color: var(--vis-map-container-background-color);

canvas {
pointer-events: all;
}
`
const cssVarDefaults = {
'--vis-map-container-background-color': '#dfe5eb',
/* Undefined by default to allow proper fallback to var(--vis-font-family) */
'--vis-map-label-font-family': undefined,

export const variables = injectGlobal`
:root {
--vis-map-container-background-color: #dfe5eb;
// Undefined by default to allow proper fallback to var(--vis-font-family)
/* --vis-map-label-font-family, var(--vis-font-family): */
'--vis-map-point-default-fill-color': '#B9BEC3',
'--vis-map-point-ring-fill-color': '#ffffff',
'--vis-map-point-default-stroke-color': '#959da3',
'--vis-map-point-default-stroke-width': '0px',
'--vis-map-point-default-cursor': 'default',

--vis-map-point-default-fill-color: #B9BEC3;
--vis-map-point-ring-fill-color: #ffffff;
--vis-map-point-default-stroke-color: #959da3;
--vis-map-point-default-stroke-width: 0px;
--vis-map-point-default-cursor: default;
'--vis-map-cluster-default-fill-color': '#fff',
'--vis-map-cluster-default-stroke-color': '#B9BEC3',
'--vis-map-cluster-default-stroke-width': '1.5px',
'--vis-map-cluster-donut-fill-color': '#959da3',

--vis-map-cluster-default-fill-color: #fff;
--vis-map-cluster-default-stroke-color: #B9BEC3;
--vis-map-cluster-default-stroke-width: 1.5px;
--vis-map-cluster-donut-fill-color: #959da3;
'--vis-map-cluster-inner-label-text-color-dark': '#5b5f6d',
'--vis-map-cluster-inner-label-text-color-light': '#fff',

--vis-map-cluster-inner-label-text-color-dark: #5b5f6d;
--vis-map-cluster-inner-label-text-color-light: #fff;
'--vis-map-point-inner-label-text-color-dark': '#5b5f6d',
'--vis-map-point-inner-label-text-color-light': '#fff',

--vis-map-point-inner-label-text-color-dark: #5b5f6d;
--vis-map-point-inner-label-text-color-light: #fff;
'--vis-map-point-bottom-label-text-color': '#5b5f6d',
'--vis-map-point-bottom-label-font-size': '10px',

--vis-map-point-bottom-label-text-color: #5b5f6d;
--vis-map-point-bottom-label-font-size: 10px;
'--vis-map-cluster-expanded-background-fill-color': '#fff',

--vis-map-cluster-expanded-background-fill-color: #fff;
/* Dark Theme */
'--vis-dark-map-container-background-color': '#dfe5eb',
'--vis-dark-map-point-default-fill-color': '#B9BEC3',
'--vis-dark-map-point-default-stroke-color': '#959da3',
'--vis-dark-map-point-ring-fill-color': '#5b5f6d',

/* Dark Theme */
--vis-dark-map-container-background-color: #dfe5eb;
--vis-dark-map-point-default-fill-color: #B9BEC3;
--vis-dark-map-point-default-stroke-color: #959da3;
--vis-dark-map-point-ring-fill-color: #5b5f6d;
'--vis-dark-map-cluster-default-fill-color': '#5b5f6d',
'--vis-dark-map-cluster-default-stroke-color': '#B9BEC3',
'--vis-dark-map-cluster-donut-fill-color': '#959da3',

--vis-dark-map-cluster-default-fill-color: #5b5f6d;
--vis-dark-map-cluster-default-stroke-color: #B9BEC3;
--vis-dark-map-cluster-donut-fill-color: #959da3;
'--vis-dark-map-cluster-inner-label-text-color-dark': '#5b5f6d',
'--vis-dark-map-cluster-inner-label-text-color-light': '#fff',

--vis-dark-map-cluster-inner-label-text-color-dark: #5b5f6d;
--vis-dark-map-cluster-inner-label-text-color-light: #fff;
'--vis-dark-map-point-inner-label-text-color-dark': '#5b5f6d',
'--vis-dark-map-point-inner-label-text-color-light': '#fff',

--vis-dark-map-point-inner-label-text-color-dark: #5b5f6d;
--vis-dark-map-point-inner-label-text-color-light: #fff;
'--vis-dark-map-point-bottom-label-text-color': '#5b5f6d',

--vis-dark-map-point-bottom-label-text-color: #eee;
'--vis-dark-map-cluster-expanded-background-fill-color': '#fff',
}

--vis-dark-map-cluster-expanded-background-fill-color: #fff;
}

body.theme-dark ${`.${root}`} {
--vis-map-container-background-color: var(--vis-dark-map-container-background-color);
--vis-map-point-default-fill-color: var(--vis-dark-map-point-default-fill-color);
--vis-map-point-default-stroke-color: var(--vis-dark-map-point-default-stroke-color);
--vis-map-point-ring-fill-color: var(--vis-dark-map-point-ring-fill-color);

--vis-map-cluster-default-fill-color: var(--vis-dark-map-cluster-default-fill-color);
--vis-map-cluster-default-stroke-color: var(--vis-dark-map-cluster-default-stroke-color);
--vis-map-cluster-donut-fill-color: var(--vis-dark-map-cluster-donut-fill-color);

--vis-map-cluster-inner-label-text-color-dark: var(--vis-dark-map-cluster-inner-label-text-color-dark);
--vis-map-cluster-inner-label-text-color-light: var(--vis-dark-map-cluster-inner-label-text-color-light);

--vis-map-point-inner-label-text-color-dark: var(--vis-dark-map-point-inner-label-text-color-dark);
--vis-map-point-inner-label-text-color-light: var(--vis-dark-map-point-inner-label-text-color-light);
export const root = css`
label: leaflet-map-component;

--vis-map-point-bottom-label-text-color: var(--vis-dark-map-point-bottom-label-text-color);
width: 100%;
height: 100%;
position: absolute;
background-color: var(--vis-map-container-background-color);

--vis-map-cluster-expanded-background-fill-color: var(--vis-dark-map-cluster-expanded-background-fill-color);
canvas {
pointer-events: all;
}
`

export const variables = getCssVarNames(cssVarDefaults)
injectGlobalCssVariables(cssVarDefaults, root)

export const background = `${root} canvas`

export const points = css`
Expand All @@ -104,12 +87,12 @@ export const pointPath = css`

stroke-opacity: 1;
fill-opacity: 1.0;
fill: var(--vis-map-point-default-fill-color);
stroke: var(--vis-map-point-default-stroke-color);
stroke-width: var(--vis-map-point-default-stroke-width);
fill: var(${variables.mapPointDefaultFillColor});
stroke: var(${variables.mapPointDefaultStrokeColor});
stroke-width: var(${variables.mapPointDefaultStrokeWidth});
pointer-events: fill !important;
transition: .2s stroke-width, .3s transform;
cursor: var(--vis-map-point-default-cursor);
cursor: var(${variables.mapPointDefaultCursor});

&:hover {
stroke-width: 2;
Expand All @@ -121,22 +104,22 @@ export const pointPath = css`

export const pointPathRing = css`
label: point-path-ring;
fill: var(--vis-map-point-ring-fill-color);
fill: var(${variables.mapPointRingFillColor});
`

export const pointPathCluster = css`
label: point-path-cluster;
fill-opacity: 0.9;
stroke: none;
animation: none;
fill: var(--vis-map-cluster-default-fill-color);
stroke: var(--vis-map-cluster-default-stroke-color);
stroke-width: var(--vis-map-cluster-default-stroke-width);
fill: var(${variables.mapClusterDefaultFillColor});
stroke: var(${variables.mapClusterDefaultStrokeColor});
stroke-width: var(${variables.mapClusterDefaultStrokeWidth});
`

export const pointSelectionRing = css`
label: point-selection-ring;
stroke: var(--vis-map-point-default-fill-color);
stroke: var(${variables.mapPointDefaultFillColor});
`

export const pointSelection = css`
Expand All @@ -156,23 +139,23 @@ export const innerLabel = css`
label: inner-label;

text-anchor: middle;
fill: var(--vis-map-point-inner-label-text-color-dark);
font-family: var(--vis-map-label-font-family, var(--vis-font-family));
fill: var(${variables.mapPointInnerLabelTextColorDark});
font-family: var(${variables.mapLabelFontFamily}, var(--vis-font-family));
pointer-events: none;
font-weight: 600;
`

export const innerLabelCluster = css`
label: inner-label-cluster;
fill: var(--vis-map-point-inner-label-text-color-dark);
fill: var(${variables.mapPointInnerLabelTextColorDark});
`

export const bottomLabel = css`
label: bottom-label;

text-anchor: middle;
fill: var(--vis-map-point-bottom-label-text-color);
font-family: var(--vis-map-label-font-family, var(--vis-font-family));
fill: var(${variables.mapPointBottomLabelTextColor});
font-family: var(${variables.mapLabelFontFamily}, var(--vis-font-family));
pointer-events: none;
font-weight: 600;
`
Expand All @@ -183,7 +166,7 @@ export const donutCluster = css`
transform: scale(1);
transition: .3s transform;
path {
fill: var(--vis-map-cluster-donut-fill-color);
fill: var(${variables.mapClusterDonutFillColor});
stroke-width: 0.5;
}

Expand All @@ -207,7 +190,7 @@ export const backgroundRect = css`
export const clusterBackground = css`
label: cluster-background;

fill: var(--vis-map-cluster-expanded-background-fill-color);
fill: var(${variables.mapClusterExpandedBackgroundFillColor});
opacity: 0.6;
visibility: hidden;

Expand Down
37 changes: 37 additions & 0 deletions packages/ts/src/utils/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { injectGlobal } from '@emotion/css'

import { kebabCaseToCamel } from 'utils/text'
import type { KebabToCamelCase, RemovePrefix } from 'utils/type'

export function getCssVarNames<
T extends Record<string, unknown>,
Prefix extends string = '--vis-',
> (cssVarsObject: T, prefix?: Prefix): {
[Property in Extract<keyof T, string> as KebabToCamelCase<RemovePrefix<Property, Prefix>>]: Property
} {
const defaultPrefix = '--vis-'
const entries = Object.entries(cssVarsObject)
return Object.fromEntries(
entries.map(([key]) => [kebabCaseToCamel(key.replace(prefix ?? defaultPrefix, '')), key])
) as {
[Property in Extract<keyof T, string> as KebabToCamelCase<RemovePrefix<Property, Prefix>>]: Property
}
}

export function injectGlobalCssVariables<T extends Record<string, string | undefined>> (
cssVarsObject: T,
componentRootClassName: string
): void {
injectGlobal({
':root': cssVarsObject,
[`body.theme-dark .${componentRootClassName}`]: Object.keys(cssVarsObject)
.filter(key => key.includes('--vis-dark'))
.map(key => ({
[key.replace('--vis-dark', '--vis')]: `var(${key})`,
})),
})
}

export function cssvar<T extends string> (name: T): `var(${T})` {
return `var(${name})` as `var(${T})`
}
11 changes: 11 additions & 0 deletions packages/ts/src/utils/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ import { TrimMode, VerticalAlign, WrapMode, WrapTextOptions } from 'types/text'
// Utils
import { isArray, flatten } from 'utils/data'

export function kebabCaseToCamel (str: string): string {
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
}

export function kebabCase (str: string): string {
return str.match(/[A-Z]{2,}(?=[A-Z][a-z0-9]*|\b)|[A-Z]?[a-z0-9]*|[A-Z]|[0-9]+/g)
?.filter(Boolean)
.map(x => x.toLowerCase())
.join('-')
}

export function trimTextStart (str = '', maxLength = 15): string {
return str.length > maxLength ? `…${str.substr(str.length - maxLength, maxLength)}` : str
}
Expand Down
9 changes: 9 additions & 0 deletions packages/ts/src/utils/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type RemovePrefix<T extends string, Prefix extends string> = T extends `${Prefix}${infer Tail}` ? `${Tail}` : T
export type KebabToCamelCase<T extends string> =
T extends `${infer Head}-${infer Tail}`
? `${Head}${Capitalize<KebabToCamelCase<Tail>>}`
: T;

export type CamelCase<T> = {
[Property in keyof T as KebabToCamelCase<Property & string>]: T[Property];
}