@aurea-uds/react 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/graph-geometria.d.ts +84 -0
- package/dist/graph-geometria.js +437 -0
- package/dist/graph.d.ts +61 -3
- package/dist/graph.js +856 -158
- package/dist/pure.d.ts +22 -0
- package/dist/pure.js +2 -2
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -47,6 +47,30 @@ Seis módulos trazem uma engine opcional e por isso ficam **fora do entry point
|
|
|
47
47
|
| `@aurea-uds/react/chart` | `recharts` |
|
|
48
48
|
| `@aurea-uds/react/graph` | `@xyflow/react` |
|
|
49
49
|
|
|
50
|
+
### O mapa (`DependencyGraph`) e as três peças opcionais dele
|
|
51
|
+
|
|
52
|
+
Cada uma só é carregada quando a função dela é pedida. Sem ela, o mapa avisa uma vez no console e
|
|
53
|
+
segue sem a função — não quebra a tela.
|
|
54
|
+
|
|
55
|
+
| Para | Instale |
|
|
56
|
+
|---|---|
|
|
57
|
+
| arrumar em camadas, em árvore ou em círculo (`layout="layered"`, `"tree"`, `"radial"`) | `elkjs` |
|
|
58
|
+
| a linha que desvia de um nó depois de arrumar à mão | `@tisoap/react-flow-smart-edge` |
|
|
59
|
+
| a foto do mapa (`toPng()`, `toSvg()`) | `modern-screenshot` |
|
|
60
|
+
|
|
61
|
+
**O arrumador fora da tela principal (`layoutWorker`).** Num mapa grande, a arrumação pode prender a
|
|
62
|
+
tela por alguns segundos. O mapa aceita um trabalhador (Web Worker) que o app cria — cada empacotador
|
|
63
|
+
cria trabalhador do seu jeito, e por isso quem entrega é o app. No Vite:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import TrabalhadorDoElk from "elkjs/lib/elk-worker.min.js?worker";
|
|
67
|
+
|
|
68
|
+
<DependencyGraph layout="layered" layoutWorker={() => new TrabalhadorDoElk()} … />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Em qualquer outro empacotador, o jeito que sempre funciona: copie `node_modules/elkjs/lib/elk-worker.min.js`
|
|
72
|
+
para a pasta pública do app e passe `layoutWorker={() => new Worker("/elk-worker.min.js")}`.
|
|
73
|
+
|
|
50
74
|
## Hooks
|
|
51
75
|
|
|
52
76
|
Quatro são públicos, e eles são a parte da API que **não tem página no catálogo** — o catálogo
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type Ponto = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
/** Retângulo no plano do mapa. `larg`/`alt`, e não `width`/`height`: o check 23 lê o fonte inteiro. */
|
|
6
|
+
export type Caixa = {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
larg: number;
|
|
10
|
+
alt: number;
|
|
11
|
+
};
|
|
12
|
+
export type Borda = "left" | "center" | "right" | "top" | "middle" | "bottom";
|
|
13
|
+
export declare function alinhar(caixas: ReadonlyMap<string, Caixa>, borda: Borda): Map<string, Ponto>;
|
|
14
|
+
/**
|
|
15
|
+
* Distribuir mantém as DUAS PONTAS paradas e deixa os vãos iguais entre os do meio. Se os nós já
|
|
16
|
+
* se sobrepõem (o vão daria negativo), o passo igual vai para os CENTROS, como no draw.io.
|
|
17
|
+
*/
|
|
18
|
+
export declare function distribuir(caixas: ReadonlyMap<string, Caixa>, eixo: "horizontal" | "vertical"): Map<string, Ponto>;
|
|
19
|
+
export declare function subarvores(ids: readonly string[], arestas: readonly {
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
}[], raiz?: string): Map<string, string[]>;
|
|
23
|
+
export declare function recolher(ids: readonly string[], pais: ReadonlyMap<string, string>, dominados: ReadonlyMap<string, readonly string[]>, recolhidos: ReadonlySet<string>, contemFilhos: ReadonlySet<string>): {
|
|
24
|
+
rep: Map<string, string>;
|
|
25
|
+
guardados: Map<string, number>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* As linhas depois do fechamento. A que fica inteira dentro de um fechado some; a que saía de um
|
|
29
|
+
* escondido passa a sair do representante; e as que ficaram entre o MESMO par viram UMA linha com o
|
|
30
|
+
* número — a "aresta agregada" do mercado (o padrão aberto de quem fecha grupo). As inteiras ficam
|
|
31
|
+
* como estavam.
|
|
32
|
+
*/
|
|
33
|
+
export declare function remapear<T extends {
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
}>(arestas: readonly T[], rep: ReadonlyMap<string, string>): {
|
|
37
|
+
item: T;
|
|
38
|
+
de: string;
|
|
39
|
+
para: string;
|
|
40
|
+
n: number;
|
|
41
|
+
}[];
|
|
42
|
+
/** Tira os pontos repetidos e os do meio de um trecho reto: a ponte e o canto contam os trechos. */
|
|
43
|
+
export declare function simplificar(pontos: readonly Ponto[]): Ponto[];
|
|
44
|
+
/** O ponto no MEIO do comprimento — onde fica o rótulo e a pastilha da linha. */
|
|
45
|
+
export declare function meioDoTracado(pontos: readonly Ponto[]): Ponto;
|
|
46
|
+
/** Uma ponte: o intervalo do trecho, medido no eixo dele, em que a linha salta. */
|
|
47
|
+
export type Ponte = {
|
|
48
|
+
de: number;
|
|
49
|
+
ate: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* AS PONTES (MNT-12.3), na técnica do draw.io: a linha desenhada DEPOIS salta sobre as de antes,
|
|
53
|
+
* só onde um trecho horizontal cruza um vertical — dois trechos no mesmo eixo não se cruzam. O
|
|
54
|
+
* cruzamento perto demais de uma dobra não ganha ponte (o arco bateria no canto). Pontes que se
|
|
55
|
+
* encostam viram uma só, mais larga. A caixa de cada linha filtra os pares antes da conta.
|
|
56
|
+
*/
|
|
57
|
+
export declare function pontes(linhas: readonly {
|
|
58
|
+
id: string;
|
|
59
|
+
pontos: readonly Ponto[];
|
|
60
|
+
}[], raio: number, canto: number): Map<string, Map<number, Ponte[]>>;
|
|
61
|
+
/**
|
|
62
|
+
* O `d` do SVG: a linha pelos pontos, com o canto arredondado (o `--radius-sm` da casa, ou menos se
|
|
63
|
+
* o trecho for curto) e o salto de cada ponte — meia elipse, por cima no trecho deitado e à direita
|
|
64
|
+
* no de pé, seja qual for o sentido do trecho.
|
|
65
|
+
*/
|
|
66
|
+
export declare function tracado(pontos: readonly Ponto[], canto: number, saltos?: ReadonlyMap<number, readonly Ponte[]>, raio?: number): string;
|
|
67
|
+
/** De que lado do nó a linha sai (ou entra), lido do primeiro (ou último) trecho. */
|
|
68
|
+
export declare function ladoDoTrecho(de: Ponto, para: Ponto, saindo: boolean): "top" | "right" | "bottom" | "left";
|
|
69
|
+
/** O trecho atravessa o MIOLO da caixa (encostar na borda não conta — é assim que a linha chega). */
|
|
70
|
+
export declare function trechoCruza(a: Ponto, b: Ponto, c: Caixa): boolean;
|
|
71
|
+
export declare const rotaCruza: (pontos: readonly Ponto[], caixas: Iterable<Caixa>) => boolean;
|
|
72
|
+
/** Onde a reta entre os dois centros sai da caixa: a ponta da linha "flutuante" (arrumação radial). */
|
|
73
|
+
export declare function bordaNaDirecao(c: Caixa, para: Ponto): Ponto;
|
|
74
|
+
/**
|
|
75
|
+
* Nenhum rótulo cobre outro rótulo nem um nó. Não há biblioteca livre que POSICIONE sem sobrepor;
|
|
76
|
+
* o que as bibliotecas abertas fazem é ESCONDER por prioridade: o de prioridade maior fica, e no empate fica
|
|
77
|
+
* o que veio antes na lista do app. Devolve os que somem.
|
|
78
|
+
*/
|
|
79
|
+
export declare function rotulosQueSomem(rotulos: readonly {
|
|
80
|
+
id: string;
|
|
81
|
+
caixa: Caixa;
|
|
82
|
+
prioridade: number;
|
|
83
|
+
}[], obstaculos: readonly Caixa[]): Set<string>;
|
|
84
|
+
export declare function contornoDeNuvem(larg: number, alt: number): string;
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
// ── ALINHAR E DISTRIBUIR (MNT-13.3) ─────────────────────────────────────────────────────────
|
|
2
|
+
// A regra é a do draw.io e a do Figma: alinha-se pela borda MAIS EXTERNA da seleção (à esquerda,
|
|
3
|
+
// a menor; à direita, a maior), e o centro é o meio da caixa que envolve todos.
|
|
4
|
+
export function alinhar(caixas, borda) {
|
|
5
|
+
const l = [...caixas.values()];
|
|
6
|
+
const minX = Math.min(...l.map(c => c.x)), maxX = Math.max(...l.map(c => c.x + c.larg));
|
|
7
|
+
const minY = Math.min(...l.map(c => c.y)), maxY = Math.max(...l.map(c => c.y + c.alt));
|
|
8
|
+
const saida = new Map();
|
|
9
|
+
for (const [id, c] of caixas) {
|
|
10
|
+
const x = borda === "left" ? minX : borda === "right" ? maxX - c.larg : borda === "center" ? (minX + maxX - c.larg) / 2 : c.x;
|
|
11
|
+
const y = borda === "top" ? minY : borda === "bottom" ? maxY - c.alt : borda === "middle" ? (minY + maxY - c.alt) / 2 : c.y;
|
|
12
|
+
saida.set(id, { x: Math.round(x), y: Math.round(y) });
|
|
13
|
+
}
|
|
14
|
+
return saida;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Distribuir mantém as DUAS PONTAS paradas e deixa os vãos iguais entre os do meio. Se os nós já
|
|
18
|
+
* se sobrepõem (o vão daria negativo), o passo igual vai para os CENTROS, como no draw.io.
|
|
19
|
+
*/
|
|
20
|
+
export function distribuir(caixas, eixo) {
|
|
21
|
+
const h = eixo === "horizontal";
|
|
22
|
+
const centro = (c) => h ? c.x + c.larg / 2 : c.y + c.alt / 2;
|
|
23
|
+
const l = [...caixas].sort(([, a], [, b]) => centro(a) - centro(b));
|
|
24
|
+
const saida = new Map(l.map(([id, c]) => [id, { x: c.x, y: c.y }]));
|
|
25
|
+
if (l.length < 3)
|
|
26
|
+
return saida;
|
|
27
|
+
const primeira = l[0][1], ultima = l[l.length - 1][1];
|
|
28
|
+
const inicio = h ? primeira.x : primeira.y, fim = h ? ultima.x + ultima.larg : ultima.y + ultima.alt;
|
|
29
|
+
const ocupado = l.reduce((s, [, c]) => s + (h ? c.larg : c.alt), 0);
|
|
30
|
+
const vao = (fim - inicio - ocupado) / (l.length - 1);
|
|
31
|
+
if (vao >= 0) {
|
|
32
|
+
let cursor = inicio;
|
|
33
|
+
for (const [id, c] of l) {
|
|
34
|
+
const p = saida.get(id);
|
|
35
|
+
if (h)
|
|
36
|
+
p.x = Math.round(cursor);
|
|
37
|
+
else
|
|
38
|
+
p.y = Math.round(cursor);
|
|
39
|
+
cursor += (h ? c.larg : c.alt) + vao;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const c0 = centro(primeira), passo = (centro(ultima) - c0) / (l.length - 1);
|
|
44
|
+
l.forEach(([id, c], i) => { const p = saida.get(id); if (h)
|
|
45
|
+
p.x = Math.round(c0 + i * passo - c.larg / 2);
|
|
46
|
+
else
|
|
47
|
+
p.y = Math.round(c0 + i * passo - c.alt / 2); });
|
|
48
|
+
}
|
|
49
|
+
return saida;
|
|
50
|
+
}
|
|
51
|
+
// ── O QUE UM NÓ DOMINA: a subárvore que se recolhe (MNT-16.7) ───────────────────────────────
|
|
52
|
+
// Recolher um switch esconde o que SÓ se alcança passando por ele. Numa rede com cabo redundante, a
|
|
53
|
+
// árvore "de quem foi achado primeiro" esconderia um switch de acesso que continua ligado pelo outro
|
|
54
|
+
// núcleo — e a linha que sobra apontaria para o nada. O certo é o DOMINADOR: X domina Y quando todo
|
|
55
|
+
// caminho da raiz até Y passa por X. Algoritmo iterativo de Cooper, Harvey e Kennedy ("A Simple, Fast
|
|
56
|
+
// Dominance Algorithm", 2001), sobre o sentido `from → to` das arestas — o mesmo que arruma as camadas.
|
|
57
|
+
// As raízes são o `raiz` pedido, os nós sem entrada e, por último, quem sobrou num ciclo sem entrada;
|
|
58
|
+
// todas penduradas numa raiz virtual.
|
|
59
|
+
export function subarvores(ids, arestas, raiz) {
|
|
60
|
+
const sai = new Map(ids.map(i => [i, []])), entra = new Map(ids.map(i => [i, []]));
|
|
61
|
+
for (const e of arestas)
|
|
62
|
+
if (sai.has(e.from) && sai.has(e.to) && e.from !== e.to) {
|
|
63
|
+
sai.get(e.from).push(e.to);
|
|
64
|
+
entra.get(e.to).push(e.from);
|
|
65
|
+
}
|
|
66
|
+
const VIRTUAL = "\u0000";
|
|
67
|
+
const raizes = [];
|
|
68
|
+
if (raiz && sai.has(raiz))
|
|
69
|
+
raizes.push(raiz);
|
|
70
|
+
for (const i of ids)
|
|
71
|
+
if (i !== raiz && entra.get(i).length === 0)
|
|
72
|
+
raizes.push(i);
|
|
73
|
+
// Pós-ordem a partir da raiz virtual, sem recursão (um mapa de 500 nós em fila não estoura a pilha).
|
|
74
|
+
const ordem = new Map(), posOrdem = [];
|
|
75
|
+
const visitado = new Set([VIRTUAL]);
|
|
76
|
+
const descer = (inicio) => {
|
|
77
|
+
if (visitado.has(inicio))
|
|
78
|
+
return;
|
|
79
|
+
visitado.add(inicio);
|
|
80
|
+
const pilha = [[inicio, 0]];
|
|
81
|
+
while (pilha.length) {
|
|
82
|
+
const topo = pilha[pilha.length - 1], filhos = sai.get(topo[0]);
|
|
83
|
+
if (topo[1] < filhos.length) {
|
|
84
|
+
const f = filhos[topo[1]++];
|
|
85
|
+
if (!visitado.has(f)) {
|
|
86
|
+
visitado.add(f);
|
|
87
|
+
pilha.push([f, 0]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
pilha.pop();
|
|
92
|
+
ordem.set(topo[0], posOrdem.length);
|
|
93
|
+
posOrdem.push(topo[0]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
for (const r of raizes)
|
|
98
|
+
descer(r);
|
|
99
|
+
for (const i of ids)
|
|
100
|
+
if (!visitado.has(i)) {
|
|
101
|
+
raizes.push(i);
|
|
102
|
+
descer(i);
|
|
103
|
+
}
|
|
104
|
+
ordem.set(VIRTUAL, posOrdem.length);
|
|
105
|
+
const ehRaiz = new Set(raizes);
|
|
106
|
+
const pred = (i) => ehRaiz.has(i) ? [VIRTUAL, ...entra.get(i)] : entra.get(i);
|
|
107
|
+
const idom = new Map([[VIRTUAL, VIRTUAL]]);
|
|
108
|
+
const cruzar = (a, b) => {
|
|
109
|
+
while (a !== b) {
|
|
110
|
+
while (ordem.get(a) < ordem.get(b))
|
|
111
|
+
a = idom.get(a);
|
|
112
|
+
while (ordem.get(b) < ordem.get(a))
|
|
113
|
+
b = idom.get(b);
|
|
114
|
+
}
|
|
115
|
+
return a;
|
|
116
|
+
};
|
|
117
|
+
const reversa = [...posOrdem].reverse();
|
|
118
|
+
for (let mudou = true; mudou;) {
|
|
119
|
+
mudou = false;
|
|
120
|
+
for (const b of reversa) {
|
|
121
|
+
let novo;
|
|
122
|
+
for (const p of pred(b))
|
|
123
|
+
if (idom.has(p))
|
|
124
|
+
novo = novo === undefined ? p : cruzar(p, novo);
|
|
125
|
+
if (novo !== undefined && idom.get(b) !== novo) {
|
|
126
|
+
idom.set(b, novo);
|
|
127
|
+
mudou = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const filhos = new Map();
|
|
132
|
+
for (const [n, d] of idom)
|
|
133
|
+
if (n !== VIRTUAL)
|
|
134
|
+
filhos.set(d, [...(filhos.get(d) ?? []), n]);
|
|
135
|
+
const saida = new Map();
|
|
136
|
+
for (const i of ids) {
|
|
137
|
+
const desc = [], fila = [...(filhos.get(i) ?? [])];
|
|
138
|
+
while (fila.length) {
|
|
139
|
+
const n = fila.shift();
|
|
140
|
+
desc.push(n);
|
|
141
|
+
fila.push(...(filhos.get(n) ?? []));
|
|
142
|
+
}
|
|
143
|
+
if (desc.length)
|
|
144
|
+
saida.set(i, desc);
|
|
145
|
+
}
|
|
146
|
+
return saida;
|
|
147
|
+
}
|
|
148
|
+
// ── O QUE FECHA, E QUEM FICA NO LUGAR (MNT-16.4, 16.6 e 16.7) ───────────────────────────────
|
|
149
|
+
// Dois jeitos de esconder: o CONTÊINER fechado (site, andar, rack — o `parentId`) esconde tudo o que
|
|
150
|
+
// está dentro dele; o nó com a subárvore recolhida esconde o que ele domina. O escondido não some do
|
|
151
|
+
// mapa: as linhas dele passam a sair do REPRESENTANTE — o contêiner fechado mais de fora, ou o nó
|
|
152
|
+
// recolhido mais perto da raiz —, que mostra quantos guarda.
|
|
153
|
+
export function recolher(ids, pais, dominados, recolhidos, contemFilhos) {
|
|
154
|
+
const porSubarvore = new Map();
|
|
155
|
+
// O de fora primeiro: quem domina outro domina MAIS nós, então a ordem por tamanho já é a de fora para dentro.
|
|
156
|
+
const quem = [...recolhidos].filter(x => !contemFilhos.has(x) && dominados.has(x)).sort((a, b) => dominados.get(b).length - dominados.get(a).length);
|
|
157
|
+
for (const x of quem)
|
|
158
|
+
for (const d of dominados.get(x))
|
|
159
|
+
if (!porSubarvore.has(d) && !porSubarvore.has(x))
|
|
160
|
+
porSubarvore.set(d, x);
|
|
161
|
+
const conteinerFechadoDeFora = (id) => {
|
|
162
|
+
let achado;
|
|
163
|
+
for (let p = pais.get(id), guarda = 0; p && guarda < ids.length; p = pais.get(p), guarda++)
|
|
164
|
+
if (recolhidos.has(p))
|
|
165
|
+
achado = p;
|
|
166
|
+
return achado;
|
|
167
|
+
};
|
|
168
|
+
const rep = new Map(), guardados = new Map();
|
|
169
|
+
for (const id of ids) {
|
|
170
|
+
const r1 = porSubarvore.get(id) ?? id;
|
|
171
|
+
const r2 = conteinerFechadoDeFora(r1) ?? r1;
|
|
172
|
+
rep.set(id, r2);
|
|
173
|
+
if (r2 !== id && !contemFilhos.has(id))
|
|
174
|
+
guardados.set(r2, (guardados.get(r2) ?? 0) + 1);
|
|
175
|
+
}
|
|
176
|
+
return { rep, guardados };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* As linhas depois do fechamento. A que fica inteira dentro de um fechado some; a que saía de um
|
|
180
|
+
* escondido passa a sair do representante; e as que ficaram entre o MESMO par viram UMA linha com o
|
|
181
|
+
* número — a "aresta agregada" do mercado (o padrão aberto de quem fecha grupo). As inteiras ficam
|
|
182
|
+
* como estavam.
|
|
183
|
+
*/
|
|
184
|
+
export function remapear(arestas, rep) {
|
|
185
|
+
const saida = [];
|
|
186
|
+
const agregada = new Map();
|
|
187
|
+
for (const e of arestas) {
|
|
188
|
+
const de = rep.get(e.from) ?? e.from, para = rep.get(e.to) ?? e.to;
|
|
189
|
+
if (de === para)
|
|
190
|
+
continue;
|
|
191
|
+
if (de === e.from && para === e.to) {
|
|
192
|
+
saida.push({ item: e, de, para, n: 1 });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const k = de < para ? `${de}|${para}` : `${para}|${de}`;
|
|
196
|
+
const ja = agregada.get(k);
|
|
197
|
+
if (ja) {
|
|
198
|
+
ja.n++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const nova = { item: e, de, para, n: 1 };
|
|
202
|
+
agregada.set(k, nova);
|
|
203
|
+
saida.push(nova);
|
|
204
|
+
}
|
|
205
|
+
return saida;
|
|
206
|
+
}
|
|
207
|
+
// ── O TRAÇADO (MNT-12.1 e 12.3) ───────────────────────────────────────────────────────────────
|
|
208
|
+
/** Tira os pontos repetidos e os do meio de um trecho reto: a ponte e o canto contam os trechos. */
|
|
209
|
+
export function simplificar(pontos) {
|
|
210
|
+
const saida = [];
|
|
211
|
+
for (const p of pontos) {
|
|
212
|
+
const u = saida[saida.length - 1];
|
|
213
|
+
if (u && Math.abs(u.x - p.x) < 0.5 && Math.abs(u.y - p.y) < 0.5)
|
|
214
|
+
continue;
|
|
215
|
+
const a = saida[saida.length - 2];
|
|
216
|
+
if (a && u && Math.abs((u.x - a.x) * (p.y - u.y) - (u.y - a.y) * (p.x - u.x)) < 0.5) {
|
|
217
|
+
saida[saida.length - 1] = { x: p.x, y: p.y };
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
saida.push({ x: p.x, y: p.y });
|
|
221
|
+
}
|
|
222
|
+
return saida;
|
|
223
|
+
}
|
|
224
|
+
const comprimento = (a, b) => Math.hypot(b.x - a.x, b.y - a.y);
|
|
225
|
+
/** O ponto no MEIO do comprimento — onde fica o rótulo e a pastilha da linha. */
|
|
226
|
+
export function meioDoTracado(pontos) {
|
|
227
|
+
const total = pontos.slice(1).reduce((s, p, i) => s + comprimento(pontos[i], p), 0);
|
|
228
|
+
let falta = total / 2;
|
|
229
|
+
for (let i = 1; i < pontos.length; i++) {
|
|
230
|
+
const d = comprimento(pontos[i - 1], pontos[i]);
|
|
231
|
+
if (d >= falta && d > 0) {
|
|
232
|
+
const t = falta / d;
|
|
233
|
+
return { x: pontos[i - 1].x + (pontos[i].x - pontos[i - 1].x) * t, y: pontos[i - 1].y + (pontos[i].y - pontos[i - 1].y) * t };
|
|
234
|
+
}
|
|
235
|
+
falta -= d;
|
|
236
|
+
}
|
|
237
|
+
return pontos[0] ?? { x: 0, y: 0 };
|
|
238
|
+
}
|
|
239
|
+
const trechos = (p) => p.slice(1).map((b, i) => ({ a: p[i], b, i }));
|
|
240
|
+
const horizontal = (t) => Math.abs(t.a.y - t.b.y) < 0.5 && Math.abs(t.a.x - t.b.x) >= 0.5;
|
|
241
|
+
const vertical = (t) => Math.abs(t.a.x - t.b.x) < 0.5 && Math.abs(t.a.y - t.b.y) >= 0.5;
|
|
242
|
+
/**
|
|
243
|
+
* AS PONTES (MNT-12.3), na técnica do draw.io: a linha desenhada DEPOIS salta sobre as de antes,
|
|
244
|
+
* só onde um trecho horizontal cruza um vertical — dois trechos no mesmo eixo não se cruzam. O
|
|
245
|
+
* cruzamento perto demais de uma dobra não ganha ponte (o arco bateria no canto). Pontes que se
|
|
246
|
+
* encostam viram uma só, mais larga. A caixa de cada linha filtra os pares antes da conta.
|
|
247
|
+
*/
|
|
248
|
+
export function pontes(linhas, raio, canto) {
|
|
249
|
+
const caixa = (p) => ({ x0: Math.min(...p.map(q => q.x)), x1: Math.max(...p.map(q => q.x)), y0: Math.min(...p.map(q => q.y)), y1: Math.max(...p.map(q => q.y)) });
|
|
250
|
+
const preparadas = linhas.map(l => ({ id: l.id, t: trechos(l.pontos), c: caixa(l.pontos) }));
|
|
251
|
+
const saida = new Map();
|
|
252
|
+
const margem = raio + canto;
|
|
253
|
+
for (let i = 1; i < preparadas.length; i++) {
|
|
254
|
+
const L = preparadas[i];
|
|
255
|
+
const porTrecho = new Map();
|
|
256
|
+
for (let j = 0; j < i; j++) {
|
|
257
|
+
const M = preparadas[j];
|
|
258
|
+
if (M.c.x1 < L.c.x0 || M.c.x0 > L.c.x1 || M.c.y1 < L.c.y0 || M.c.y0 > L.c.y1)
|
|
259
|
+
continue;
|
|
260
|
+
for (const s of L.t) {
|
|
261
|
+
const sh = horizontal(s), sv = vertical(s);
|
|
262
|
+
if (!sh && !sv)
|
|
263
|
+
continue;
|
|
264
|
+
for (const t of M.t) {
|
|
265
|
+
if (sh ? !vertical(t) : !horizontal(t))
|
|
266
|
+
continue;
|
|
267
|
+
// O cruzamento: o x do vertical com o y do horizontal.
|
|
268
|
+
const cx = sh ? t.a.x : s.a.x, cy = sh ? s.a.y : t.a.y;
|
|
269
|
+
const [s0, s1] = sh ? [Math.min(s.a.x, s.b.x), Math.max(s.a.x, s.b.x)] : [Math.min(s.a.y, s.b.y), Math.max(s.a.y, s.b.y)];
|
|
270
|
+
const [t0, t1] = sh ? [Math.min(t.a.y, t.b.y), Math.max(t.a.y, t.b.y)] : [Math.min(t.a.x, t.b.x), Math.max(t.a.x, t.b.x)];
|
|
271
|
+
const naL = sh ? cx : cy, naM = sh ? cy : cx;
|
|
272
|
+
if (naL <= s0 + margem || naL >= s1 - margem || naM <= t0 + 0.5 || naM >= t1 - 0.5)
|
|
273
|
+
continue;
|
|
274
|
+
porTrecho.set(s.i, [...(porTrecho.get(s.i) ?? []), naL]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!porTrecho.size)
|
|
279
|
+
continue;
|
|
280
|
+
const m = new Map();
|
|
281
|
+
for (const [k, cs] of porTrecho) {
|
|
282
|
+
const ord = [...cs].sort((a, b) => a - b), juntas = [];
|
|
283
|
+
for (const c of ord) {
|
|
284
|
+
const u = juntas[juntas.length - 1];
|
|
285
|
+
if (u && c - raio <= u.ate)
|
|
286
|
+
u.ate = c + raio;
|
|
287
|
+
else
|
|
288
|
+
juntas.push({ de: c - raio, ate: c + raio });
|
|
289
|
+
}
|
|
290
|
+
m.set(k, juntas);
|
|
291
|
+
}
|
|
292
|
+
saida.set(L.id, m);
|
|
293
|
+
}
|
|
294
|
+
return saida;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* O `d` do SVG: a linha pelos pontos, com o canto arredondado (o `--radius-sm` da casa, ou menos se
|
|
298
|
+
* o trecho for curto) e o salto de cada ponte — meia elipse, por cima no trecho deitado e à direita
|
|
299
|
+
* no de pé, seja qual for o sentido do trecho.
|
|
300
|
+
*/
|
|
301
|
+
export function tracado(pontos, canto, saltos, raio = 0) {
|
|
302
|
+
const p = pontos;
|
|
303
|
+
if (p.length < 2)
|
|
304
|
+
return "";
|
|
305
|
+
const dir = (a, b) => { const d = comprimento(a, b) || 1; return { x: (b.x - a.x) / d, y: (b.y - a.y) / d }; };
|
|
306
|
+
const r = (k) => k <= 0 || k >= p.length - 1 ? 0 : Math.min(canto, comprimento(p[k - 1], p[k]) / 2, comprimento(p[k], p[k + 1]) / 2);
|
|
307
|
+
const n = (v) => Math.round(v * 100) / 100;
|
|
308
|
+
let d = `M${n(p[0].x)},${n(p[0].y)}`;
|
|
309
|
+
for (let k = 0; k < p.length - 1; k++) {
|
|
310
|
+
const a = p[k], b = p[k + 1], u = dir(a, b);
|
|
311
|
+
const fimX = b.x - u.x * r(k + 1), fimY = b.y - u.y * r(k + 1);
|
|
312
|
+
const t = { a, b, i: k };
|
|
313
|
+
const lista = saltos?.get(k);
|
|
314
|
+
if (lista && (horizontal(t) || vertical(t))) {
|
|
315
|
+
const h = horizontal(t), sentido = h ? Math.sign(b.x - a.x) : Math.sign(b.y - a.y);
|
|
316
|
+
const ord = [...lista].sort((x, y) => sentido * (x.de - y.de));
|
|
317
|
+
for (const s of ord) {
|
|
318
|
+
const [ini, fim] = sentido > 0 ? [s.de, s.ate] : [s.ate, s.de];
|
|
319
|
+
const meia = (s.ate - s.de) / 2;
|
|
320
|
+
d += h ? ` L${n(ini)},${n(a.y)} A${n(meia)},${n(raio)} 0 0 ${sentido > 0 ? 1 : 0} ${n(fim)},${n(a.y)}`
|
|
321
|
+
: ` L${n(a.x)},${n(ini)} A${n(raio)},${n(meia)} 0 0 ${sentido > 0 ? 1 : 0} ${n(a.x)},${n(fim)}`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
d += ` L${n(fimX)},${n(fimY)}`;
|
|
325
|
+
if (k + 1 < p.length - 1 && r(k + 1) > 0) {
|
|
326
|
+
const v = dir(b, p[k + 2]);
|
|
327
|
+
d += ` Q${n(b.x)},${n(b.y)} ${n(b.x + v.x * r(k + 1))},${n(b.y + v.y * r(k + 1))}`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return d;
|
|
331
|
+
}
|
|
332
|
+
/** De que lado do nó a linha sai (ou entra), lido do primeiro (ou último) trecho. */
|
|
333
|
+
export function ladoDoTrecho(de, para, saindo) {
|
|
334
|
+
const dx = para.x - de.x, dy = para.y - de.y;
|
|
335
|
+
if (Math.abs(dy) >= Math.abs(dx))
|
|
336
|
+
return saindo ? (dy > 0 ? "bottom" : "top") : (dy > 0 ? "top" : "bottom");
|
|
337
|
+
return saindo ? (dx > 0 ? "right" : "left") : (dx > 0 ? "left" : "right");
|
|
338
|
+
}
|
|
339
|
+
// ── A ROTA AINDA VALE? (MNT-12.1) ─────────────────────────────────────────────────────────────
|
|
340
|
+
/** O trecho atravessa o MIOLO da caixa (encostar na borda não conta — é assim que a linha chega). */
|
|
341
|
+
export function trechoCruza(a, b, c) {
|
|
342
|
+
const x0 = c.x + 1, x1 = c.x + c.larg - 1, y0 = c.y + 1, y1 = c.y + c.alt - 1;
|
|
343
|
+
if (x1 <= x0 || y1 <= y0)
|
|
344
|
+
return false;
|
|
345
|
+
// Liang–Barsky: recorta o segmento contra o retângulo; sobra pedaço, cruzou.
|
|
346
|
+
let t0 = 0, t1 = 1;
|
|
347
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
348
|
+
for (const [p, q] of [[-dx, a.x - x0], [dx, x1 - a.x], [-dy, a.y - y0], [dy, y1 - a.y]]) {
|
|
349
|
+
if (p === 0) {
|
|
350
|
+
if (q < 0)
|
|
351
|
+
return false;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const t = q / p;
|
|
355
|
+
if (p < 0) {
|
|
356
|
+
if (t > t1)
|
|
357
|
+
return false;
|
|
358
|
+
if (t > t0)
|
|
359
|
+
t0 = t;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
if (t < t0)
|
|
363
|
+
return false;
|
|
364
|
+
if (t < t1)
|
|
365
|
+
t1 = t;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return t1 > t0;
|
|
369
|
+
}
|
|
370
|
+
export const rotaCruza = (pontos, caixas) => {
|
|
371
|
+
for (const c of caixas)
|
|
372
|
+
for (let i = 1; i < pontos.length; i++)
|
|
373
|
+
if (trechoCruza(pontos[i - 1], pontos[i], c))
|
|
374
|
+
return true;
|
|
375
|
+
return false;
|
|
376
|
+
};
|
|
377
|
+
/** Onde a reta entre os dois centros sai da caixa: a ponta da linha "flutuante" (arrumação radial). */
|
|
378
|
+
export function bordaNaDirecao(c, para) {
|
|
379
|
+
const cx = c.x + c.larg / 2, cy = c.y + c.alt / 2, dx = para.x - cx, dy = para.y - cy;
|
|
380
|
+
if (!dx && !dy)
|
|
381
|
+
return { x: cx, y: cy };
|
|
382
|
+
const t = Math.min(dx ? (c.larg / 2) / Math.abs(dx) : Infinity, dy ? (c.alt / 2) / Math.abs(dy) : Infinity);
|
|
383
|
+
return { x: cx + dx * t, y: cy + dy * t };
|
|
384
|
+
}
|
|
385
|
+
// ── OS RÓTULOS QUE SOMEM (MNT-12.5) ─────────────────────────────────────────────────────────
|
|
386
|
+
const encostam = (a, b) => a.x < b.x + b.larg && b.x < a.x + a.larg && a.y < b.y + b.alt && b.y < a.y + a.alt;
|
|
387
|
+
/**
|
|
388
|
+
* Nenhum rótulo cobre outro rótulo nem um nó. Não há biblioteca livre que POSICIONE sem sobrepor;
|
|
389
|
+
* o que as bibliotecas abertas fazem é ESCONDER por prioridade: o de prioridade maior fica, e no empate fica
|
|
390
|
+
* o que veio antes na lista do app. Devolve os que somem.
|
|
391
|
+
*/
|
|
392
|
+
export function rotulosQueSomem(rotulos, obstaculos) {
|
|
393
|
+
const ordem = rotulos.map((r, i) => ({ ...r, i })).sort((a, b) => b.prioridade - a.prioridade || a.i - b.i);
|
|
394
|
+
const ficam = [], somem = new Set();
|
|
395
|
+
for (const r of ordem) {
|
|
396
|
+
if (obstaculos.some(o => encostam(r.caixa, o)) || ficam.some(f => encostam(r.caixa, f)))
|
|
397
|
+
somem.add(r.id);
|
|
398
|
+
else
|
|
399
|
+
ficam.push(r.caixa);
|
|
400
|
+
}
|
|
401
|
+
return somem;
|
|
402
|
+
}
|
|
403
|
+
// ── A NUVEM (MNT-16.5) ────────────────────────────────────────────────────────────────────────
|
|
404
|
+
// Internet, nuvem pública, site remoto: o contorno de nuvem, como no mercado. É um desenho de FUNDO
|
|
405
|
+
// (SVG atrás do texto) e não um recorte da caixa: o `clip-path` cortaria também o anel de foco.
|
|
406
|
+
// Os gomos são arcos entre pontos ESPAÇADOS POR COMPRIMENTO numa elipse recuada — espaçados por
|
|
407
|
+
// ângulo, eles se amontoariam nas pontas de uma elipse larga. O recuo é o tamanho do gomo (~0,13 da
|
|
408
|
+
// altura): a nuvem enche a caixa e o gomo encosta na borda. Com 0,18, a nuvem ficava encolhida e o
|
|
409
|
+
// ícone do nó, na borda da caixa, saía para fora dela (o Victor viu na bancada, 10/10/2026).
|
|
410
|
+
export function contornoDeNuvem(larg, alt) {
|
|
411
|
+
const recuo = alt * 0.13, rx = larg / 2 - recuo, ry = alt / 2 - recuo, cx = larg / 2, cy = alt / 2;
|
|
412
|
+
if (rx <= 0 || ry <= 0)
|
|
413
|
+
return "";
|
|
414
|
+
const AMOSTRAS = 240, amostra = [], acumulado = [0];
|
|
415
|
+
for (let i = 0; i <= AMOSTRAS; i++) {
|
|
416
|
+
const t = (i / AMOSTRAS) * 2 * Math.PI;
|
|
417
|
+
amostra.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });
|
|
418
|
+
}
|
|
419
|
+
for (let i = 1; i < amostra.length; i++)
|
|
420
|
+
acumulado.push(acumulado[i - 1] + comprimento(amostra[i - 1], amostra[i]));
|
|
421
|
+
const total = acumulado[acumulado.length - 1];
|
|
422
|
+
const gomos = Math.max(8, Math.min(24, Math.round(total / (alt * 0.5))));
|
|
423
|
+
const passo = total / gomos, pontos = [];
|
|
424
|
+
for (let k = 0, j = 0; k < gomos; k++) {
|
|
425
|
+
const alvo = k * passo;
|
|
426
|
+
while (j < acumulado.length - 1 && acumulado[j + 1] < alvo)
|
|
427
|
+
j++;
|
|
428
|
+
pontos.push(amostra[j]);
|
|
429
|
+
}
|
|
430
|
+
const n = (v) => Math.round(v * 100) / 100;
|
|
431
|
+
let d = `M${n(pontos[0].x)},${n(pontos[0].y)}`;
|
|
432
|
+
for (let k = 0; k < gomos; k++) {
|
|
433
|
+
const a = pontos[k], b = pontos[(k + 1) % gomos], raio = comprimento(a, b) * 0.62;
|
|
434
|
+
d += ` A${n(raio)},${n(raio)} 0 0 1 ${n(b.x)},${n(b.y)}`;
|
|
435
|
+
}
|
|
436
|
+
return d + " Z";
|
|
437
|
+
}
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
2
|
import { type AureaIcon } from "./system.js";
|
|
3
|
+
import { type Borda } from "./graph-geometria.js";
|
|
3
4
|
export type GraphOrientation = "horizontal" | "vertical";
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* `simple` (a de sempre, sem dependência), `layered` (camadas, com contêineres por dentro), `tree`
|
|
7
|
+
* (árvore compacta) e `radial` (a raiz no centro, as linhas retas). As três últimas pedem o `elkjs`.
|
|
8
|
+
*/
|
|
9
|
+
export type GraphLayout = "simple" | "layered" | "tree" | "radial";
|
|
5
10
|
export type GraphEdgeShape = "curve" | "step";
|
|
6
11
|
export type GraphPortSide = "top" | "right" | "bottom" | "left";
|
|
7
12
|
/** Uma porta do nó: uma alça com nome e lado (MNT-14). A aresta liga nela por `fromPort`/`toPort`. */
|
|
@@ -31,6 +36,17 @@ export interface GraphNodeItem {
|
|
|
31
36
|
ports?: GraphPort[];
|
|
32
37
|
/** A camada sugerida pelo app, contada de 0 (só na arrumação `layered`). */
|
|
33
38
|
layer?: number;
|
|
39
|
+
/** O CONTÊINER deste nó (site, andar, rack): um só. O contêiner é um nó como os outros, e vira
|
|
40
|
+
* a caixa em volta dos filhos, com o botão de fechar (MNT-16.6). VLAN não é contêiner: um
|
|
41
|
+
* equipamento está em várias — ela é filtro e destaque (`hiddenIds`, `highlight`). */
|
|
42
|
+
parentId?: string;
|
|
43
|
+
/** Começa FECHADO: o contêiner esconde o que tem dentro, e o nó com `collapsible` esconde a
|
|
44
|
+
* subárvore. Fechado, ele mostra quantos guarda (MNT-16.4 e 16.7). */
|
|
45
|
+
collapsed?: boolean;
|
|
46
|
+
/** Fixo no lugar: não arrasta, e "Reorganizar" não o move (MNT-13.4). */
|
|
47
|
+
pinned?: boolean;
|
|
48
|
+
/** `cloud` desenha o nó como nuvem: Internet, nuvem pública, site remoto (MNT-16.5). */
|
|
49
|
+
shape?: "box" | "cloud";
|
|
34
50
|
}
|
|
35
51
|
export type GraphEdgePattern = "solid" | "dashed" | "dotted" | "double";
|
|
36
52
|
export type GraphEdgeWeight = "regular" | "thick" | "heavy";
|
|
@@ -55,18 +71,40 @@ export interface GraphEdgeItem {
|
|
|
55
71
|
count?: number;
|
|
56
72
|
/** O nome desta espécie de linha na legenda automática (`legend`). */
|
|
57
73
|
legend?: string;
|
|
74
|
+
/** Quando dois rótulos se encostam, fica o de prioridade MAIOR; no empate, o que vem antes na
|
|
75
|
+
* lista (MNT-12.5). O nome da porta na ponta conta com a mesma prioridade da linha. */
|
|
76
|
+
priority?: number;
|
|
58
77
|
}
|
|
78
|
+
/** A arrumação para o app guardar (MNT-13.6): onde cada nó está e quais estão fixos. */
|
|
79
|
+
export interface GraphLayoutChange {
|
|
80
|
+
positions: Record<string, {
|
|
81
|
+
x: number;
|
|
82
|
+
y: number;
|
|
83
|
+
}>;
|
|
84
|
+
pinned: string[];
|
|
85
|
+
}
|
|
86
|
+
export type GraphAlign = Borda;
|
|
59
87
|
/** O que o app pede ao mapa pelo `apiRef` (MNT-18 e MNT-19). */
|
|
60
88
|
export interface DependencyGraphApi {
|
|
61
89
|
fitView(): void;
|
|
62
90
|
zoomIn(): void;
|
|
63
91
|
zoomOut(): void;
|
|
64
92
|
focus(id: string): void;
|
|
65
|
-
/** O mapa como arquivo do draw.io (`.drawio`), com as posições de agora. */
|
|
93
|
+
/** O mapa como arquivo do draw.io (`.drawio`), com as posições e as dobras de agora. */
|
|
66
94
|
toDrawio(): string;
|
|
67
95
|
/** A foto do mapa inteiro, como `data:` URL. Pede o peer opcional `modern-screenshot`. */
|
|
68
96
|
toPng(): Promise<string>;
|
|
69
97
|
toSvg(): Promise<string>;
|
|
98
|
+
undo(): void;
|
|
99
|
+
redo(): void;
|
|
100
|
+
/** Alinha os nós escolhidos pela borda pedida. */
|
|
101
|
+
align(edge: GraphAlign): void;
|
|
102
|
+
/** Deixa os vãos iguais entre os escolhidos (três ou mais). */
|
|
103
|
+
distribute(axis: "horizontal" | "vertical"): void;
|
|
104
|
+
/** Volta à arrumação automática, menos os fixos. */
|
|
105
|
+
relayout(): void;
|
|
106
|
+
/** A arrumação de agora, no mesmo formato do `onLayoutChange`. */
|
|
107
|
+
getLayout(): GraphLayoutChange;
|
|
70
108
|
}
|
|
71
109
|
export interface DependencyGraphProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
72
110
|
nodes: GraphNodeItem[];
|
|
@@ -126,5 +164,25 @@ export interface DependencyGraphProps extends Omit<React.HTMLAttributes<HTMLDivE
|
|
|
126
164
|
highContrast?: boolean;
|
|
127
165
|
/** Os comandos do mapa para o app: caber, aproximar, ir até, e exportar (MNT-18 e MNT-19). */
|
|
128
166
|
apiRef?: React.Ref<DependencyGraphApi>;
|
|
167
|
+
/**
|
|
168
|
+
* O MODO DE ARRUMAR (MNT-13): arrastar na área vazia faz o laço; Shift soma à escolha; arrastar
|
|
169
|
+
* move todos os escolhidos; a barra alinha, distribui, fixa, reorganiza, desfaz e refaz; as setas
|
|
170
|
+
* movem os escolhidos (com Shift, quatro passos). Desligado, é o mapa de leitura.
|
|
171
|
+
*/
|
|
172
|
+
editable?: boolean;
|
|
173
|
+
/** A grade com encaixe do modo de arrumar. Ligada por padrão. */
|
|
174
|
+
grid?: boolean;
|
|
175
|
+
/** A arrumação mudou (arraste, alinhar, fixar, reorganizar, desfazer): guardar é do app (MNT-13.6). */
|
|
176
|
+
onLayoutChange?: (layout: GraphLayoutChange) => void;
|
|
177
|
+
/** Os nós escolhidos no modo de arrumar. Com UM escolhido, o `onSelect` também é chamado. */
|
|
178
|
+
onSelectionChange?: (ids: string[]) => void;
|
|
179
|
+
/** Os nós com subárvore ganham o botão de fechar e abrir (MNT-16.7). O contêiner sempre tem. */
|
|
180
|
+
collapsible?: boolean;
|
|
181
|
+
/** Um contêiner ou uma subárvore abriu ou fechou. */
|
|
182
|
+
onCollapseChange?: (id: string, collapsed: boolean) => void;
|
|
183
|
+
/** Desenha só o que está na tela (mapa muito grande, MNT-19.1). */
|
|
184
|
+
visibleOnly?: boolean;
|
|
185
|
+
/** O trabalhador que arruma FORA da tela principal (MNT-19.1). A receita de cada empacotador está no README. */
|
|
186
|
+
layoutWorker?: () => Worker;
|
|
129
187
|
}
|
|
130
|
-
export declare function DependencyGraph({ nodes, edges, label, selectedId, onSelect, connectable, onConnect, onNodeMove, height, className, orientation, layout, rootId, edgeShape, groupParallel, minimap, controls, legend, focusId, highlight, highlightNeighbors, hiddenIds, onNodeHover, onNodeContextMenu, onEdgeSelect, textSize, highContrast, apiRef, style, ...props }: DependencyGraphProps): React.JSX.Element;
|
|
188
|
+
export declare function DependencyGraph({ nodes, edges, label, selectedId, onSelect, connectable, onConnect, onNodeMove, height, className, orientation, layout, rootId, edgeShape, groupParallel, minimap, controls, legend, focusId, highlight, highlightNeighbors, hiddenIds, onNodeHover, onNodeContextMenu, onEdgeSelect, textSize, highContrast, apiRef, editable, grid, onLayoutChange, onSelectionChange, collapsible, onCollapseChange, visibleOnly, layoutWorker, style, ...props }: DependencyGraphProps): React.JSX.Element;
|