Skip to content

Commit

Permalink
Add tick labels to ColorBar (#19)
Browse files Browse the repository at this point in the history
* add tick labels to ColorBar

add props tick_labels, range, tick_side, orientation
add ~10 CSS variable for external custom style

* ColorScaleSelect pipe cbar_props to ColorBar

* export prop key from PropertySelect for external binding

* refactor landing page to bind:key={heatmap_key} on PropertySelect

* add 2 ColorBar examples with tick labels to src/routes/(demos)/color-bar/+page.md

* ColorScaleSelect justify color scale name and bar with space-between
  • Loading branch information
janosh authored Mar 17, 2023
1 parent 2fdbb07 commit 7342930
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 37 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
},
"dependencies": {
"@iconify/svelte": "^3.1.0",
"@sveltejs/kit": "1.11.0",
"@sveltejs/kit": "1.12.0",
"d3-array": "^3.2.2",
"d3-color": "^3.1.0",
"d3-interpolate-path": "^2.3.0",
Expand Down Expand Up @@ -61,8 +61,8 @@
"remark-math": "3.0.0",
"sharp": "^0.31.3",
"svelte-check": "^3.1.4",
"svelte-preprocess": "^5.0.2",
"svelte-toc": "^0.5.3",
"svelte-preprocess": "^5.0.3",
"svelte-toc": "^0.5.4",
"svelte-zoo": "^0.4.3",
"svelte2tsx": "^0.6.10",
"typescript": "5.0.2",
Expand Down
77 changes: 63 additions & 14 deletions src/lib/ColorBar.svelte
Original file line number Diff line number Diff line change
@@ -1,41 +1,90 @@
<script lang="ts">
import { pretty_num } from '$lib/labels'
import * as d3sc from 'd3-scale-chromatic'
export let text: string = ``
export let color_scale: ((x: number) => string) | null = null
export let text: string | null = null
export let color_scale: ((x: number) => string) | string | null = null
export let text_side: 'left' | 'right' | 'top' | 'bottom' = `left`
export let style: string | null = null
export let text_style: string | null = null
export let wrapper_style: string | null = null
export let tick_labels: (string | number)[] | number = 0
export let range: [number, number] = []
export let tick_side: 'top' | 'bottom' | 'center' = `bottom`
// TODO vertical not fully implemented yet
export let orientation: 'horizontal' | 'vertical' = `horizontal`
$: if (color_scale === null) {
color_scale = d3sc[`interpolate${text}`]
if (color_scale === undefined) console.error(`Color scale not found: ${text}`)
$: if (
tick_labels?.length == 0 ||
typeof tick_labels == `number` ||
range?.length > 0
) {
const n_ticks = Array.isArray(tick_labels) ? 5 : tick_labels
tick_labels = [...Array(n_ticks).keys()].map((idx) => {
const x = idx / (n_ticks - 1)
return range[0] + x * (range[1] - range[0])
})
}
$: if (color_scale === null || typeof color_scale == `string`) {
color_scale = d3sc[`interpolate${color_scale ?? text}`]
if (color_scale === undefined) {
console.error(`Color scale '${color_scale ?? text}' not found`)
}
}
$: grad_dir = {
horizontal: `to right`,
vertical: `to bottom`,
}[orientation]
$: ramped = [...Array(10).keys()].map((idx) => color_scale?.(idx / 10))
$: flex_dir = {
left: `row`,
right: `row-reverse`,
top: `column`,
bottom: `column-reverse`,
}[text_side]
$: tick_pos = {
bottom: `top: 100%`,
top: `bottom: 100%`,
center: `top: 50%; transform: translateY(-50%)`,
}[tick_side]
</script>

<div style:flex-direction={flex_dir} style={wrapper_style}>
<div style:flex-direction={flex_dir} style={wrapper_style} class="colorbar">
{#if text}<span style={text_style}>{text}</span>{/if}
<div style:background="linear-gradient(to right, {ramped})" {style} />
<div style:background="linear-gradient({grad_dir}, {ramped})" {style}>
{#each tick_labels || [] as tick_label, idx}
<span style="left: calc(100% * {idx} / {tick_labels?.length - 1}); {tick_pos}">
{pretty_num(tick_label, 1)}
</span>
{/each}
</div>
</div>

<style>
div {
div.colorbar {
display: flex;
gap: 5pt;
width: max-content;
box-sizing: border-box;
place-items: center;
gap: var(--cbar-gap, 5pt);
margin: var(--cbar-margin);
padding: var(--cbar-padding);
width: var(--cbar-wrapper-width);
}
div.colorbar > div {
position: relative;
height: var(--cbar-height, 1em);
width: var(--cbar-width, 10em);
border-radius: var(--cbar-border-radius, 2pt);
}
div > div {
border-radius: 2pt;
height: 1em;
width: 10em;
div.colorbar > div > span {
position: absolute;
transform: translate(-50%);
font-weight: var(--cbar-tick-label-font-weight, lighter);
font-size: var(--cbar-tick-label-font-size, 10pt);
color: var(--cbar-tick-label-color);
background: var(--cbar-tick-label-bg);
}
</style>
6 changes: 4 additions & 2 deletions src/lib/ColorScaleSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
export let selected: string[] = [`Viridis`]
export let minSelect: number = 0
export let placeholder = `Select a color scale`
export let cbar_props: Record<string, unknown> = {}
const options = Object.keys(d3sc)
.filter((key) => key.startsWith(`interpolate`))
.map((key) => key.replace(`interpolate`, ``))
const wrapper_style = `justify-content: space-between;`
</script>

<Select
Expand All @@ -22,6 +24,6 @@
{placeholder}
{...$$props}
>
<ColorBar slot="option" let:option text={option} />
<ColorBar slot="selected" let:option text={option} />
<ColorBar slot="option" let:option text={option} {...cbar_props} {wrapper_style} />
<ColorBar slot="selected" let:option text={option} {...cbar_props} {wrapper_style} />
</Select>
3 changes: 2 additions & 1 deletion src/lib/PropertySelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
export let selected: string[] = empty ? [] : [options[1]]
export let minSelect: number = 0
export let id: string | null = null
export let key: string | null = null
$: $heatmap_key = heatmap_labels[value ?? ``] ?? null
$: $heatmap_key = key = heatmap_labels[value ?? ``] ?? null
</script>

<Select
Expand Down
78 changes: 76 additions & 2 deletions src/routes/(demos)/color-bar/+page.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
`ColorBar.svelte`

ColorBar supports <code>text_side = ['top', 'bottom', 'left', 'right']</code>
Here's a `ColorBar` with tick labels:

```svelte example stackblitz
<script>
import { ColorBar } from '$lib'
</script>
{#each [[`Viridis`, `top`, [0, 0.25, 0.5, 0.75, 1]], [`Magma`, `center`, [], [100, 1000]], [`Cividis`, `bottom`, [], [-100, -10]]] as [color_scale, tick_side, tick_labels, range]}
<ColorBar
text="{color_scale} (tick side={tick_side})"
{color_scale}
{tick_side}
{tick_labels}
{range}
--cbar-width="100%"
--cbar-padding="2em"
/>
{/each}
```

`ColorBar` supports `text_side = ['top', 'bottom', 'left', 'right']`

```svelte example stackblitz
<script>
Expand Down Expand Up @@ -46,7 +66,7 @@ ColorBar supports <code>text_side = ['top', 'bottom', 'left', 'right']</code>
</style>
```

You can also fat and skinny bars:
You can make fat and skinny bars:

```svelte example stackblitz
<script>
Expand All @@ -59,3 +79,57 @@ You can also fat and skinny bars:
<br />
<ColorBar text="Viridis" {wrapper_style} style="width: 3em; height: 2em;" />
```

`PeriodicTable.svelte` heatmap example with `ColorBar` inside `TableInset`

```svelte example stackblitz code_above
<script>
import {
ColorBar,
ColorScaleSelect,
element_data,
PeriodicTable,
PropertySelect,
TableInset,
} from '$lib'
let color_scale = `Viridis`
let heatmap_key
$: heatmap_values = heatmap_key
? element_data.map((el) => el[heatmap_key])
: []
$: heat_range = [Math.min(...heatmap_values), Math.max(...heatmap_values)]
</script>
<form>
<ColorScaleSelect bind:value={color_scale} minSelect={1} />
<PropertySelect bind:key={heatmap_key} />
</form>
<PeriodicTable
{heatmap_values}
style="margin: 2em auto 4em;"
bind:color_scale
links="name"
>
<TableInset slot="inset" style="place-items: center; padding: 2em;">
<ColorBar
color_scale="Viridis"
range={heat_range}
tick_side="bottom"
--cbar-width="100%"
--cbar-height="15pt"
/>
</TableInset>
</PeriodicTable>
<style>
form {
display: flex;
place-items: center;
place-content: center;
gap: 1em;
margin: 2em auto;
}
</style>
```
30 changes: 18 additions & 12 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
TableInset,
} from '$lib'
import { property_labels } from '$lib/labels'
import { active_category, active_element, heatmap_key, last_element } from '$lib/stores'
import { active_category, active_element, last_element } from '$lib/stores'
import { DemoNav } from '$site'
import type { Snapshot } from './$types'
let window_width: number
let color_scale: string
let heatmap_key: string | null = null
$: heatmap_values = heatmap_key ? element_data.map((el) => el[heatmap_key]) : []
$: [y_label, y_unit] = property_labels[$heatmap_key] ?? []
$: [y_label, y_unit] = property_labels[heatmap_key] ?? []
export const snapshot: Snapshot = {
capture: () => ({ color_scale }),
Expand All @@ -35,12 +37,16 @@

<h1>Periodic Table of Elements</h1>

<label for="heatmap-select">
<PropertySelect empty id="heatmap-select" />
{#if $heatmap_key}
<ColorScaleSelect bind:value={color_scale} minSelect={1} />
<form>
<PropertySelect empty id="heatmap-select" bind:key={heatmap_key} />
{#if heatmap_key}
<ColorScaleSelect
bind:value={color_scale}
minSelect={1}
cbar_props={{ range: [Math.min(...heatmap_values), Math.max(...heatmap_values)] }}
/>
{/if}
</label>
</form>

{#if $last_element && window_width > 1100}
{@const { shells, name, symbol } = $last_element}
Expand All @@ -50,18 +56,18 @@
{/if}
<PeriodicTable
tile_props={{ show_name: window_width > 1000 }}
heatmap_values={$heatmap_key ? element_data.map((el) => el[$heatmap_key]) : []}
{heatmap_values}
style="margin: 2em auto 4em; max-width: 85vw;"
bind:color_scale
bind:active_element={$active_element}
bind:active_category={$active_category}
links="name"
>
<TableInset slot="inset">
{#if $heatmap_key}
{#if heatmap_key}
<ElementScatter
y_lim={[0, null]}
y={element_data.map((el) => el[$heatmap_key])}
y={element_data.map((el) => el[heatmap_key])}
{y_label}
{y_unit}
on:change={(e) => ($active_element = element_data[e.detail.x - 1])}
Expand All @@ -74,7 +80,7 @@
</TableInset>
</PeriodicTable>

{#if !$heatmap_key}
{#if !heatmap_key}
<ColorCustomizer collapsible={false} />
{/if}

Expand All @@ -95,7 +101,7 @@
top: 2%;
left: 3%;
}
label {
form {
display: flex;
place-content: center;
gap: 1em;
Expand Down
6 changes: 3 additions & 3 deletions src/routes/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@
</p>
{/if}

<label>
<form>
<PropertySelect minSelect={1} />
<ColorScaleSelect bind:value={color_scale} bind:selected minSelect={1} />
</label>
</form>
<section class="viz">
<ElementPhoto
element={$active_element}
Expand Down Expand Up @@ -274,7 +274,7 @@
table tr:hover {
background-color: rgba(150, 150, 255, 0.2);
}
label {
form {
display: flex;
place-content: center;
gap: 1em;
Expand Down

0 comments on commit 7342930

Please sign in to comment.