import * as d3 from '../d3Modules'; import { Class_NodeBase } from './NodeBase'; import { Class_LinkElement } from './Link'; import { Type_JSON } from '../types/Utils'; import type { Type_Origin } from '../types/Origin'; import { Class_DataTag, Class_Tag } from '../types/Tag'; import { Class_DrawingArea } from '../types/DrawingArea'; import { Class_NodeDimension, NodeDimensionsManager } from './NodeDimension'; import { Class_LevelTagGroup, Class_TagGroup, Class_ViewTagGroup } from '../types/TagGroup'; import { NodeTagsManager } from './NodeTagsManager'; import { NodeDrawValueLabel } from './DrawLabel'; import { Class_StockValue, Class_ElementValueTree } from './LinkValues'; import { Class_StockShape } from './StockShape'; import { Type_Side } from './ElementsAttributesConfig'; /** * Class that define a node element and how to interact with it * * @class Class_NodeElement * @extends {ClassAbstract_NodeElement} */ export declare class Class_NodeElement extends Class_NodeBase { _nodeDimensionsManager: NodeDimensionsManager; protected _dimensions_as_parent: { [id: string]: Class_NodeDimension; }; protected _dimensions_as_child: { [id: string]: Class_NodeDimension; }; protected _leveltaggs_as_antitagged: Class_LevelTagGroup[]; protected _view_taggs_as_excluded: Class_ViewTagGroup[]; protected _nodeDrawValueLabel: NodeDrawValueLabel; protected d3_selection_g_value_label: d3.Selection | null; protected _sibling_node: Class_NodeElement | undefined; _nodeTagsManager: NodeTagsManager; protected _tags: Class_Tag[]; protected _taggs_dict: { [x: string]: Class_Tag[]; }; protected _are_related_node_tags_selected: boolean | undefined; protected _node_tags_fingerprint: string; protected _are_related_dimensions_selected: boolean | undefined; protected _tooltip_text: string; has_stock: boolean; stock_shape_is_visible: boolean; stock_si_caption: string; stock_delta_caption: string; use_stock_for_height: boolean; stock_height_scale_factor: number; has_material_balance: boolean; balance_marker_mode: 'inherit' | 'on' | 'off'; _stock_values: Class_StockValue | Class_ElementValueTree; unitary_ref_link_id: string | null; _stock_shape: Class_StockShape | null; /** * Get current stock value based on selected data tags. * Returns the leaf Class_StockValue matching current datatag selection. */ get stock_value(): Class_StockValue | null; /** * Creates an instance of Class_NodeBase. */ constructor(id: string, name: string, drawing_area: Class_DrawingArea); createValue(parent: Class_ElementValueTree | Class_NodeElement): Class_StockValue; protected _links_visibilities_fingerprint: string; protected _are_links_visibilities_ok: boolean | undefined; private _input_links; private _output_links; private _links_order; private _input_links_ending_point; private _output_links_starting_point; private _input_links_handle; private _output_links_handle; private _link_dragged; resetLinkVisibilitiesMemorization(): void; protected _orderD3Elements(): void; protected openValueLabelEditor(): void; drawSelectedLabelHandles(prefix: 'name_label' | 'value_label' | 'icon' | null): void; copyTagsReferencingFrom(node_to_copy: Class_NodeElement, matching_tagg: { [_: string]: string; }, matching_tags: { [_: string]: { [_: string]: string; }; }): void; /** * Couleur propre du nœud : celle du thème si une palette s'applique, sinon celle * que rend la cascade habituelle (storage de l'élément, styles, défaut usine). * * `themeNodeColor` rend `undefined` dès que le nœud porte une couleur explicite, * ce qui garantit qu'un choix de l'utilisateur bat toujours la palette. Voir * NOTE-THEMES.md : le test porte sur le storage de l'ÉLÉMENT, pas sur la chaîne * de styles — celle-ci définit toujours `shape_color` (pré-remplissage du style * 'default'), donc la consulter rendrait la règle inatteignable. */ private _ownShapeColor; /** * Select the right color to use for this node (attribute / style / tags / theme / ...) */ getShapeColorToUse(): string; protected cleanForDeletion(): void; protected _copyFrom(_: Class_NodeElement): void; copyAttrFrom(_: Class_NodeElement): void; copyDimensionsFrom(node_to_copy: Class_NodeElement): void; keepLinkOrderingFrom(node_to_copy: Class_NodeElement, matching_link_id: { [_: string]: string; }): void; dimensionsFromJSON(json_node_object: Type_JSON, create_tag: boolean, matching_nodes_id?: { [_: string]: string; }, matching_taggs_id?: { [_: string]: string; }, matching_tags_id?: { [_: string]: { [_: string]: string; }; }): void; get sibling(): Class_NodeElement | undefined; set sibling(_: Class_NodeElement | undefined); /** * Ce nœud d'échange est-il DÉJÀ le produit d'un éclatement import/export ? * * `SplitIOrE` fabrique un nœud par flux, d'identifiant * `-`, relié à cette * seule extrémité. Cette forme se relit donc sur le graphe — ce que `sibling` * ne permet pas : le lien de fratrie n'est PAS persisté (la sauvegarde * réécrit un nœud éclaté en son agrégat, cf. `SankeyPersistence.toJSON`), il * est donc `undefined` sur TOUT fichier fraîchement chargé. * * L'enjeu est un fichier ancien qui stocke les nœuds déjà éclatés au lieu de * leur agrégat (Bois Savoie : 316 de ses 506 nœuds). Les éclater une seconde * fois fabrique une génération dont `setTradeDimensions` ne sait plus * retrouver la racine — il la lit en `id.split('-')[1]`, qui y désigne le * produit et non l'échange. Ces nœuds perdent alors toute hiérarchie de * niveaux, et un nœud sans dimension gouvernante est visible à TOUS les * niveaux (`checkIfRelatedDimensionsAreSelected`) : le diagramme agrégé * affichait 99 flux d'échange au lieu d'une poignée. * * Le test porte sur UN seul flux, parce que c'est ce que produit un * éclatement — un agrégat mono-flux (mfa_problem#222), lui, ne porte pas * l'identifiant de son extrémité en préfixe et reste donc éclatable. */ get is_split_trade_node(): boolean; /** * Issue #1225 — remonte la chaîne dim_as_child via les dims désagrégées * (force_show_children) jusqu'à trouver une dim is_expanded. Renvoie le * parent expansé et le côté de l'expansion, ou null si ce nœud n'est pas * (transitivement) enfant d'une expansion. * * Utilisé en plusieurs endroits (Link.is_allowed_by_container_modes, * Link._computeExpansionValue, NodeActions._restackEnglobingDim) pour * traiter de la même façon les enfants directs et les petits-enfants * d'une expansion en cascade. */ findExpandedAncestor(): { ancestor: Class_NodeElement; side: 'left' | 'right'; } | null; /** * Draw given node on drawing area */ protected _draw(): void; /** * Manage the lifecycle of the stock visual sub-element (SA#1229): lazily * create it when the node carries a stock, draw it, or remove it otherwise. */ private _drawStockShape; drawNameLabel(): void; /** * Draw stock indicator box near the node shape. * Position controlled by stock_horiz, stock_vert, stock_inside. * Box width = stock_box_width * nodeWidth (ratio). */ drawStockBox(): void; drawValueLabel(): void; /** * Recursive function to return list of descendant nodes */ getListDescendantOfNode(): Class_NodeElement[]; getListAncestorOfNode(): Class_NodeElement[]; protected resolveTagLabel(): string; protected resolveAncestorLabel(): string; protected resolveTemplateToken(token: string): string | null; /** * Symbole d'unité du label de valeur du nœud — même résolution que * Class_LinkElement.unit_name (unité saisie, registre d'unités OS#1286, ou * tag d'unité sélectionné). */ get value_unit_name(): string; hasGivenTag(tag: Class_Tag): boolean; tagsUpdated(): void; addTag(tag: Class_Tag): void; removeTag(tag: Class_Tag): void; /** * sa#283 lot 6 — état de tags SÉRIALISÉ de ce nœud (forme de `toJSON`). * Cf. `NodeTagsManager.tagsStateToJSON`. */ tagsStateToJSON(group_ids?: string[]): Type_JSON; /** * sa#283 lot 6 — remplace l'appartenance de ce nœud aux groupes cités, caches de * visibilité invalidés. Cf. `NodeTagsManager.applyTagsState`. */ applyTagsState(state: Type_JSON): void; get grouped_taggs_dict(): { [x: string]: Class_Tag[]; }; get tags_list(): Class_Tag[]; get taggs_dict(): { [_: string]: Class_TagGroup; }; get tags_dict(): { [_: string]: Class_Tag; }; get taggs_list(): Class_TagGroup[]; dimensionsUpdated(): void; addNewDimensionAsParent(_: Class_NodeDimension): void; addNewDimensionAsChild(_: Class_NodeDimension): void; addAsAntiTagged(_: Class_LevelTagGroup): void; removeDimensionAsParent(_: Class_NodeDimension): void; removeDimensionAsChild(_: Class_NodeDimension): void; removeAsAntiTagged(_: Class_LevelTagGroup): void; /** * sa#283 lot 5 — état de dimension SÉRIALISÉ de ce nœud (forme de `toJSON`). * Cf. `NodeDimensionsManager.dimensionsStateToJSON`. */ dimensionsStateToJSON(dimension_ids?: string[]): Type_JSON; /** * sa#283 lot 5 — remplace l'état de dimension de ce nœud pour les dimensions citées, * cache de visibilité invalidé. Cf. `NodeDimensionsManager.applyDimensionsState`. */ applyDimensionsState(state: Type_JSON): void; nodeDimensionAsParent(child: Class_NodeElement): Class_NodeDimension | null; nodeDimensionAsChild(parent: Class_NodeElement): Class_NodeDimension | null; unDraw(): void; drawAsSelected(): void; drawLinks(): void; drawLinksArrow(): void; drawLinksSourceNotch(): void; /** * Launch animation from this node */ launchAnimation(): void; getShapeWidthToUse(): number; /** * Current stock initial value used for stock-driven node sizing. Result in * reconciled/calculated mode, raw data otherwise (mirrors drawStockBox). */ currentStockInitialForHeight(): number | null; /** * #1231b — Stock initial pour un jeu de datatags ARBITRAIRE (miroir de * currentStockInitialForHeight mais à des tags donnés). Sert d'élément de référence du * mode proportionnel / échelle adaptée (datatag de référence). null si pas de stock. */ stockInitialForDataTags(tags: Class_DataTag[]): number | null; /** * #1231b — Valeur-équivalente de la hauteur RÉELLEMENT rendue par ce nœud quand * il est en mode stock-pour-hauteur : `max(hauteur-stock, bande de flux)` reconverti * en unités de valeur (mêmes unités que la valeur de stock), via l'inverse de * scaleValueToPx. Sert d'ancrage à l'échelle adaptée (élément de référence) : la * taille pixel tenue constante devient la hauteur réelle du nœud, pas la seule valeur * de stock. Quand la bande de flux ne dépasse pas, le résultat == |stock_value| * (comportement inchangé). `stock_value` est la valeur de stock au datatag voulu. */ stockValueAugmentedByFluxBand(stock_value: number): number; getShapeHeightToUse(): number; getNaturalShapeHeight(): number; private _getNaturalShapeHeight; hasInputLinks(): boolean; hasOutputLinks(): boolean; hasVisibleInputLinks(): boolean; hasVisibleOutputLinks(): boolean; addInputLink(link: Class_LinkElement): void; addOutputLink(link: Class_LinkElement): void; deleteInputLink(link: Class_LinkElement): void; deleteOutputLink(link: Class_LinkElement): void; deleteRecyclingLinkOnSameNode(link: Class_LinkElement): void; removeInputLink(link: Class_LinkElement): void; removeOutputLink(link: Class_LinkElement): void; swapInputLink(link: Class_LinkElement, node: Class_NodeElement): void; swapOutputLink(link: Class_LinkElement, node: Class_NodeElement): void; getFirstInputLink(): Class_LinkElement | undefined; getFirstOutputLink(): Class_LinkElement | undefined; getInputLinksForGivenSide(_: Type_Side): Class_LinkElement[]; getOutputLinksForGivenSide(_: Type_Side): Class_LinkElement[]; getLinksOrdered(_: Type_Side): Class_LinkElement[]; getInputLinkEndingPoint(link: Class_LinkElement): { x: number; y: number; } | undefined; getOutputLinkStartingPoint(link: Class_LinkElement): { x: number; y: number; } | undefined; /** * Re-derive the I/O links order from the relative node positions. * * @param release_locks When true (default), the anchor locks ("cadenas" of the * "Ordre des flux E/S" menu) set manually on this node are released first and * every link is re-sorted — this is the explicit "recalcul automatique" the lock * tooltip refers to (Réorganiser button, computeAutoSankey, expand/contract…). * When false, locked anchors are PRESERVED : they keep their frozen side and * their slot in the order, and only the unlocked links are re-sorted around * them. A manual node drag passes false so it no longer undoes a user-locked * arrangement (the lock promises "déplacer le noeud opposé ne la repositionnera plus"). */ reorganizeIOLinks(release_locks?: boolean): void; /** * Build a link → display-rank map for the geometry-aware I/O order (modes 'simple' and * 'advanced', cf. ioOrderGeometry.ts). Turning links ('vh'/'hv') get the direction-split * + height fan so it does not cross ; straight links ('hh'/'vv') keep the plain * opposite-position order. All positions are taken at node CENTRES ; each link carries the * curvature on its node-side end (used as a height tie-break in ADVANCED only — `use_curve`). * A recycling link additionally carries `stack_ref`, the centre of its loop's belly, * because its opposite node — which sits backwards, beyond this node — says nothing about * where the loop actually passes. */ private _computeIOOrderIndex; reorganizeIOFromListIds(l: string[]): void; /** * sa#283 lot 6 — ORDRE DES FLUX autour de ce nœud, sur la forme EXACTE du JSON * (`links_order` : la liste des ids de flux, cf. `SankeyPersistence.toJSON`). * * C'est la QUATRIÈME catégorie de données qui peut différer d'une tranche à l'autre, à * côté des attributs du sac `_storage`, de la présence de l'élément et de la hiérarchie * de dimension : l'empilement des bandes autour du nœud, qui gouverne le rendu et ne vit * dans aucun sac d'attributs. */ linksOrderToJSON(): string[]; /** * sa#283 lot 6 — REPOSE l'ordre des flux de ce nœud. Exactement réversible : * `applyLinksOrder(linksOrderToJSON())` est un no-op, et rejouer l'ordre COMPLET mémorisé * avant l'écriture rend l'ordre d'origine (cf. `reorderLinksByIds`). * * Liste éventuellement PARTIELLE : un id inconnu de ce nœud est ignoré silencieusement * (même règle que partout dans les contextes — un fichier d'époque ne connaît pas tous les * flux du réseau réconcilié), et un flux du nœud absent de la liste passe À LA SUITE, dans * son ordre courant. Jamais de flux inconnu intercalé entre les bandes ordonnées. * * Aucun redessin ici : l'appelant en déclenche un (le runtime des contextes redessine * après l'overlay), comme pour `applyDimensionsState`. */ applyLinksOrder(ordered_ids: string[]): void; moveLinkToPositionInOrderBefore(link_to_move: Class_LinkElement, link_target_pos: Class_LinkElement): void; moveLinkToPositionInOrderAfter(link_to_move: Class_LinkElement, link_target_pos: Class_LinkElement): void; protected drawElements(): void; /** * Apply node position to it shape in d3 */ applyPosition(): void; /** * Call what is necessary each time a link is modified */ private _drawLinks; /** * Function that draw all the arrow of link visible linked to this node */ private _drawLinksArrow; /** * Compute the "source notch" (negative arrow) chevrons for this node's * outgoing links. All links leaving the same node side share a SINGLE notch: * one chevron whose base spans every link's attach band on that side and whose * apex is pushed into the ribbon (toward the targets) by the deepest requested * notch size. The resulting path is pushed onto every participating link, which * carves it out of its own paint via an SVG mask — the notch is a transparent * cutout (what lies behind stays visible), consistent whatever the z-order. */ private _drawLinksSourceNotch; /** * Redraw links to recolor them */ protected updateLinksColor(): void; private getSumOfLinksThickness; /** * Visible band extent of ONE side for the node shape (#201) — height for * left/right sides, width for top/bottom sides. * Σ of the side's RAW link thicknesses (sub-pixel/structural flows overlap, so * they contribute < minimum_flux / 0), then floored ONCE on the band — not the * sum of per-link 2px floors, which inflated the node to N × minimum_flux. * * The flux size limit (minimum_flux) applies to the NODE band as well as to the * links — "taille limite des nœuds et flux" (#201). It floors the band in EVERY * display mode so a node never renders thinner than its links: in structure * display thin flows overlap to ~minimum_flux; in value display a node whose raw * band is sub-pixel was thinner than its 2px-floored links (that was #200). */ private getSideBandExtent; private getLinksStartingPositionOffSet; /** Sum of the user-set anchor deltas of this node's visible links on `side`. */ private getSumOfAnchorDeltas; private removeLinkFromOrderingLinksList; private addMovingHandleForGivenLink; private updateLinksPositions; private dragHandlerMoveLink; private dragStartHandlerMoveLink; private dragEndHandlerMoveLink; /** * Filtre « mode vue » (cf. Class_ViewTagGroup.view_mode), version PLATE qui * court-circuite les level tags. Retourne : * - undefined : aucun groupe view tag en mode filtre, OU ce nœud ne porte aucun * de ces groupes → porte node-tag/dimension normale ; * - true : le nœud porte une étiquette SÉLECTIONNÉE d'un groupe en mode filtre ; * - false : le nœud porte une autre étiquette (non sélectionnée) d'un tel groupe. * (Visibilité seulement — pas de remontée vers les ancêtres ; cf. #173 pour ça.) */ viewTagVisibility(): boolean | undefined; get are_related_node_tags_selected(): boolean; get is_visible(): boolean; /** * os#671 / os#1347 — contrainte des smart guides pour un NŒUD DE DONNÉES : * un axe dont la position est re-dérivée par la mise en page au drop ne doit * pas snapper (l'alignement serait mensonger, défait au dessin suivant). * - Mise en page paramétrique globale : x est re-quantifié en colonnes * (inferPositionUFromX → computeParametrization) — x non snappable ; un * nœud u-verrouillé voit même son x déposé ignoré. Le y déposé, lui, est * reproduit (backCalculateShapePositionDyFromY) — y reste snappable. * - Nœud en position RELATIVE : son x est défini par rapport à sa source * (shape_position_dx re-calculé au drop) — x non snappable. */ getFreeDragSnapAxes(): { x: boolean; y: boolean; }; /** * #368 — Vrai si ce nœud est un CADRE ENGLOBANT ayant au moins un membre visible. * Un tel cadre doit être dessiné quels que soient SES PROPRES flux : il n'est pas * un nœud à part entière mais l'enveloppe de ses enfants (cf. #364, la décision * symétrique qui le masque quand il n'a plus rien à entourer). * * Sans cette porte, la visibilité du cadre dépendait de ses flux propres via * `are_links_visibilities_ok` : un cadre qui n'en porte AUCUN restait affiché * (règle orphelin), tandis qu'un cadre qui en porte, tous invisibles pour la * sélection courante, était masqué — deux résultats différents pour un même * rendu attendu, celui du second dépendant en outre de l'ordre de calcul des * valeurs (d'où l'asymétrie premier rendu / rendus suivants de #368). * * On reprend les portes de `is_visible_without_orphan` SAUF celle des flux — la * règle orphelin reste, elle, le levier de masquage manuel (cf. #364). On ne lit * pas `is_visible_without_orphan` : la récursion s'arrête sur la hiérarchie * parent → enfants, jamais sur la visibilité des flux. */ get is_visible_as_container_frame(): boolean; /** * Vrai si ce nœud n'est PAS visible dans la vue courante mais est révélé par le * mode « afficher aussi les flux porteurs de données » (extrémité d'un flux * portant une valeur collectée saisie). Sert à reconnecter ces nœuds au * diagramme via les flux structurels (cf. `Class_LinkElement.is_visible`). * Lit `is_visible_without_orphan` (pas `is_visible`) pour rester non récursif. */ get is_revealed_by_data(): boolean; /** * Vrai si ce nœud est l'extrémité d'au moins un flux (feuille, sans child_links) * porteur d'une valeur collectée saisie. Sert au mode « afficher aussi les flux * porteurs de données » (cf. `Class_ApplicationData.reveal_data_links`). */ get is_attached_to_collected_data_link(): boolean; get is_visible_without_orphan(): boolean; get links_order_visible(): Class_LinkElement[]; get links_order(): Class_LinkElement[]; get input_links_dict(): { [id: string]: Class_LinkElement; }; get input_links_list(): Class_LinkElement[]; get visible_input_links_list(): Class_LinkElement[]; get output_links_dict(): { [id: string]: Class_LinkElement; }; get output_links_list(): Class_LinkElement[]; get visible_output_links_list(): Class_LinkElement[]; get link_dragged(): Class_LinkElement | undefined; set link_dragged(value: Class_LinkElement | undefined); private get are_links_visibilities_ok(); private getLinksVisibilitiesFingerprint; private get orphan_visible(); private checkIfLinksVisibilitiesAreOK; SplitIOrE(importation: boolean): void; setTradeDimensions(importation: boolean): void; protected eventMaintainedClick(event: React.MouseEvent): void; get is_child(): boolean; get is_parent(): boolean; get is_multi_parent(): boolean; get dimensions_as_parent(): Class_NodeDimension[]; get is_multi_children(): boolean; get dimensions_as_child(): Class_NodeDimension[]; get are_related_dimensions_selected(): boolean; get internalTagsData(): { tags: Class_Tag[]; taggs_dict: { [x: string]: Class_Tag[]; }; leveltaggs_as_antitagged: Class_LevelTagGroup[]; view_taggs_as_excluded: Class_ViewTagGroup[]; }; get internalDimensionsData(): { dimensions_as_parent: { [id: string]: Class_NodeDimension; }; dimensions_as_child: { [id: string]: Class_NodeDimension; }; }; get tooltip_text(): string; set tooltip_text(_: string); private _origin; get origin(): Type_Origin | undefined; set origin(v: Type_Origin | undefined); get data_value(): number; /** * Valeur de stock initiale courante (signée) au datatag actif, ou null si le nœud * n'a pas de stock. Résultat en mode réconcilié/calculé, donnée brute en mode 'data' * (miroir de drawStockBox / Class_StockShape). Sert au seuil d'affichage « stock ». */ get stock_initial_value(): number | null; /** * Seuil d'affichage des labels de nœud (#seuil px) : compare data_value (mode * valeur) ou la hauteur de bande rendue (mode pixel) au seuil `filter_node[_px]`. */ get is_above_label_threshold(): boolean; get name_label_effective(): string; get data_label(): string; /** * Valeur du nœud rendue dans le slot du libellé de nom quand celui-ci est en * mode « value » (name_label_is_value). Même calcul que data_label, mais * formaté avec les attributs name_label_* (unité, décimales…). */ get name_value_label(): string; private _computeValueLabelText; /** * Somme des valeurs de flux VISIBLES entrant et sortant du n\u0153ud, au datatag/unit\u00e9 * courant. C\u00f4t\u00e9 entr\u00e9e : valeur arrivant \u00e0 la cible (valueCurrentTarget ?? valueCurrent). * C\u00f4t\u00e9 sortie : valeur quittant la source (valueCurrent). */ getFluxBalance(): { input: number; output: number; diff: number; has_in: boolean; has_out: boolean; }; /** Marqueur de bilan effectivement actif pour ce n\u0153ud (override par n\u0153ud sinon global). */ get balance_marker_effective_enabled(): boolean; /** * \u00c9tat du bilan du n\u0153ud si le marqueur est actif ET que le n\u0153ud porte des flux des * DEUX c\u00f4t\u00e9s (un pur puits/source n'a pas de bilan \u00e0 v\u00e9rifier). null sinon (rien \u00e0 * signaler). `violated` selon la strat\u00e9gie/tol\u00e9rance globale de la zone de dessin. */ get balance_status(): { input: number; output: number; diff: number; violated: boolean; } | null; /** * Dessine (ou retire) le marqueur d'avertissement \u00ab \u26a0 \u00bb dans le coin haut-droit * de la forme lorsque le bilan entr\u00e9e/sortie n'est pas respect\u00e9. Idempotent : * nettoie toujours le marqueur pr\u00e9c\u00e9dent ; ne dessine rien si le marqueur est * inactif, le n\u0153ud sans double flux, ou le bilan respect\u00e9. Un SVG donne * entr\u00e9e / sortie / diff\u00e9rence au survol direct de l'ic\u00f4ne. */ drawBalanceMarker(): void; get selected_elements_list(): Class_NodeElement[]; set_contextualized_element(element: Class_NodeBase): void; /** * Dessine le début des flux sur les ellipses pour un rendu plus fluide */ /** * Dessine le début des flux sur les ellipses pour un rendu plus fluide */ private _drawLinksStartCaps; /** * Dessine les caps pour tous les liens d'un côté donné */ private _drawCapsForSide; /** * Crée une partie découpée du cap d'ellipse (comme draw_arrow_part) */ private _createEllipseCapPart; }