Mind Map
Mapa mental com no central, ramos radiais de 2 niveis, zoom/pan/drag interativo e layout radial automatico. Componente React Island baseado em @xyflow/react.
Uso
Quando e como utilizar o mapa mental para visualizar hierarquias radiais de ate 2 niveis de profundidade.
- JSON data — Carrega a estrutura do mapa de um endpoint JSON via atributo
data-propsno container. O componente faz fetch automatico ao montar. - Inline data — Dados passados diretamente no atributo
data-propscomo JSON serializado, sem fetch externo. - Programmatic — Monta a ilha manualmente via
ArenitoIslands.mount()passando o elemento container e as props como objeto JavaScript.
Exemplos
Veja abaixo os exemplos interativos do mapa mental, incluindo expand/collapse, layout radial e dark mode.
Anatomia
Estrutura interna do componente e seus elementos.
| Parte | Elemento | Descricao |
|---|---|---|
| Container | .mind-map | Wrapper principal com position relative e width 100% |
| Canvas | .mind-map__canvas | Area do ReactFlow com zoom, pan e drag |
| Node | .mind-map__node | No base circular com padding 12px |
| Root | .mind-map__node--root | No central 80x80, background brand, texto branco |
| Category | .mind-map__node--category | No nivel 1, 60x60, cor configuravel por categoria |
| Leaf | .mind-map__node--leaf | No nivel 2, 44x44, estilo mais sutil e leve |
| Collapsed | .mind-map__node--collapsed | Modificador para nos com filhos ocultos, borda dashed |
| Node Label | .mind-map__node-label | Texto do no, Inter 12px w500, centralizado |
| Node Icon | .mind-map__node-icon | Icone SVG 16x16 acima do label |
| Node Badge | .mind-map__node-badge | Badge numerico no canto superior direito |
| Edge | .mind-map__edge | Aresta bezier com stroke 2px |
| Edge Level 1 | .mind-map__edge--level-1 | Aresta root→categoria, stroke 2.5px |
| Edge Level 2 | .mind-map__edge--level-2 | Aresta categoria→folha, stroke 1.5px, opacity 0.6 |
| Controls | .mind-map__controls | Botoes de zoom no canto inferior esquerdo |
| Vazio | .mind-map__empty | Estado sem dados, borda dashed e padding 48px |
Layout Radial
O algoritmo de layout radial distribui os nos automaticamente ao redor do no central.
O Mind Map utiliza um algoritmo de layout radial que posiciona os nos em circulos concentricos ao redor do no raiz. O calculo de posicao segue estas regras:
- O no raiz e posicionado no centro do canvas (0, 0).
- Os nos de categoria (nivel 1) sao distribuidos uniformemente em um circulo de raio
R1 = 200px. O angulo de cada categoria e calculado por:
const angle = (index / totalCategories) * 2 * Math.PI;
const x = Math.cos(angle) * R1;
const y = Math.sin(angle) * R1;- Os nos folha (nivel 2) sao distribuidos em um arco ao redor de sua categoria pai, com raio
R2 = 120px. O arco e centrado na direcao do pai em relacao ao root:
const parentAngle = Math.atan2(parentY, parentX);
const spread = Math.PI / 3; // 60 graus de arco
const leafAngle = parentAngle - spread / 2 + (leafIndex / (totalLeaves - 1)) * spread;Quando o numero de categorias muda (por expand/collapse), o layout e recalculado com uma animacao de transicao de 300ms usando requestAnimationFrame.
Expand/Collapse
Clicar em um no de categoria alterna a visibilidade dos seus nos folha.
O comportamento de expand/collapse funciona da seguinte forma:
- Clicar em um no de categoria (
.mind-map__node--category) alterna o estadocollapsedno state interno do React. - Quando colapsado, o no recebe o modificador
--collapsed(borda tracejada) e seus nos folha e arestas de nivel 2 sao removidos do canvas. - O badge (
.mind-map__node-badge) exibe a contagem de filhos ocultos quando colapsado (ex: "3"). - O layout radial e recalculado apos cada toggle para redistribuir o espaco entre os nos visiveis.
- A transicao usa
opacityetransform: scalenos nos folha comtransition: all 200ms ease-out.
Por padrao, todos os nos iniciam expandidos. Para iniciar com nos colapsados, passe collapsed: true na propriedade data de cada categoria no JSON.
Eventos
CustomEvents disparados pelo componente para integracao com o monolito PHP.
O Mind Map emite CustomEvents no elemento container para que o codigo do monolito possa reagir a acoes do usuario sem acoplar-se ao React.
| Evento | detail | Quando dispara |
|---|---|---|
arenito:mindmap:node-click | { nodeId: string, level: number, data: object } | Usuario clica em qualquer no do mapa |
document.querySelector('[data-island="MindMap"]')
.addEventListener('arenito:mindmap:node-click', (e) => {
console.log('Node:', e.detail.nodeId);
console.log('Level:', e.detail.level);
});Propriedades
Classes CSS disponiveis para configurar o mapa mental.
| Propriedade | Tipo | Default | Descrição |
|---|---|---|---|
.mind-map | classe | — | Container principal do mapa mental. |
.mind-map__canvas | classe | — | Area do ReactFlow com zoom, pan e drag interativo. |
.mind-map__node | classe | — | No base do mapa com border-radius 50% e padding 12px. |
.mind-map__node--root | classe | — | No central (raiz) com tamanho maior (80x80) e background brand. |
.mind-map__node--category | classe | — | No de categoria (nivel 1) com 60x60 e cor configuravel. |
.mind-map__node--leaf | classe | — | No folha (nivel 2) com 44x44 e estilo mais sutil. |
.mind-map__node--collapsed | classe | — | Modificador para no com filhos colapsados (borda tracejada). |
.mind-map__node-label | classe | — | Label de texto do no (12px w500, text-align center). |
.mind-map__node-icon | classe | — | Icone opcional dentro do no (16x16, centralizado). |
.mind-map__node-badge | classe | — | Badge numerico no canto do no de categoria (contagem de filhos). |
.mind-map__edge | classe | — | Aresta curva (bezier) conectando nos com stroke 2px. |
.mind-map__edge--level-1 | classe | — | Aresta do root para categoria (stroke 2.5px, cor do no destino). |
.mind-map__edge--level-2 | classe | — | Aresta de categoria para folha (stroke 1.5px, opacity 0.6). |
.mind-map__controls | classe | — | Controles de zoom posicionados no canto inferior esquerdo. |
.mind-map__empty | classe | — | Estado vazio com borda tracejada e texto centralizado. |
Codigo
Snippets prontos para copiar e usar no seu projeto.
HTML com data-island e data-props
<div data-island="MindMap"
data-props='{
"rootLabel": "Gestao Ambiental",
"categories": [
{
"id": "cat-1",
"label": "Monitoramento",
"color": "#3B82F6",
"items": [
{ "id": "leaf-1", "label": "Qualidade da Agua" },
{ "id": "leaf-2", "label": "Emissoes Atmosfericas" },
{ "id": "leaf-3", "label": "Residuos Solidos" }
]
},
{
"id": "cat-2",
"label": "Licenciamento",
"color": "#10B981",
"items": [
{ "id": "leaf-4", "label": "LP" },
{ "id": "leaf-5", "label": "LI" },
{ "id": "leaf-6", "label": "LO" }
]
},
{
"id": "cat-3",
"label": "Fiscalizacao",
"color": "#F59E0B",
"items": [
{ "id": "leaf-7", "label": "Vistorias" },
{ "id": "leaf-8", "label": "Autuacoes" }
]
}
]
}'>
</div>Montagem programatica via ArenitoIslands.mount()
const container = document.getElementById('mindmap-container');
ArenitoIslands.mount(container, 'MindMap', {
rootLabel: 'Planejamento Estrategico',
categories: [
{
id: 'cat-1',
label: 'Objetivos',
color: '#8B5CF6',
items: [
{ id: 'leaf-1', label: 'Reducao de custos' },
{ id: 'leaf-2', label: 'Expansao de cobertura' }
]
},
{
id: 'cat-2',
label: 'Indicadores',
color: '#3B82F6',
items: [
{ id: 'leaf-3', label: 'OKR Q1' },
{ id: 'leaf-4', label: 'KPI Operacional' }
]
},
{
id: 'cat-3',
label: 'Riscos',
color: '#EF4444',
collapsed: true,
items: [
{ id: 'leaf-5', label: 'Orcamentario' },
{ id: 'leaf-6', label: 'Regulatorio' },
{ id: 'leaf-7', label: 'Operacional' }
]
}
]
});Integracao com Factory (PHP)
<div id="mindmap-areas"
data-island="MindMap"
data-endpoint="/api/mindmap/areas/<?= $organizacao_id ?>">
</div>
<script src="/assets/arenito-islands.js"></script>Fonte de Dados
O componente aceita dados em JSON com um label raiz e array de categorias contendo itens.
JSON (formato recomendado)
O objeto JSON raiz contem o rootLabel para o no central e um array categories com as ramificacoes de nivel 1, cada uma contendo um array items para os nos folha.
{
"rootLabel": "Gestao Ambiental",
"categories": [
{
"id": "cat-1",
"label": "Monitoramento",
"color": "#3B82F6",
"icon": "chart-line",
"items": [
{ "id": "leaf-1", "label": "Qualidade da Agua" },
{ "id": "leaf-2", "label": "Emissoes Atmosfericas" },
{ "id": "leaf-3", "label": "Residuos Solidos" }
]
},
{
"id": "cat-2",
"label": "Licenciamento",
"color": "#10B981",
"icon": "file-check",
"collapsed": false,
"items": [
{ "id": "leaf-4", "label": "Licenca Previa" },
{ "id": "leaf-5", "label": "Licenca de Instalacao" },
{ "id": "leaf-6", "label": "Licenca de Operacao" }
]
}
]
}Mapeamento de campos
| Campo | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
rootLabel | string | Sim | Texto exibido no no central (raiz) do mapa |
categories | array | Sim | Array de categorias (nos de nivel 1) |
id | string | Sim | Identificador unico da categoria ou item |
label | string | Sim | Texto exibido no no |
color | string | Sim (cat) | Cor hexadecimal do no de categoria e suas arestas |
icon | string | Nao | Nome do icone exibido dentro do no de categoria |
collapsed | boolean | Nao | Se true, inicia com filhos ocultos (padrao: false) |
items | array | Sim (cat) | Array de nos folha pertencentes a categoria |
Dark Mode
O mapa mental adapta automaticamente ao tema escuro via classe .dark.
No dark mode, o canvas recebe um fundo escuro e os nos ajustam suas cores de borda e fill para manter contraste. As arestas reduzem levemente a opacidade para evitar excesso de destaque.
| Propriedade | Light | Dark |
|---|---|---|
| Canvas background | --surface-base-neutral-container-default | --surface-base-neutral-container-default |
| Root node background | --accent-500 | --accent-400 |
| Root node text | #FFFFFF | #FFFFFF |
| Category node border | rgba(0,0,0,0.12) | rgba(255,255,255,0.12) |
| Category node background | Cor da categoria com opacity 0.15 | Cor da categoria com opacity 0.20 |
| Leaf node background | --surface-base-neutral-container-emphasis | --surface-base-neutral-container-emphasis |
| Leaf node text | --text-base-neutral-default | --text-base-neutral-default |
| Edge level 1 stroke | Cor da categoria destino | Cor da categoria destino com opacity 0.8 |
| Edge level 2 stroke | --border-base-neutral-default | --border-base-neutral-default |
| Controls background | --surface-base-neutral-container-default | --surface-base-neutral-container-default |
| Badge background | --surface-base-neutral-container-emphasis | --surface-base-neutral-container-emphasis |
Os tokens semanticos de superficie e texto herdam automaticamente os valores do dark mode. As cores das categorias sao mantidas em ambos os temas, com leve ajuste de opacidade no fundo dos nos para garantir contraste adequado.