charts/bar.ex
Bar chart
A bar chart card with one bar picked out in lime.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.charts_bar animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
title |
string | "Orders shipped on time" |
|
value |
string | "94%" |
|
caption |
string | "Last 8 weeks" |
|
values |
list | ~c">D@JOMX^" |
|
labels |
list | ["W1", "W2", "W3", "W4", "W5", "W6", ...] |
One label per bar. |
highlight |
integer | -1 |
Index of the lime bar. A negative index counts from the end. |
Frame props, shared by every composition
| Prop | Type | Default | What it does |
|---|---|---|---|
animated |
boolean | false |
Turns motion on. Without it the composition renders in its final, still state. |
trigger |
"mount" | "in-view" | "in-view-repeat" | "in-view" |
When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view. |
hover |
boolean | false |
Enables the hover micro-interactions the composition supports. |
glow |
boolean | true |
The warm incandescent halo behind the composition. |
fade_out |
boolean | false |
Fades the bottom edge into the surface underneath. |
isometric |
boolean | false |
Tilts the composition into the isometric plane of the platform stack. |
theme |
"dark" | "light" | "dark" |
`dark` for the site canvas, `light` for the cream reversed surface. |
class |
string | nil |
Extra classes for the frame. |
rest |
global | — |
Any other HTML attribute, set on the frame. |
Source
Two files. Copy them into your app if you would rather own the code than depend on the library.
lib/eh_visuals/charts/bar.ex
defmodule EHVisuals.Charts.Bar do
@moduledoc """
A bar chart card with one bar picked out in lime.
"""
use EHVisuals.Composition,
name: "charts/bar",
title: "Bar chart",
description: "A bar chart card with one bar picked out in lime."
attr(:title, :string, default: "Orders shipped on time")
attr(:value, :string, default: "94%")
attr(:caption, :string, default: "Last 8 weeks")
attr(:values, :list, default: [62, 68, 64, 74, 79, 77, 88, 94])
attr(:labels, :list, default: ~w(W1 W2 W3 W4 W5 W6 W7 W8), doc: "One label per bar.")
attr(:highlight, :integer,
default: -1,
doc: "Index of the lime bar. A negative index counts from the end."
)
attr(:label, :string, default: "A bar chart rising week over week")
frame_attrs()
@spec charts_bar(map()) :: Phoenix.LiveView.Rendered.t()
def charts_bar(assigns) do
assigns = assign(assigns, :bars, bars(assigns.values, assigns.labels, assigns.highlight))
~H"""
<.frame name="charts/bar" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 360 240">
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="359" height="239" rx="12" />
<text class="ehv-t-2 ehv-a-fade ehv-i-1" x="20" y="34" font-size="12">{@title}</text>
<text class="ehv-t-display ehv-a-fade ehv-i-2" x="20" y="64" font-size="24">{@value}</text>
<text class="ehv-mono ehv-a-fade ehv-i-2" x="340" y="34" font-size="9" text-anchor="end">
{String.upcase(@caption)}
</text>
<line
:for={y <- [88.5, 124.5, 160.5]}
class="ehv-line-faint"
x1="20"
x2="340"
y1={y}
y2={y}
/>
<line class="ehv-line-soft" x1="20" x2="340" y1="196.5" y2="196.5" />
<g :for={bar <- @bars}>
<rect
class={[
"ehv-charts-bar__bar ehv-a-grow-y ehv-i-#{bar.step}",
bar.on && "ehv-charts-bar__bar--on"
]}
x={bar.x}
y={bar.y}
width={bar.w}
height={bar.h}
rx="3"
/>
<text
class={["ehv-mono ehv-a-fade ehv-i-#{bar.step}", bar.on && "ehv-t-ink"]}
x={bar.cx}
y="216"
font-size="8"
text-anchor="middle"
>
{bar.label}
</text>
</g>
</g>
</svg>
</.frame>
"""
end
# The plot is 320 wide from x = 20 and 108 tall above the baseline at y = 196.
defp bars(values, labels, highlight) do
count = max(length(values), 1)
top = max(Enum.max(values, fn -> 1 end), 1)
slot = 320 / count
width = Float.round(slot * 0.56, 1)
on = Integer.mod(highlight, count)
for {value, index} <- Enum.with_index(values) do
height = Float.round(value / top * 108, 1)
%{
x: Float.round(20 + index * slot + (slot - width) / 2, 1),
y: Float.round(196 - height, 1),
w: width,
h: height,
cx: Float.round(20 + index * slot + slot / 2, 1),
on: index == on,
step: min(index + 2, 24),
label: Enum.at(labels, index)
}
end
end
end
assets/css/charts/bar.css
/* charts/bar */
.ehv-charts-bar { --ehv-max-w: 380px; --ehv-step: 60ms; }
.ehv-charts-bar__bar {
fill: var(--ehv-surface-3);
stroke: var(--ehv-border-strong);
stroke-width: 1;
}
.ehv-charts-bar__bar--on { fill: var(--ehv-lime-fill); stroke: none; }
.ehv--hover .ehv-charts-bar__bar { transition: fill 200ms var(--ehv-ease); }
.ehv--hover .ehv-charts-bar__bar:not(.ehv-charts-bar__bar--on):hover { fill: var(--ehv-ink-4); }