import { Class_NodeElement } from '../Elements/Node'; import { Class_LinkElement } from '../Elements/Link'; import { Class_LevelTag } from '../types/Tag'; import { Class_DrawingArea } from '../types/DrawingArea'; import * as Geometry from './NodePositioningGeometry'; import { NodePositioningCyclesCore } from './NodePositioningCyclesCore'; import { NodePositioningReference } from './NodePositioningReference'; import { NodePositioningProportional } from './NodePositioningProportional'; /** * Class responsible for node and link positioning logic * Handles auto-sankey computation, parametrization, and trade arrangements */ export declare class NodePositioning { readonly drawingArea: Class_DrawingArea; captureScaleReference(): void; applyAdaptedScale(): boolean; clearScaleAdaptation(): void; deriveScaleAdaptedCornersFromCenter(): void; resolveScaleAdaptedOverlaps(): void; get scaleAdaptedReference(): { scale: number; magnitude: number; } | undefined; restoreScaleReference(scale: number, magnitude: number): void; diagramMagnitude(): number; tallestNodeMagnitude(): number; forgetScaleAdaptedCapture(): void; get scaleAdaptedWarning(): string | undefined; get suppressProportionalCompression(): boolean; set suppressProportionalCompression(v: boolean); readonly cycles: NodePositioningCyclesCore; readonly reference: NodePositioningReference; private _scale; private _auto; readonly proportional: NodePositioningProportional; private _parametric; private _column_top; /** * #366 — Oublie les hauts de colonne mémorisés : la disposition courante refera référence au * prochain empilement. À appeler quand l'utilisateur repose lui-même les positions (fin de * drag), sans quoi une tête de colonne déplacée serait rappelée à son ancien haut. */ clearColumnTops(): void; /** #366 — Hauts de colonne mémorisés (lecture, pour les tests et le diagnostic). */ get columnTops(): ReadonlyMap; constructor(drawingArea: Class_DrawingArea); detectAllCyclesAndOptimize(nodes_to_process: Class_NodeElement[]): { recycling_links: string[]; horizontal_indexes: { [node_id: string]: number; }; }; computeHorizontalIndex(start_node: Class_NodeElement, nodes_to_process: Class_NodeElement[], starting_index: number, _visited_nodes_ids: string[], recycling_links_ids: string[], horizontal_indexes_per_nodes_ids: { [node_id: string]: number; }): void; computeRecyclingHorizontalIndex(nodes_to_process: Class_NodeElement[], link: Class_LinkElement, recycling_links_ids: string[], horizontal_indexes_per_nodes_ids: { [node_id: string]: number; }): void; private repositionNodesWithoutInputs; computeAutoSankey(launched_from_process: boolean, optimize_crossing: boolean, h_spacing?: number, v_spacing?: number, sources_mode?: 'before_neighbor' | 'left_extremity', sinks_mode?: 'after_neighbor' | 'right_extremity', skip_horizontal?: boolean, skip_vertical?: boolean, apply_target_fonts?: boolean): void; computeAutoSankeyWithToast(launched_from_process: boolean, optimize_crossing: boolean, h_spacing?: number, v_spacing?: number, sources_mode?: 'before_neighbor' | 'left_extremity', sinks_mode?: 'after_neighbor' | 'right_extremity', skip_horizontal?: boolean, skip_vertical?: boolean, apply_target_fonts?: boolean): void; computeScale(): void; /** * #1230 — Mode coordonnées absolues : garde le centre des nœuds fixe quand leur * taille de rendu change (échelle globale des flux, valeur, bascule de * vue/datatag). Pendant du `recomputeParametricLayout` pour le mode absolu, * appelé en tête de `drawElements` avant `_sankey.draw()` pour que le coin * recalculé soit utilisé dès cette frame. * * N'agit que sur les nœuds « libres » en absolu : exclut les nœuds `relative` * (collés à un voisin, position auto-calculée) et les cadres tied (taille pilotée * par l'enveloppe de leurs enfants — re-centrer se battrait avec * `expandToContainAttachedNodes`). */ anchorAbsoluteNodesByCenter(): void; /** * #1231 (1.1.5) — Force le retour des nœuds « libres » à leur vraie position absolue * (coin = centre stocké − taille/2), en ignorant l'heuristique « taille inchangée » de * `anchorByCenterIfResized` (qui recommiterait le coin d'affichage). Mêmes exclusions que * `anchorAbsoluteNodesByCenter`. Appelé en sortie de proportionnel / échelle. */ deriveAbsoluteNodesFromCenter(): void; /** * Mix de positionnement PAR NŒUD, indépendant du mode global. * * Les nœuds marqués `absolute` restent placés par le mode global (absolu garde le * centre fixe, proportionnel comprime, etc.) — on n'y touche pas. Les nœuds marqués * `parametric` (« Ecartement ») se recalent verticalement sous le nœud directement * AU-DESSUS d'eux dans leur colonne (`position_u`, ordre `position_v`), à l'écart * constant `shape_position_dy`. Le nœud du dessus peut être un nœud absolu (servant * d'ancre) ou un parametric déjà calé → une pile de parametrics pend sous l'ancre * absolue. * * Le premier nœud d'une colonne, s'il est `parametric`, se cale sur le HAUT DE COLONNE, * mémorisé au premier passage et gardé fixe ensuite (cf. `_column_top`) : sans quoi il garde * son propre CENTRE, et deux colonnes posées à la même hauteur se désalignent dès que leurs * nœuds de tête changent de taille. C'est aussi ce qui fait remonter la colonne quand c'est * sa TÊTE qui disparaît — le suivant prend le haut. * * À appeler en fin de placement global, AVANT `_sankey.draw()`. À NE PAS appeler en * mode global `parametric` (recomputeParametricLayout empile déjà la colonne entière). * * Exclus : nœuds invisibles, échange, `relative` (collés à un voisin), enfants de * cadre englobant (positionnés par leur container). */ anchorParametricNodesToAbsolute(): void; /** * #372 — Empile une chaîne de colonne : chaque nœud « Écartement » se pose à son écart sous le * bas de celui qui le précède ; les nœuds absolus servent d'ancres et gardent leur position. * * `keeps` retient les nœuds dont la position vient d'être POSÉE à la souris : ils gardent leur * place et servent d'ancre au reste de la pile. C'est ce qui permet au settle de mesurer leur * écart sur la position DÉFINITIVE de leur prédécesseur — laquelle peut bouger si le * déplacement a changé l'ordre de la colonne. */ private stackParametricChain; /** * #372 — Chaînes d'empilement du mix « Écartement » : les membres retenus, groupés par colonne * (`position_u`) et déjà ordonnés comme la pile les parcourt. * * SOURCE UNIQUE des deux sens de lecture : `anchorParametricNodesToAbsolute` (écarts → * positions) et `settleParametricStacksFromY` (positions → écarts) doivent voir exactement la * même chaîne — mêmes membres, même ordre, même prédécesseur. Sans quoi le settle écrit des * écarts que la pile ne relit pas, ce qui était précisément le piège de * `backCalculateShapePositionDyFromY` (autre ensemble, autre ordre). * * Exclus : nœuds invisibles, échange, `relative` (collés à un voisin), enfants de cadre * englobant (positionnés par leur cadre). Map VIDE si aucune colonne ne porte de nœud * « Écartement » : il n'y a alors ni empilement à faire, ni écart à régler. */ parametricColumnChains(): Map; /** * #372 — SETTLE : relit les positions DÉPOSÉES à la souris et en redéduit l'ordre et les écarts * des empilements, pour que le dessin suivant les reproduise. Sans lui, la position d'un nœud * en « Écartement » est DÉRIVÉE de son écart au nœud du dessus — écart que le déplacement ne * touchait pas : le nœud revenait à sa place au dessin suivant. * * C'est le pendant PAR NŒUD de ce que `backCalculateShapePositionDyFromY` fait pour le mode * global « écart » ; les deux ne sont pas interchangeables (cf. `parametricColumnChains`). * * Ordre des passes, imposé par le recouvrement des deux empilements : les membres d'un cadre * englobant sont replacés depuis le HAUT du cadre, et la HAUTEUR du cadre est l'enveloppe de * ses membres. On règle donc les cadres d'abord, on les ré-empile (leur hauteur redevient * exacte), et seulement ensuite on règle les colonnes, qui lisent cette hauteur. * * `moved_ids` = les nœuds que le déplacement a réellement bougés. Eux seuls portent une * position DÉPOSÉE, qui fait autorité ; celle des autres sera recalculée au prochain dessin et * ne doit donc rien figer (cf. `settleStackOrderFromY` / `settleStackGapsFromY`). * * Renvoie true si un nœud déplacé appartenait bel et bien à un empilement — le seul cas où le * dessin doit être relancé. Un déplacement qui ne touche aucune pile (diagramme en coordonnées * absolues sans cadre) ne coûte donc pas de redessin complet. */ settleParametricStacksFromY(moved_ids: ReadonlySet): boolean; /** * #1231 — Étendue géométrique verticale de chaque colonne (`position_u`) : haut = bord * supérieur du nœud le plus haut, bas = bord inférieur du nœud le plus bas, centre = * milieu géométrique de la pile. C'est la **définition unique de la « médiane » d'une * colonne**, partagée par le mode paramétrique (ancre = centre géométrique gardé fixe) * et le mode proportionnel (centre de gravité = moyenne des centres géométriques). */ /** * #1231 — (Re)capture la médiane (centre géométrique) de CHAQUE colonne sur l'état * courant, pour le mode paramétrique. À appeler à l'entrée du mode et en fin de drag * (état cohérent positions↔hauteurs). La médiane est ensuite gardée FIXE par * recomputeParametricLayout au changement de datatag/dimension. Même définition que * le proportionnel (columnGeometricExtents). Reconstruit la map (vide les u périmés). */ static columnGeometricExtents: typeof Geometry.columnGeometricExtents; layoutChildrenInParentSlot(children: Class_NodeElement[], parent_top: number, parent_h: number): void; get proportionalReferenceLink(): Class_LinkElement | undefined; get proportionalReferenceNode(): Class_NodeElement | undefined; get proportionalReferenceDatatagIds(): string[] | undefined; set proportionalReferenceDatatagIds(ids: string[] | undefined); setProportionalReferenceLink(link: Class_LinkElement | undefined): void; setProportionalReferenceNode(node: Class_NodeElement | undefined): void; attachReferenceLinkFromAttributes(): void; resetProportionalState(): void; captureProportionalReference(): void; anchorProportionalNodes(): void; /** * NOUVELLE ÉTAPE : Optimisation des croisements de flux * À appeler APRÈS le positionnement initial des nœuds * * @param {boolean} apply_optimization - Active/désactive l'optimisation */ optimizeCrossingsPositioning(apply_optimization?: boolean, h_spacing?: number, v_spacing?: number): void; /** * Initially there is only one node per type of exchanges. * it must be split to have one import and one export per product * International will be split to give InternationalProduct1Importation InternationalProduct1Exportation */ splitTrade(): void; arrangeTrade(compute_xy: boolean): void; /** * Empile verticalement une liste de nœuds en partant d'une ancre (top du premier * nœud). Invariant canonique du mode paramétrique : * * n_0.y = anchor_y * n_{i+1}.y = n_i.y + n_i.height + n_{i+1}.shape_position_dy * * `shape_position_dy` est lu sur chaque nœud (cascade de style respectée) et est * la **seule** source de vérité pour l'espacement. Le dy du premier nœud est * ignoré (il n'a pas de prédécesseur). `applyPosition()` est appelé sur chaque * nœud après la mise à jour. * * L'ordre des nœuds est celui de la liste passée — à trier par le caller selon * son propre critère (position_v, position_y, etc.). */ static stackNodesVertically: typeof Geometry.stackNodesVertically; static totalStackHeight: typeof Geometry.totalStackHeight; static containerChildGap: typeof Geometry.containerChildGap; static stackContainerChildren: typeof Geometry.stackContainerChildren; static totalContainerStackHeight: typeof Geometry.totalContainerStackHeight; recomputeParametricLayout(scope: { type: 'all'; } | { type: 'column'; u: number; } | { type: 'subtree'; node: Class_NodeElement; }): void; restackContainerChildren(keeps?: (leaf: Class_NodeElement) => boolean): void; containerChildGroups(): Class_NodeElement[][]; /** * Redresse immédiatement un flux marqué « à garder droit » (clic droit → « Rendre * droit ») — issue su-model/opensankey#665, refonte #1231. * * Le marquage (`shape_must_stay_straight`) est posé par l'appelant ; ici on relance * simplement un `drawElements`, dont le post-process `enforceStraightLinks` applique * ET maintient la droiture à chaque dessin (dans les 3 modes). Plus de back-calc * d'écarts : la droiture n'est plus figée dans la métadonnée paramétrique, elle est * re-calculée à chaque frame. * * @returns toujours `true` (le redraw a été déclenché). */ straightenLink(link: Class_LinkElement): boolean; /** * #665 (refonte #1231) — Post-processing « flux droit » appliqué APRÈS placement, * dans les **trois modes** (paramétrique, absolu, proportionnel). Modèle simple * **par flux** : pour chaque flux marqué `shape_must_stay_straight`, on déplace le * **nœud cible** verticalement pour que son accroche coïncide avec celle de la source * (source = référence). Pas de groupes rigides, pas de back-calc d'écarts : la * droiture est re-appliquée à chaque dessin (ce post-process tourne après * `_sankey.draw()` à chaque `drawElements`), donc rien à « figer ». * * Les flux sont traités triés par `position_u` de la source (amont → aval) pour que * les chaînes A→B→C se propagent correctement (B déplacé avant de traiter B→C). Sur * un nœud cible de deux flux marqués incompatibles, le dernier traité gagne. * * Option par flux `shape_straight_include_children` : redresse aussi les flux * « enfant-enfant » (source et cible descendantes des nœuds du flux marqué dans la * hiérarchie de dimensions) → la droiture survit à la désagrégation. * * À appeler après un draw (les accroches `getOutputLinkStartingPoint`/ * `getInputLinkEndingPoint` reflètent les épaisseurs courantes ; l'offset relatif est * invariant par translation). Géométrie pure ; le caller redessine si `true`. * * @returns `true` si au moins un nœud cible a bougé (le caller redessine). */ enforceStraightLinks(): boolean; /** * #1231 — Ensemble { nœud + tous ses descendants } via la hiérarchie de dimensions * (`dimensions_as_parent.children`). Utilisé pour propager la droiture aux flux * désagrégés. */ static collectNodeDescendants: typeof Geometry.collectNodeDescendants; backCalculateShapePositionDyFromY(): number; inferPositionUFromX(): void; computeColumnsFromX(): { [node_id: string]: number; }; updateRecyclingFromPositions(only_touching_nodes?: Set): { [link_id: string]: boolean; }; lockRecyclingStatusDivergences(): string[]; computeParametrization(use_horizontal_index: boolean): void; applyVForLevelTag(columns: { [_: number]: Class_NodeElement[]; }, tag: Class_LevelTag): void; computeParametricV(tag: Class_LevelTag | undefined): void; computeParametricVForTagg(tag: Class_LevelTag): void; applyVAgregate(node: Class_NodeElement): void; applyVDesagregate(node: Class_NodeElement, current_v: number, tag: Class_LevelTag): number; protected _arrangeNodesToGrid(): void; /** * Align node pos with grid lines & save it's undo * */ arrangeNodesToGrid: () => void; }