ASCII charts for clear communication
A diagram that lives in a wiki dies in the wiki. A diagram that lives next to the code - in a comment, a commit message, a README, an incident report - gets read, reviewed, and updated together with the thing it describes.
ASCII charts are the most portable diagrams we have. They render identically in a terminal, an email, a git log, a pager alert, and a code review. They diff cleanly. They need no toolchain, no export step, and no license to a SaaS product that might not exist in five years.
A note on terminology: most of these use Unicode box-drawing characters rather than strict 7-bit ASCII. If you need to paste into something truly ASCII-only, the +--| fallback style is shown at the end.
Box diagrams: architecture at a glance
The workhorse. Boxes are components, lines are relationships. Keep it to one level of abstraction per diagram - if you need to show what is inside a box, draw a second diagram.
┌───────────────┐
│ clients │
└───────┬───────┘
│ https
┌───────▼───────┐
│ load balancer │ (active/standby pair,
└───────┬───────┘ shared virtual IP)
┌──────────────┼──────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ app-1 │ │ app-2 │ │ app-3 │ stateless
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
└──────────────┼──────────────┘
┌───────▼───────┐
│ databases │ (primary + replica)
└───────────────┘Rules of thumb:
- Traffic flows top to bottom or left to right. Pick one and stay consistent.
- Annotate to the side in parentheses rather than cramming text into boxes.
- Three to seven boxes. More than that and the diagram needs splitting.
Box diagrams, advanced: nesting, boundaries, edge styles
Once the basics are comfortable, three techniques carry most real architecture diagrams.
Nesting for containment. Put a label in the top border (┌─ name ──...) and a box can contain other boxes: clusters containing namespaces, regions containing zones, processes containing threads. Let an edge cross a border with ┼ to show traffic passing through it:
┌─ kubernetes cluster ────────────────────────────┐
│ │
│ ┌─ namespace: web ──────────────────────┐ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ ingress │────►│ web-api │ │ │
│ │ └─────────┘ └────┬────┘ │ │
│ └────────────────────────┼──────────────┘ │
│ │ jobs │
│ ┌─ namespace: data ──────┼──────────────┐ │
│ │ ┌─────────┐ ┌────▼────┐ │ │
│ │ │ redis │◄────│ workers │ │ │
│ │ └─────────┘ └─────────┘ │ │
│ └───────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘Edge labels and line styles. Name what flows along an edge, and use dashed lines (╌) for asynchronous hops so the reader can tell at a glance where the system decouples. If you use more than one line style, spend two lines on a legend:
┌──────────┐ publish ┌─────────┐ consume ┌──────────┐
│ checkout ├╌╌╌╌╌╌╌╌╌╌►│ queue ├╌╌╌╌╌╌╌╌╌╌►│ invoicer │
└────┬─────┘ └─────────┘ └────┬─────┘
│ rpc │ rpc
┌────▼─────┐ ┌────▼─────┐
│ payments │ │ mailer │
└──────────┘ └──────────┘
──► sync call (caller waits)
╌╌► async event (consumer may lag)This is the diagram that answers "if invoicing is down, do checkouts fail?" - follow the dashes: no, they queue.
Trust boundaries. A double line (═) across the page marks where the rules change - untrusted to trusted, one team's ownership to another's, internet to VPC. Crossing points (╪) are exactly the places that need authentication, validation, and rate limits, so the diagram doubles as a security checklist:
clients (untrusted)
│ tls
═════════════════╪═════════════════ trust boundary
│
┌─────▼─────┐
│ gateway │ authn, input validation
└─────┬─────┘
│ internal rpc
┌─────▼─────┐
│ backend │
└───────────┘Double-line boxes for emphasis. Drawing one box with double borders (╔ ═ ╗ ║ ╚ ╝) makes it the typographic loudest thing on the page. Spend that on the box the diagram is about - the component under incident, the system being replaced, or, as here, the one with a compliance perimeter around it:
┌───────────┐ tokenize ╔═══════════╗
│ payment ├────────────►║ vault ║ PCI scope:
└───────────┘ ╚═════╤═════╝ card data never
│ store leaves this box
┌─────▼─────┐
│ pgsql │
└───────────┘One double box per diagram. Two and the emphasis cancels out.
These compose: a nested region diagram with a trust boundary, labeled async edges, and one double-boxed component can replace a whole page of prose. The limit is still readability - when a diagram needs every technique and ten boxes, split it.
Trees: hierarchy, the tree way
The output style of the tree command is so widely recognized that it has become the standard notation for any containment hierarchy - not just directories. Use it whenever the relationship is "X contains Y":
myservice/
├── cmd/
│ └── myservice/
│ └── main.go
├── internal/
│ ├── api/
│ │ ├── handler.go
│ │ └── middleware.go
│ ├── store/
│ │ ├── postgres.go
│ │ └── migrations/
│ └── queue/
├── deploy/
│ ├── Dockerfile
│ └── k8s/
└── go.modAnd a dependency tree with a problem annotated in place:
app v2.1.0
├── libhttp v4.2.0
│ └── libtls v1.8.1
├── libjson v3.0.2
└── libqueue v0.9.0
└── libtls v1.4.0 ◄ conflict: two libtls versionsWhen drawing by hand, you only need four pieces: ├──, └── (last child), │ (continuation), and three spaces where a parent branch has ended.
Sequence diagrams: who talks to whom, in what order
When the order of interactions is the point - auth flows, retries, race conditions - a sequence diagram beats a box diagram every time.
client api auth db
│ │ │ │
│ POST /login │ │ │
├─────────────►│ │ │
│ │ verify token │ │
│ ├─────────────►│ │
│ │ ok │ │
│ │◄─────────────┤ │
│ │ SELECT user │
│ ├─────────────────────────────►
│ │ row │
│ │◄─────────────────────────────
│ 200 + cookie │ │ │
│◄─────────────┤ │ │
│ │ │ │This style is especially good in incident reports: it makes "the retry fired before the first request timed out" visible in a way prose never does.
Horizontal bar charts: comparing magnitudes
For "which of these is biggest", nothing beats a bar per item. Scale the bars to the data and always print the actual number - the bars give shape, the numbers give truth.
p99 latency by endpoint (ms)
/search ████████████████████████████ 712
/checkout ███████████████ 385
/profile ██████ 152
/healthz ▌ 11The same shape works for disk usage, request counts, cost per service, test duration - anything one-dimensional. Sort by value unless the natural order (alphabetical, chronological) carries meaning.
When the parts of a whole matter more than absolute size, stack them into a single 100% bar:
request outcomes (last hour, n=48,210)
██████████████████████████████████░░░░▒▒▓
└──────────── 2xx 81% ────────────┘└3xx┘└─ 4xx 7%, 5xx 2%And for "how far along is it", the humble progress bar is still the right tool:
backfill: [█████████████████░░░░░░░░░░░░░] 57% (4.1M / 7.2M rows, ETA 2h10m)Sparklines and time series: shape over time
Block elements (▁▂▃▄▅▆▇█) compress a series into a single line. Perfect for chat messages and alert annotations:
error rate, last 24h: ▁▁▁▁▂▁▁▃▇█▆▃▂▁▁▁▁▁▁▂▁▁▁▁
▲
└ deploy 14:32 UTCWhen you have more vertical room, a proper axis tells a fuller story:
req/s
400 ┤ ╭──╮
300 ┤ ╭─────────╯ ╰─╮
200 ┤ ╭─────╯ ╰────╮
r100 ┤────╯ ╰─────
0 ┼────┬────┬────┬────┬────┬────┬────
00 04 08 12 16 20 24 (h)Tools like gnuplot (set terminal dumb) and libraries in most languages will generate these for you, but for documentation a hand-drawn approximation is usually clearer than a precise one - you control what the reader notices.
Histograms: distribution, not average
Averages hide bimodal distributions. A histogram in a benchmark report or a profiling writeup shows the real shape:
request duration distribution (n=10,000)
0-10ms ████████████████████████████████ 6,213
10-25ms ██████████ 1,904
25-50ms ███ 588
50-100ms █ 201
100-250ms ▏ 71
250ms+ █████▌ 1,023 ◄ second mode:
cold cache pathThat last bucket is the entire reason to draw the chart. "Mean 28ms" would have hidden it.
Heatmaps: two dimensions with shading
The shading blocks ░ ▒ ▓ █ give you four intensity levels - enough for a calendar heatmap or a load matrix. Where does the traffic actually land?
requests by hour and weekday (darker = more)
00 03 06 09 12 15 18 21
mon ░ ░ ▒ █ █ ▓ ▓ ▒
tue ░ ░ ▒ █ █ █ ▓ ▒
wed ░ ░ ▒ █ ▓ █ ▓ ▒
thu ░ ░ ▒ █ █ █ █ ▓
fri ░ ░ ▒ ▓ ▓ ▒ ▒ ░
sat ░ ░ ░ ░ ▒ ▒ ▒ ░
sun ░ ░ ░ ░ ░ ▒ ▒ ▒ ◄ maintenance window:
sunday 00–06The conclusion - schedule the maintenance window for Sunday night - falls out of the picture without anyone reading a single number.
Scatter plots: correlation, roughly
Low resolution, but often enough to show whether two things move together:
response time vs. payload size
ms
800 ┤ ×
600 ┤ × × ×
400 ┤ × × × ×
200 ┤ × × × × ×
0 ┼────┬───────┬───────┬───────┬──
0 250 500 750 1000 KBIf the answer requires precision, this is the moment to switch to a real plotting tool. The ASCII version's job is to justify that effort.
Timelines: incidents and rollouts
A timeline turns an incident postmortem from a wall of timestamps into a story:
14:02 ─┬─ deploy v2.41 starts (canary, 5%)
│
14:09 ─┼─ canary error rate climbs ▁▂▅█
│
14:11 ─┼─ alert: ERROR_RATE_HIGH fires
│
14:14 ─┼─ rollback initiated ◄── 3 min from alert
│ to action
14:19 ─┼─ error rate recovered ▆▃▁▁
│
14:40 ─┴─ incident closedGantt-style variants work for planning and for showing overlap:
week 1 week 2 week 3 week 4
schema mig ████████
backfill █████████████
dual-write ██████████████████████
cutover ███
cleanup ████████State machines: legal transitions only
When a bug report says "the order went from shipped back to pending", this is the diagram that settles whether that is even supposed to be possible:
┌─────────┐
create ───►│ pending │
└────┬────┘
│ pay
┌────▼────┐ cancel ┌───────────┐
│ charged ├───────────►│ cancelled │
└────┬────┘ └───────────┘
│ ship
┌────▼────┐ ┌───────────┐
│ shipped │──── deliver ───►│ delivered │
└─────────┘ └───────────┘Every arrow is a legal transition; everything not drawn is a bug.
Decision trees: runbooks that fit in a pager alert
A small decision tree in a runbook or an alert description gets followed at 3 a.m. in a way a paragraph of prose does not:
disk usage alert fired
│
├─ is it /var/log? ──────────── yes ─► rotate logs, done
│ no
├─ is a backup running? ─────── yes ─► wait, re-check in 30m
│ no
├─ growth > 1%/min? ─────────── yes ─► page storage on-call
│ no
└─ file ticket, handle in business hoursNote that this reuses the tree notation - readers already know how to follow it.
Tables with meaning: the humble grid
Sometimes the clearest "chart" is a well-aligned table with a judgmental column:
option setup failover cost verdict
──────────────── ────── ───────── ────── ────────────────
DNS round-robin easy minutes $ too slow to fail
virtual IP (VRRP) medium seconds $ ✓ chosen
anycast hard instant $$$ overkill here
cloud LB easy seconds $$ vendor lock-inAlignment is the entire trick. Misaligned columns read as noise; aligned ones read as data.
Composed diagrams: the advanced end
The notations compose, and that is where plain text starts replacing diagram software outright. Here is a multi-region deployment: nested region boxes, an edge entering through the top border, and dashed cross-region replication crossing four borders - one diagram, three techniques:
users
│
┌──────────────┴──────────────┐
│ geo-dns │ geo-dns
┌───────────┼───────────┐ ┌───────────┼───────────┐
│ eu-north-1 │ │ us-east-1 │
│ ┌─────▼─────┐ │ │ ┌─────▼─────┐ │
│ │ gateway │ │ │ │ gateway │ │
│ └─────┬─────┘ │ │ └─────┬─────┘ │
│ ┌─────▼─────┐ │ │ ┌─────▼─────┐ │
│ │ app │ │ │ │ app │ │
│ └─────┬─────┘ │ │ └─────┬─────┘ │
│ ┌─────▼─────┐ │ │ ┌─────▼─────┐ │
│ │ primary ├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌►│ replica │ │
│ └───────────┘ │ │ └───────────┘ │
└───────────────────────┘ └───────────────────────┘
╌╌► async replication (replica lags primary)The failure questions answer themselves: lose us-east-1 and reads fail over but writes were never there; lose the replication link and both regions keep serving while the replica goes stale.
Hybrids work too. A tree crossed with a bar chart is a profile - where a request's time actually goes, with the call structure preserved:
where the 240ms goes (p50)
main ████████████████████████████████ 240ms
├── parse request ██ 12ms
├── auth check ████ 31ms
│ └── jwt verify ███ 24ms
├── db query ████████████████████ 152ms ◄ here
│ ├── wait (pool) ████████ 61ms
│ └── execute ████████████ 88ms
└── render json █████ 41msAnyone who has read a flame graph recognizes this instantly; anyone who has not can still see that the pool wait is 40% of the database time. That is the test of a composed diagram: each notation must still mean what it meant alone.
And the deepest end I know: the distributed trace waterfall. Four notations on one shared time axis - a tree on the left for call structure, gantt bars for duration, vertical position for sequence, and a notes column carrying the analysis. Every span starts at the column its timestamp says it should; that discipline is what makes the gaps as readable as the bars:
0ms 80 160 240 320 400
┼─────────┼─────────┼─────────┼─────────┼─────────┼──
gateway ▐███████████████████████████████████████████████████▌ 412ms
├─ authn ▐██▌ 28ms
├─ orders ▐█████████████████████████████████▌ 271ms
│ ├─ db ▐███▌ 34ms SELECT cart ┐
│ ├─ db ▐████▌ 41ms SELECT inventory │ sequential -
│ └─ db ▐████▌ 39ms SELECT prices ┘ parallelize?
├─ payment ▐░░░░▌ timeout ◄ retry adds ~200ms
│ └─ retry ▐██████████████████████▌ 187ms
└─ email ▐▌ 7ms async, off critical pathThis one diagram contains the whole performance review. The three database queries are sequential when nothing says they must be - running them in parallel saves roughly 75ms. The hollow ░ bar is a payment attempt that timed out; the retry that follows it is the single largest span in the trace, so the timeout setting, not the database, is the first thing to fix. And the email span hangs past everything at the far right, where its position proves it is not the problem. None of that needed a paragraph; it needed columns that line up.
Finally, the box diagram pushed as far as I know how to push it: an architecture diagram that is also a dashboard. Every box carries its own telemetry - a sparkline and the number that matters - and numbered markers trace the failure cascade through the system. It is a postmortem, a topology, and a metrics page in one figure:
clients
│ ◄╌╌ (4) retry storm ×3
┌─ gateway ──▼────────────┐
│ rps ▄▅▆▆▅▄▄▃▃▂▂ │
│ 5xx ▁▁▁▁▂▅██ 31% (3) │
└────────────┬────────────┘
┌──────────────┴──────────────┐
┌─ orders ───▼────────────┐ ┌─ payments ─▼────────────┐
│ p99 ▂▂▃▅▇██ 4.1s (2) │ │ p99 ▂▂▂▂▂▂▂ 80ms │
│ pool 50/50 exhausted │ │ idle - traffic gone │
└────────────┬────────────┘ └────────────┬────────────┘
└──────────────┬──────────────┘
┌─ database ─▼────────────┐
│ lock ▁▁▁▂▆███ 9.8s (1)│
│ slow query since 14:02 │
└─────────────────────────┘
requests flow down · the failure climbs up: (1) (2) (3) (4)Read it twice, in opposite directions. Top-down it is the request path: clients through the gateway, fanning out to two services, merging into one database. Bottom-up it is the incident: a slow query at 14:02 (1), the orders connection pool saturates (2), the gateway starts throwing 5xx (3), and clients respond with a retry storm (4) that triples the load on a system already down. The flat sparkline in payments is the quiet punchline - it was never broken, it just stopped receiving traffic. Failure propagates against the flow of requests, and this is the rare diagram where you can watch it do so.
How far can a single box diagram scale before it collapses? About this far:
┌─────────┐ ┌─────────┐ ┌─────────┐
users ───►│ cdn ├────►│ waf ├────►│ lb-pair │
└─────────┘ └─────────┘ └────┬────┘
│
┌──────────────┘
┌───────────┐ jwt ┌─────▼─────┐
│ authn │◄────┤ gateway │
└───────────┘ └─────┬─────┘
┌────────────────┬───────┴────────┬────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ ║ ┌─────────┐
│ account │ │ catalog │ │ booking │ │ payment ├╌╌╫╌►│ psp │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ ║ └─────────┘
│ events │ │ │ ║
┌─────▼────────────────▼── kafka bus ───▼────────────────▼─────┐ ║
│ booking.created · payment.captured · price.updated │ ║
└──────────────────────────────┬───────────────────────────────┘ ║
│ ║
┌─────▼─────┐ ║ ┌─────────┐
│ workers ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╫╌►│ email │
└─────┬─────┘ ║ └─────────┘
┌────────────────┬───────┴────────┬────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ pgsql │ │ redis │ │ elastic │ │ minio │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
──► sync ╌╌► async ║ trust boundary · read paths omitted for sanityEvery technique from this post is in there earning its keep: a horizontal chain across the edge tier, an L-shaped route from the load balancer down into the gateway, a labeled side edge to authn, fan-out and fan-in bars, a bus box tapped at four points along its top border, a vertical trust boundary (║) punched twice by dashed async edges (╫), a legend, and - the most honest annotation a diagram can carry - a note saying what was left out. The grid is what holds it together: services and datastores share the same four center columns, so the eye tracks booking straight down through the bus to elastic without a ruler.
This is the ceiling, though. One more tier, one more crossing edge, and it stops being a diagram and becomes a puzzle. When you feel that happening, take the earlier advice: split it, one question per diagram.
Past this point you are no longer drawing charts, you are typesetting instruments - and the rules stay the same all the way up: one question per panel, numbers next to shapes, alignment as the load-bearing structure.
Six-dot charts: braille for resolution
Every chart so far spends one character per data point. The Unicode braille block (U+2800) packs a 2×3 grid of dots into each character cell - six dots, individually addressable, which multiplies plotting resolution by six. The same daily traffic curve, first as a six-dot line chart, then as one-block-per-column sparkline:
req/s
600 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠔⠊⠉⠉⠒⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
400 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠤⠒⠁⠀⠀⠀⠀⠀⠀⠉⠒⠒⠊⠉⠒⠄⠀⠀⠀
200 ⠀⠀⠀⠠⠤⠤⠤⠤⠤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⠤⠤
0 ⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
00 04 08 12 16 20 24
▂▂▂▂▂▂▂▃▃▃▄▅▆▇▇████▇▆▆▅▆▆▆▄▃▃▂ ◄ same data, full blocksThe braille version resolves the evening shoulder at 20:00 that the block version smears into the main peak. Six-dot charts are not hand-drawable in practice - generate them. btop draws its graphs this way, plotille does it from Python, and the encoding is simple enough to implement in twenty lines: dots 1-3 are the left column of the cell (bits 0x01, 0x02, 0x04 top to bottom), dots 4-6 the right (0x08, 0x10, 0x20), added to U+2800.
There is also an eight-dot extension: dots 7 and 8 (bits 0x40 and 0x80) add a fourth row to every cell. Same curve, same 12-dot vertical resolution, but 2×4 cells squeeze it into three lines instead of four:
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠊⠉⠉⠒⠢⣀⠀⠀⢀⣀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠁⠀⠉⠢⡀⠀⠀
⠒⠒⠒⠊⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉Two caveats. The blank braille cell ⠀ is not a space, which surprises tools that trim or wrap text. And while braille glyphs are present in essentially every terminal font, the eight-dot rows render less reliably than the six-dot subset - when portability matters most, stay with six dots.
The pure-ASCII fallback
Some destinations - old ticketing systems, plain-text email with aggressive fonts, code comments in projects that mandate ASCII - choke on box-drawing characters. The classic +--| style still works everywhere:
+--------+ +--------+ +--------+
| client |----->| server |----->| db |
+--------+ +--------+ +--------+
|
v
+-------+
| cache |
+-------+It is uglier, but "renders everywhere" beats pretty.
Practical advice
- Use a monospace context or don't bother. A proportional font destroys every chart above. Fence them in code blocks in Markdown.
- Draw for the reader's question, not for completeness. A diagram that answers one question well beats one that answers five badly.
- Hand-align, then leave it alone. Editors with column-edit modes (or
gg+ visual block in vim) make this fast. - Put data in numbers, shape in bars. Never make someone count
█characters. - Date your diagrams if the architecture changes. A wrong diagram is worse than none.
Tools, if you want them: asciiflow for box diagrams, gnuplot with set terminal dumb for plots, and graph-easy for converting DOT graphs to ASCII. But the bar to entry is exactly one keyboard, which is rather the point.
Character reference
Every non-ASCII character used in this post, grouped for copying. Most editors also offer these via a unicode picker (vim: ga to inspect, Ctrl-V u2500 to insert).
corners ┌ ┐ └ ┘
rounded corners ╭ ╮ ╰ ╯
double corners ╔ ╗ ╚ ╝
lines ─ │
dashed line ╌
junctions ├ ┤ ┬ ┴ ┼
double line ═ ║
single/double mix ╪ ╫ ╤
arrowheads ► ◄ ▲ ▼
bars, 1/8 steps ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
partial bars ▏ ▌ ▐
shading ░ ▒ ▓ █
callout markers ✓ × · (1) (2) (3)
braille, dots 1-8 ⠁ ⠂ ⠄ ⠈ ⠐ ⠠ ⡀ ⢀
braille, full ⠿ (six-dot) ⣿ (eight-dot)Numbered callouts are plain parenthesized digits on purpose: the circled forms (U+2460 and up) render at inconsistent widths across monospace fonts and quietly break the alignment everything else depends on. The same warning applies to most emoji and to East Asian fullwidth forms - if a character is not reliably one cell wide, it does not belong in a chart.
The blocks they come from, if you need more than shown here: box drawing is U+2500-257F, block elements U+2580-259F, the arrowheads live in geometric shapes U+25A0-25FF, and braille patterns are U+2800-28FF.
A practical shortcut: you rarely type these. Copy a skeleton from an existing diagram - this post is CC0 precisely so you can - and edit the labels.
License
All diagrams and code examples in this post are dedicated to the public domain under CC0 1.0 Universal. Use them however you like, with or without attribution.