empowered.house ↗

metrics/health-grid.ex

Health grid

The health of each company area, week by week, as a grid of cells.

Use it
# in the module that renders the template
use EHVisuals

<div class="h-96 rounded-xl border border-border">
  <.metrics_health_grid animated />
</div>
default
isometric
three areas · custom copy
hover · no glow
light surface
isometric · fade out · still

Props

Prop Type Default What it does
title string "Area health"
caption string "Last 8 weeks"
areas list [%{name: "Sales", weeks: [2, 2, 3, ...]}, ...] Up to five `%{name, weeks}`. Each week is a level: 3 healthy, 2 fine, 1 watch, 0 off track. Up to eight weeks.
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/metrics/health-grid.ex
health-grid.ex
defmodule EHVisuals.Metrics.HealthGrid do
  @moduledoc """
  The health of each company area, week by week.
  """

  use EHVisuals.Composition,
    name: "metrics/health-grid",
    title: "Health grid",
    description: "The health of each company area, week by week, as a grid of cells."

  @areas [
    %{name: "Sales", weeks: [2, 2, 3, 3, 2, 3, 3, 3]},
    %{name: "Delivery", weeks: [1, 2, 2, 0, 1, 2, 3, 3]},
    %{name: "Finance", weeks: [3, 3, 3, 2, 3, 3, 3, 3]},
    %{name: "People", weeks: [2, 1, 1, 2, 2, 2, 3, 2]},
    %{name: "Support", weeks: [0, 0, 1, 2, 2, 3, 3, 3]}
  ]

  attr(:title, :string, default: "Area health")
  attr(:caption, :string, default: "Last 8 weeks")

  attr(:areas, :list,
    default: @areas,
    doc:
      "Up to five `%{name, weeks}`. Each week is a level: 3 healthy, 2 fine, 1 watch, 0 off track. Up to eight weeks."
  )

  attr(:label, :string, default: "A grid of area health by week, mostly healthy")
  frame_attrs()

  @spec metrics_health_grid(map()) :: Phoenix.LiveView.Rendered.t()
  def metrics_health_grid(assigns) do
    rows =
      for {area, row} <- assigns.areas |> Enum.take(5) |> Enum.with_index() do
        cells =
          for {level, column} <- area.weeks |> Enum.take(8) |> Enum.with_index() do
            %{x: 114 + column * 26, level: level, step: min(row + column + 2, 24)}
          end

        %{name: area.name, y: 72 + row * 30, cells: cells, step: row + 1}
      end

    assigns =
      assigns
      |> assign(:rows, rows)
      |> assign(:height, 72 + length(rows) * 30 + 12)

    ~H"""
    <.frame name="metrics/health-grid" label={@label} {frame_opts(assigns)}>
      <svg viewBox={"0 0 340 #{@height}"}>
        <g class="ehv-h-lift">
          <rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="339" height={@height - 1} rx="12" />
          <text class="ehv-t-strong ehv-a-fade ehv-i-1" x="20" y="35" font-size="14">{@title}</text>
          <text class="ehv-mono ehv-a-fade ehv-i-1" x="320" y="35" font-size="9" text-anchor="end">
            {String.upcase(@caption)}
          </text>
          <line class="ehv-line-faint" x1="20" x2="320" y1="52.5" y2="52.5" />
          <g :for={row <- @rows}>
            <text
              class={"ehv-t-2 ehv-a-fade ehv-i-#{row.step}"}
              x="20"
              y={row.y + 13.5}
              font-size="12"
            >
              {row.name}
            </text>
            <rect
              :for={cell <- row.cells}
              class={"ehv-metrics-health-grid__cell ehv-metrics-health-grid__cell--#{cell.level} ehv-a-pop ehv-i-#{cell.step}"}
              x={cell.x}
              y={row.y}
              width="18"
              height="18"
              rx="5"
            />
          </g>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/metrics/health-grid.css
health-grid.css
/* metrics/health-grid */
.ehv-metrics-health-grid { --ehv-max-w: 360px; --ehv-step: 45ms; --ehv-dur: 500ms; }

.ehv-metrics-health-grid__cell { stroke-width: 1; }
.ehv-metrics-health-grid__cell--3 { fill: var(--ehv-lime-fill); stroke: none; }
.ehv-metrics-health-grid__cell--2 { fill: var(--ehv-lime-soft); stroke: var(--ehv-lime); stroke-opacity: 0.35; }
.ehv-metrics-health-grid__cell--1 { fill: var(--ehv-surface-3); stroke: var(--ehv-border-strong); }
.ehv-metrics-health-grid__cell--0 { fill: var(--ehv-coral-soft); stroke: var(--ehv-coral); }

.ehv--hover .ehv-metrics-health-grid__cell { transition: opacity 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-metrics-health-grid__cell:not(:hover) { opacity: 0.55; }