Diagrama em imagem envelhece pior que código. O PNG exportado há dois
anos continua no README mostrando um serviço que fala com
um banco que já foi desligado, e ninguém corrige — corrigir exige achar
o arquivo original, abrir a ferramenta, exportar de novo. Diagrama em
texto envelhece junto com o texto: mora no mesmo
arquivo, entra no mesmo commit, aparece no mesmo diff.
É por isso que este site desenha com Mermaid. O preço é que o Mermaid chega com identidade visual própria, e um site cuja regra é uma cor de acento e só uma não convive com isso. Este texto é o catálogo do que ele desenha aqui e o registro do que custou para ele desaparecer na página.
As nove cores
Toda cor de diagrama sai de um custom property do
css/base.css, lido em tempo de execução. São nove, e são
hex sólidos de propósito: o Mermaid faz conta com eles
para derivar tons — borda de nó, fundo de nota, texto sobre fundo — e
derivar a partir de rgba() com alfa dá resultado errado.
- --mm-nodefundo de nó, ator, estado
- --mm-node-borderborda de tudo que é caixa
- --mm-texttodo rótulo e toda legenda
- --mm-linearesta, seta, linha de vida
- --mm-clusterfundo de subgrafo
- --mm-cluster-borderborda tracejada do subgrafo
- --mm-notenota e ativação — o único destaque
- --mm-note-borderborda da nota, na cor de acento
- --mm-altfaixa alternada, fundo secundário
Troque o tema no canto superior direito e as nove trocam de valor. O que não troca sozinho é o desenho: o SVG que o Mermaid produz carrega a cor embutida em cada forma e não herda custom property. Trocar o tema aqui significa redesenhar — é a única parte deste site em que o JavaScript precisa saber que cor é a cor.
O catálogo
O Mermaid 11.15 desenha vinte e dois tipos de diagrama. Vinte e um funcionam por CDN sem plugin nenhum; os oito abaixo são os que este site usa, e cada um está desenhando algo verdadeiro sobre o próprio site — nenhum é exemplo de manual.
flowchart
largura 643px vem de conteúdo tema por themeVariables
O tipo mais usado, e o único em que vale gastar configuração: curve, nodeSpacing, rankSpacing e wrappingWidth mudam bastante o resultado. TD (de cima para baixo) costuma caber na coluna; LR costuma não.
flowchart TD
F["fonte no .html"] --> W["espera document.fonts.ready"]
subgraph tokens["css/base.css"]
direction LR
T1["--mm-node"] --- T2["--mm-line"]
end
tokens --> C["themeVariables"]
W --> I["mermaid.initialize"]
C --> I
I --> R["mermaid.run"]
R --> A{{"cabe na coluna?"}}
A -- "sim" --> N(["fica no tamanho natural"])
A -- "não" --> Z(["reduz, e o rótulo encolhe junto"])
{{"texto"}} — e é por causa dele que o .nojekyll na raiz do repositório não pode ser apagado: sem ele o GitHub Pages roda o Jekyll, que interpreta as chaves duplas como Liquid e quebra a página inteira.sequenceDiagram
largura 690px vem de conteúdo tema por themeVariables
Para ordem no tempo, e o tipo que mais se beneficia de autonumber. A ativação (a barra sobre a linha de vida) usa --mm-note, o mesmo token da nota: é o único destaque de cor que um diagrama recebe aqui.
sequenceDiagram
autonumber
participant N as navegador
participant F as document.fonts
participant M as mermaid
N->>F: espera fonts.ready
activate F
F-->>N: a JetBrains Mono chegou
deactivate F
N->>M: run() com os fontes do grafo
activate M
Note over M: mede o texto renderizado
M-->>N: svg com a cor embutida
deactivate M
stateDiagram-v2
largura 580px vem de conteúdo tema por themeVariables + themeCSS
Máquina de estados. É o tipo que mais deu trabalho para tematizar: o rótulo da transição não é <text> do SVG, mora dentro de um foreignObject e escapa dos seletores normais — precisa de uma regra própria no themeCSS.
stateDiagram-v2
[*] --> Escondido
Escondido --> Desenhado: run() terminou
Desenhado --> Ajustado: ajustarEscala
Ajustado --> Escondido: tema mudou
Escondido --> Fonte: mermaid fora do ar
Fonte --> [*]
note right of Fonte
o leitor vê o texto do
grafo, não um buraco
end note
note a quebra de linha no fonte funciona — é a exceção à regra do <br/>.classDiagram
largura 247px vem de conteúdo tema por themeVariables
Estrutura e relação entre tipos. Serve bem para desenhar a superfície de uma API — abaixo, a que o site.js expõe e a publicação consome.
classDiagram
class Site {
+tema() string
+token(nome) string
+alternar()
}
class Publicacao {
-fontes
+desenhar()
+ajustarEscala()
}
Publicacao ..> Site : lê os tokens
: ouve tema:mudou derruba o parser e o Mermaid desenha a bomba de erro no lugar do diagrama. Custou-me uma medição para descobrir que o defeito era meu, não dele.erDiagram
largura 596px vem de conteúdo tema por themeVariables
Entidade e relacionamento. Aqui ele desenha o modelo do build.mjs — que não tem banco nenhum: a publicação é a fonte de verdade e o resto é derivado dela.
erDiagram
PUBLICACAO }o--|| TOPICO : "tem"
PUBLICACAO ||--o{ DIAGRAMA : "contém"
PUBLICACAO ||--|| CARTAO : "gera"
PUBLICACAO {
string titulo
date publicado
int palavras
}
}o--|| diz que toda publicação tem exatamente um tópico, e que um tópico pode não ter nenhuma publicação — que é justamente o estado de três dos quatro tópicos deste site.gitGraph
largura 368px vem de conteúdo tema por git0..git7
Ramos e merges. Foi o primeiro tipo a mostrar que themeVariables não é suficiente: ele pinta por uma paleta numerada própria e ignorava primaryColor — os ramos saíam caqui.
gitGraph
commit id: "site no ar"
branch fix/ancoras
commit id: "checks na ci"
checkout main
merge fix/ancoras tag: "publica"
commit id: "este texto"
main publicando — não há homologação, então o PR é a revisão. A main fica na cor de linha; o ramo, no acento.mindmap
largura 665px vem de conteúdo tema por cScale0..cScale11
Hierarquia solta, sem seta. Bom para mapa de assunto e ruim para processo — não há ordem nem condição. Abaixo, o mapa da própria camada de customização que este texto descreve.
mindmap
root((mermaid))
cor
themeVariables
themeCSS
redesenhar no tema
medida
fonts.ready
useMaxWidth
largura natural
queda
sem CDN
grafo inválido
cScale0…), e a terceira vez que a mesma correção foi necessária. É o padrão que dá o título da seção seguinte.xychart-beta
largura 700px vem de configuração tema por themeVariables.xyChart
Gráfico de barras ou linha. Não é diagrama, é gráfico — e por isso tem bloco de tema próprio, largura fixa e nenhuma relação com o conteúdo. Abaixo, as larguras medidas dos oito espécimes desta página, desenhadas pelo próprio Mermaid.
xychart-beta
title "largura natural de cada espécime, em px"
x-axis [flow, seq, state, class, er, git, mind, xy]
y-axis "px" 0 --> 800
bar [643, 690, 580, 247, 596, 368, 665, 700]
xychart é ele mesmo: 700px fixos, que não mudam com o número de barras. Sem o bloco xyChart no tema, essas barras seriam bege.A camada de customização
Nada do que está acima é o Mermaid de fábrica. São nove ajustes, e cada um existe por um defeito visível — não por preferência estética:
| O que incomoda | Padrão do Mermaid | O que este site faz | Onde mora |
|---|---|---|---|
| Paleta | Lilás e pastel próprios | theme: 'base' e cada cor lida dos tokens em tempo de execução |
themeVariables |
| Fonte do rótulo | Sans genérica; themeVariables.fontSize não chega ao rótulo do nó |
JetBrains Mono a 15px, com !important, por seletor de SVG |
themeCSS |
| Rótulo como HTML | foreignObject com HTML dentro do SVG |
htmlLabels: false — o rótulo vira <text>/<tspan> e o SVG fica autocontido |
config do flowchart |
| Quebra de linha | <br/>, que não funciona |
Rótulo corrido e wrappingWidth: 170 |
config do flowchart |
| Largura | Tamanho natural, com barra de rolagem | useMaxWidth: true no desktop; tamanho natural e arraste abaixo de 900px |
ajustarEscala |
| Medida do texto | Desenha já, medindo com a fonte que estiver disponível | Espera document.fonts.ready antes do primeiro desenho |
js/publicacao.js |
| Troca de tema | Nada: a cor está embutida no SVG | Redesenha ao ouvir tema:mudou |
js/site.js emite, a publicação ouve |
| Moldura | Sem opinião | Sem borda, sem fundo, sem barra; legenda numerada por contador CSS | css/publicacao.css |
| Queda | Buraco na página, ou a bomba de erro | Mostra o fonte do grafo, com barra à esquerda | .diagram--sem-js |
As cores não são escritas na configuração: são lidas do CSS. É o que garante que existe um só lugar onde a paleta do site vive, e que o diagrama nunca discorda dele.
function cor(nome) {
return window.Site.token(nome); // getComputedStyle no :root
}
themeVariables: {
darkMode: window.Site.tema() === 'dark',
fontFamily: MONO,
primaryColor: cor('--mm-node'),
primaryBorderColor: cor('--mm-node-border'),
primaryTextColor: cor('--mm-text'),
lineColor: cor('--mm-line'),
clusterBkg: cor('--mm-cluster'),
noteBkgColor: cor('--mm-note'),
noteBorderColor: cor('--mm-note-border')
// …e mais trinta, para ator, linha de vida, ativação e estado
}
O ajuste menos óbvio e o mais importante é a espera pela fonte. O Mermaid calcula o tamanho do desenho medindo o texto renderizado; se ele roda antes de a JetBrains Mono chegar, mede com a fonte de fallback, que é mais larga. O mesmo diagrama de sequência sai com 922px em vez de 777px — número que está registrado no próprio código, de quando o defeito apareceu — e passa a ser reduzido para caber na coluna sem nenhum motivo, e sem erro nenhum no console.
var fontesProntas = (document.fonts && document.fonts.ready)
? document.fonts.ready
: Promise.resolve();
fontesProntas.then(desenhar).then(ajustarEscala);
As três camadas de obediência
Medindo tipo por tipo apareceu um padrão que eu não esperava, e que é a informação mais útil deste texto: os tipos do Mermaid não obedecem todos ao mesmo mecanismo de tema. São três grupos.
Primeiro: os que obedecem a themeVariables.
Flowchart, sequência, estado, classe, ER, requisito, bloco, arquitetura.
Configura uma vez, valem todos. É o grupo onde vive quase todo diagrama
de engenharia.
Segundo: os que têm paleta numerada própria. O
gitGraph pinta por git0..git7, o
mindmap por cScale0..cScale11, o
xychart por um bloco xyChart inteiro, e nenhum
deles olha para primaryColor. Cada um exige tratamento
separado — e o sintoma é sempre o mesmo: uma cor que não existe
no seu site aparecendo no desenho. Caqui, no caso de dois
deles.
Terceiro: os que não sobrevivem a uma cor de acento só. Aqui não é questão de configurar: gráfico de pizza e jornada de usuário precisam de cor categórica — cor que distingue, não que decora. Numa paleta de quase-branco, quase-preto e um vermelho, três fatias viram três tons quase idênticos:
pie title peso de uma publicação, em KB
"mermaid pela CDN" : 3300
"highlight.js" : 120
"html, css e js do site" : 46
A medição
A regra que eu seguia — "diagrama não pode passar de uns 840px" — estava
escrita no CLAUDE.md do repositório como estimativa. Medindo,
ela virou um número exato e ganhou explicação: 840px é a largura
da coluna do artigo numa tela de 1440px. Numa tela de 1520px ou
mais a coluna é de 920px. Acima disso, o desenho é reduzido para caber e
o rótulo encolhe na mesma proporção.
E a largura vem de dois lugares diferentes, o que decide como consertar. Nos tipos que têm espécime nesta página, o número da tabela é o deste desenho — largura de tipo cujo tamanho vem do conteúdo só significa algo junto com o grafo que a produziu:
| Tipo | Largura natural | De onde vem | Cabe em 840px? |
|---|---|---|---|
packet-beta | 1026px | fixa no tipo | não — sempre reduzido |
treemap-beta | 996px | fixa no tipo | não — sempre reduzido |
gantt | 920px | fixa no tipo | não — sempre reduzido |
journey | 900px | conteúdo | não |
timeline | 895px | conteúdo | não |
pie | 710px | conteúdo | sim |
xychart-beta | 700px | fixa no tipo | sim |
radar-beta | 700px | fixa no tipo | sim |
sequenceDiagram | 690px | conteúdo | sim |
mindmap | 665px | conteúdo | sim |
flowchart | 643px | conteúdo | sim |
sankey-beta | 600px | fixa no tipo | sim |
erDiagram | 596px | conteúdo | sim |
stateDiagram-v2 | 580px | conteúdo | sim |
quadrantChart | 500px | fixa no tipo | sim |
kanban | 425px | conteúdo | sim |
architecture-beta | 403px | conteúdo | sim |
gitGraph | 368px | conteúdo | sim |
requirementDiagram | 252px | conteúdo | sim |
classDiagram | 247px | conteúdo | sim |
block-beta | 239px | conteúdo | sim |
zenuml | — | — | não desenha: não vem no pacote |
A diferença é prática. Nos tipos cuja largura vem do
conteúdo, um diagrama largo se resolve encurtando rótulo ou
virando o eixo — mexendo no grafo. Nos de largura fixa,
mexer no grafo não muda nada: dobrei o conteúdo do
quadrantChart, do sankey, do
packet e do gantt e os quatro devolveram
exatamente a mesma largura. Lá o único caminho é configuração — ou não
usar o tipo.
O que não funciona
Seis coisas que custaram tempo, na ordem em que doeriam de novo:
-
<br/>em rótulo de nó. A tag é removida e o espaço em volta é engolido:"cabe na<br/>janela?"vira cabe najanela?. Não muda comhtmlLabelsligado nem desligado.1 -
Dois-pontos no rótulo de relação do
classDiagram. Derruba o parser inteiro e desenha a bomba de erro. Vale para o rótulo depois de:em qualquer relação de classe. -
themeVariables.fontSizenão chega ao rótulo do nó. Quem manda ali é othemeCSS, e só com!important. -
Um grafo inválido derruba os outros. Defeito deste
site, não do Mermaid: o
catchdomermaid.runé global, então um erro de sintaxe num diagrama marca todos os da página como indisponíveis. Foi assim que eu descobri o item 2 — e é o que a medição um-tipo-por-página contornou.3 -
zenumlnão vem no pacote. Está na documentação, mas omermaid.min.jsdo cdnjs não tem uma única ocorrência da palavra: exige o pacote@mermaid-js/mermaid-zenumlà parte. Dos 22 tipos, é o único que não desenha aqui. -
Ligadura em bloco de código. A JetBrains Mono desenha
!=como um sinal de diferente. Em prosa é bonito; em código é mentira, porque mostra um caractere que não está lá e que não vem no copiar. Desligada empreecode.
O que eu levo disso
Diagrama como texto vale a pena — o desenho envelhece junto com o texto, e isso resolve o problema real. Mas tematizar uma biblioteca de desenho não é escolher cores: é descobrir, um tipo por vez, onde cada cor foi parar. Três mecanismos diferentes, uma paleta numerada escondida por tipo, e um cálculo de layout que depende de a fonte já ter chegado.
E a regra que eu recomendaria a quem for fazer o mesmo é a mais chata: meça. As duas coisas que este texto corrigiu no meu próprio entendimento — de onde vem o 840px e quais tipos ignoram o tema — não apareceram lendo a documentação nem o código. Apareceram renderizando e lendo o número.
notas
-
Em bloco
notede diagrama de sequência ou de estado, a quebra de linha no próprio fonte funciona — é o jeito de ter nota em várias linhas, e está no espécime destateDiagram-v2acima. O caso apareceu primeiro em Um blog sem framework. ↩ -
O driver de medição são 90 linhas de Node sem dependência alguma —
o
WebSockete ofetchjá vêm na plataforma desde o Node 22, então falar CDP com o Chrome não exige instalar nada. Ele não está no repositório do site: é ferramenta de apuração, não parte da publicação. ↩ -
O conserto seria desenhar um bloco por vez, com
catchpor diagrama em vez de umcatchem volta dorun()inteiro. Está anotado como defeito conhecido e não corrigido na data desta publicação — quando for, esta nota vira uma correção datada, que é como este site trata texto que envelheceu. ↩