intelligence/knowledge-graph.ex
Knowledge graph
A goal in the middle with the facts, rules, owners and sources the company connects to it.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.intelligence_knowledge_graph animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
center |
map | %{label: "Ship in 48 hours", type: "Goal"} |
`%{type, label}` of the middle card. |
nodes |
list | [%{label: "Cut-off is 14:00", type: "Fact"}, %{label: "Stock check first", type: "Rule"}, ...] |
Up to six `%{type, label}` around it. Keep labels under 18 characters. |
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/intelligence/knowledge-graph.ex
defmodule EHVisuals.Intelligence.KnowledgeGraph do
@moduledoc """
What the company knows about one thing: a goal in the middle, and the
facts, rules, owners and sources connected to it.
"""
use EHVisuals.Composition,
name: "intelligence/knowledge-graph",
title: "Knowledge graph",
description:
"A goal in the middle with the facts, rules, owners and sources the company connects to it."
@nodes [
%{type: "Fact", label: "Cut-off is 14:00"},
%{type: "Rule", label: "Stock check first"},
%{type: "Owner", label: "Marta, Delivery"},
%{type: "Metric", label: "On time: 94%"},
%{type: "Process", label: "Order to cash"},
%{type: "Source", label: "Ops sync, 12 Mar"}
]
# Centre points of the six slots around the middle card.
@slots [{84, 52}, {316, 52}, {66, 150}, {334, 150}, {84, 248}, {316, 248}]
attr(:center, :map,
default: %{type: "Goal", label: "Ship in 48 hours"},
doc: "`%{type, label}` of the middle card."
)
attr(:nodes, :list,
default: @nodes,
doc: "Up to six `%{type, label}` around it. Keep labels under 18 characters."
)
attr(:label, :string,
default: "A goal connected to the facts, rules, owners and sources behind it"
)
frame_attrs()
@spec intelligence_knowledge_graph(map()) :: Phoenix.LiveView.Rendered.t()
def intelligence_knowledge_graph(assigns) do
items =
for {{node, {cx, cy}}, index} <- assigns.nodes |> Enum.zip(@slots) |> Enum.with_index() do
node |> Map.put(:cx, cx) |> Map.put(:cy, cy) |> Map.put(:index, index)
end
assigns = assign(assigns, :items, items)
~H"""
<.frame name="intelligence/knowledge-graph" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 400 300">
<g class="ehv-h-lift">
<line
:for={item <- @items}
class={"ehv-line ehv-a-draw ehv-i-#{item.index + 2}"}
x1="200"
y1="150"
x2={item.cx}
y2={item.cy}
pathLength="1"
/>
<g :if={@animated} class="ehv-smil ehv-a-fade ehv-i-12">
<circle :for={item <- @items} class="ehv-fill-lime" r="2.5">
<animate
attributeName="cx"
from={item.cx}
to="200"
dur="3s"
begin={"#{item.index * 0.5}s"}
repeatCount="indefinite"
/>
<animate
attributeName="cy"
from={item.cy}
to="150"
dur="3s"
begin={"#{item.index * 0.5}s"}
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;1;1;0"
keyTimes="0;0.2;0.7;1"
dur="3s"
begin={"#{item.index * 0.5}s"}
repeatCount="indefinite"
/>
</circle>
</g>
<g :for={item <- @items} class={"ehv-a-rise ehv-i-#{item.index + 3}"}>
<rect
class="ehv-card ehv-intelligence-knowledge-graph__node"
x={item.cx - 60}
y={item.cy - 20}
width="120"
height="40"
rx="8"
/>
<text class="ehv-mono" x={item.cx - 49} y={item.cy - 4} font-size="7.5">
{String.upcase(item.type)}
</text>
<text x={item.cx - 49} y={item.cy + 11} font-size="11.5">{item.label}</text>
</g>
<g class="ehv-a-pop">
<rect class="ehv-card-lime" x="132.5" y="126.5" width="135" height="47" rx="10" />
<text class="ehv-mono ehv-t-lime" x="146" y="144" font-size="7.5">
{String.upcase(@center.type)}
</text>
<text class="ehv-t-strong" x="146" y="161" font-size="12.5">{@center.label}</text>
</g>
</g>
</svg>
</.frame>
"""
end
end
assets/css/intelligence/knowledge-graph.css
/* intelligence/knowledge-graph */
.ehv-intelligence-knowledge-graph { --ehv-max-w: 440px; }
.ehv--hover .ehv-intelligence-knowledge-graph__node { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover .ehv-intelligence-knowledge-graph__node:hover { stroke: var(--ehv-lime); }