import * as d3 from '../d3Modules'; import { NameLabelAttributeTypes, ShapePrefix, ValueLabelAttributeTypes } from './ElementsAttributesConfig'; import { Class_Handler } from './Handler'; import { Class_NodeBase } from './NodeBase'; import { Class_NodeElement } from './Node'; import { Class_LinkElement } from './Link'; import { LinkControlPoints } from './LinkControlPoints'; import { Class_BaseShape } from './Element'; type DisplayPrefix = 'name' | 'value'; type LabelPrefix = 'name_label' | 'value_label' | 'icon'; type d3_selection_type = d3.Selection; export declare function breakLongWords(text: string, maxWidth: number, font: string): string; /** * Classe de base abstraite pour tous les labels (nodes et links) */ export declare abstract class DrawLabelBase { protected _element: Class_BaseShape; protected readonly prefix: LabelPrefix; protected _label_values: NameLabelAttributeTypes | ValueLabelAttributeTypes; protected enableEditing: boolean; protected _force_editable_draw: boolean; private _edit_value_before; d3_selection: d3_selection_type | null; private _resize_handle_left; private _resize_handle_right; private _resize_drag_state; protected abstract createLabelGroup(): d3_selection_type | null; constructor(element: Class_BaseShape, prefix: LabelPrefix); protected abstract getLabelText(): string | number; protected abstract shouldDrawLabel(): boolean; protected abstract getIconSize(): [number, number]; protected abstract getLabelPos(): [number, number, string, string?]; protected abstract updateLabelPos(): void; protected abstract applyTextStyle(selection: d3.Selection | undefined): void; protected abstract getD3Selection(): d3.Selection | null | undefined; protected abstract getElementId(): string; protected onInputChange?(value: string): void; protected getTextSelector(): string; /** * Returns the label's font-size compensated for the drawing area's live zoom * (issue #165, mode « police verrouillée »). Labels live in the local * coordinate system that d3-zoom scales by k ; without compensation a 20px * font renders as 20*k screen px — invisible when k ≈ 1e-4 (large Sankey * scale) or varying with every wheel zoom. En mode verrouillé, on multiplie * par font_compensation (= 1/k live) pour annuler le scale SVG : la taille * écran reste égale à _label_values.font_size quel que soit le zoom. En mode * déverrouillé, font_compensation vaut 1 (police native qui scale avec le * zoom). Toute la logique de positionnement dérivée de font_size (line height, * offsets multi-ligne, boîte d'édition) doit passer par ce getter. */ protected getEffectiveFontSize(): number; /** * Issue #1232 — transform de placement d'un foreignObject rich text : * translate au coin haut-gauche local + scale(comp) pour annuler le zoom en * mode police verrouillée. comp === 1 (mode déverrouillé) → translate seul, * rendu strictement identique au comportement historique. Partagé entre * drawFO (pose initiale) et updateGenericPosition (drag) pour rester cohérent. */ protected foScaleTransform(x: number, y: number, comp: number): string; /** * Hauteur disponible pour les étiquettes de cet élément, en repère local. * null = élément hors périmètre de la règle (zone de texte, légende, forme de stock…), dont la * taille ne dit rien de la place réellement occupée par le texte. */ protected getFitAvailableHeight(): number | null; /** * Libellé (name_label) du MÊME élément, ou null. Sert au budget « libellé d'abord » : la valeur * ne s'affiche que sur la hauteur restante. Surchargé par les labels de valeur. */ protected getPeerNameLabel(): DrawLabelBase | null; /** * Chaîne `font` canvas construite depuis les attributs du label — contrairement à * getCanvasFontStringAtSize, n'exige aucun déjà présent dans le DOM. */ private getFitMeasureFont; /** * Hauteur (repère local) que ce label occuperait s'il était dessiné — 0 s'il n'y a rien à * dessiner (label masqué par l'utilisateur, texte vide, mode icône/image/texte riche, pour * lesquels la hauteur du texte n'a pas de sens). * * Publique : le label de valeur interroge celui du libellé pour connaître la place déjà prise. */ getRequiredLabelHeight(): number; /** Opt-in de CETTE étiquette à l'élagage (attribut `prune_if_unfitting`). */ get prunes_if_unfitting(): boolean; /** * Porte de visibilité #377 : ce label tient-il dans la hauteur de son élément ? * true dès que l'attribut est décoché ou que l'élément est hors périmètre — la règle ne * s'ajoute qu'aux portes existantes, elle n'en remplace aucune. * * Le label de VALEUR consulte le libellé du même élément même quand celui-ci n'élague pas : * un libellé dessiné occupe sa hauteur, donc il la retire du budget de la valeur. */ protected fitsInAvailableHeight(): boolean; protected getStickNameTextSelector(): string; protected getStickValueTextSelector(): string; /** * ✅ Dessine un background générique (texte, icône, FO) */ protected drawGenericBackground(parent: d3_selection_type, x: number, y: number, width: number, height: number, options?: { bgPrefix?: ShapePrefix; className?: string; anchor?: string; }): d3.Selection | null; /** * Poignées de redimensionnement de la "boîte" du label (label.box_width). * * Architecture (calquée sur les poignées de redimensionnement des nœuds * `_drag_handler.{left,right,top,bottom}`) : * - les poignées sont des `Class_Handler` instanciés dans le top-level * `g_handlers` du drawing area, repère absolu = repère `g_drawing` ; * - leur position est calculée en transformant le point d'ancrage du * label (label_pos_x en repère local du label) vers ce repère * absolu via la matrice CTM relative — fonctionne uniformément pour * nœuds (dont le a un translate) et liens (dont le n'en a pas) ; * - le drag utilise `event.dx` directement (déjà en repère `g_drawing`), * même unité que `box_width`. La poignée tirée suit toujours le * curseur 1:1, l'autre poignée bouge selon le facteur (2 pour ancre * 'middle' qui est symétrique, 1 pour 'start'/'end' où seul le bord * libre bouge — l'autre étant ancré, ne sera pas dessinée). * - taille du carré compensée par `1/zoomScale` (héritée de Class_Handler). * * Visibilité : tied à la sub-sélection du label (`selected_label_prefix`). * À chaque appel, refreshLabelResizeHandles re-positionne et re-dessine * (ou cache) les poignées en fonction de l'état courant. */ refreshLabelResizeHandles(): void; private _shouldShowResizeHandles; /** * Crée paresseusement les deux Class_Handler avec leurs callbacks de drag. * Les callbacks accèdent à `this.prefix`, `this._element`, etc. via closure. */ private _ensureResizeHandlesCreated; private _currentTextAnchor; /** * Calcule la position absolue (repère `g_handlers` = `g_drawing`) des * deux poignées à partir de la position d'ancrage du label en repère * local et de la box_width courante. Utilise la matrice CTM relative * `g_handlers` ← `label_g` pour traverser uniformément les transforms * intermédiaires (translate du nœud, identité pour les liens). */ private _computeResizeHandlesAbsolutePos; /** * ✅ Dessine le background du label */ protected drawBackground(group: d3_selection_type | undefined, tspanWidths: number[]): void; /** * BBox du du name_label — réf. pour positionner la valeur en mode stick. * Tient compte de la transform propre du (rotation si vertical_text), * sinon la valeur se collerait à la bbox horizontale et ne suivrait pas. */ protected getStickNameTextBBox(): { x: number; y: number; width: number; height: number; } | null; /** * Position d'un value_label collé (stick_to_label) : calculée par rapport à la * bbox du du name_label, selon horiz/vert/inside/shift du value_label. */ protected computeStickPos(nameBBox: { x: number; y: number; width: number; height: number; }): [number, number, string, string]; /** * En mode stick, le bloc combiné est-il recalé verticalement sur l'ancrage du * name_label ? Vrai pour les nœuds (name_label centré/ancré sur la forme). * Faux pour les liens : le name_label y est positionné par rapport au flux * (convention de baseline propre aux liens), on le laisse en place et la * valeur se contente d'y coller. */ protected stickRecentersVertically(): boolean; /** * Quand value_label_stick_to_label est on, à appeler sur le drawer du * name_label APRÈS que le value_label ait rendu son : * 1. recale le bloc combiné (nom + valeur) pour qu'il soit ancré sur la forme * comme le serait le name_label seul — les deux (nom + valeur) sont * décalés ensemble de (dx, dy) ; * 2. redessine le fond du name_label pour qu'il englobe nom + valeur (le fond * du value_label est skip côté drawBackground). */ refreshStickLayout(): void; /** * ✅ Dessine un ForeignObject */ drawFO(): void; protected abstract getIconPos(): [number, number]; drawImage(): void; protected getImageDimensions(icon_pos_x: number, icon_pos_y: number, icon_width: number, icon_height: number): [number, number, number, number]; protected setupImageDrag(): void; /** * ✅ Dessine une icône */ drawIcon(): void; /** * ✅ Drag générique unifié pour tous les types (text, FO, icon, image) */ protected dragGenericStart(_event: d3.D3DragEvent): void; protected dragGenericMove(event: d3.D3DragEvent): void; protected dragGenericEnd(_event: d3.D3DragEvent): void; /** * ✅ Mise à jour visuelle pendant le drag */ /** * ✅ Mise à jour visuelle pendant le drag */ protected updateGenericPosition(): void; /** * ✅ Input d'édition générique (contenteditable, multi-ligne, WYSIWYG) */ setInputLabelVisible(initialValue?: string): void; setInputLabelInvisible(): void; /** * Fin d'édition inline : enregistre la session de frappe comme UNE entrée * d'historique. On rejoue `onInputChange` — seul écrivain du modèle pendant * l'édition — avec l'ancienne puis la nouvelle valeur, ce qui évite de dupliquer * ici la logique propre à chaque label (renommage vs label custom vs valeur de flux). * Rien n'est enregistré si le texte n'a pas bougé : ouvrir l'éditeur puis en sortir * ne doit pas consommer un slot d'historique. */ protected commitEditToHistory(): void; /** * Ouvre l'éditeur inline de valeur même quand le flux n'a pas encore de valeur * (flux en pointillé) : dans ce cas aucun label n'est dessiné, donc aucun input * n'existe à révéler. On force alors le dessin de la structure éditable puis on * affiche l'input. Sert au double-clic sur le tracé du flux lui-même. */ openInlineEditor(): void; /** * ✅ Crée le foreignObject + input d'édition inline. * Reste masqué par défaut ; révélé par setInputLabelVisible. */ protected drawLabelInput(d3_selection: d3_selection_type | null, box_pos_x: number, box_pos_y: number, box_width: number, box_height?: number, force_nowrap?: boolean): void; protected getInputInitialValue(): string; /** * ✅ Attache un double-click sur le texte du label pour ouvrir l'input d'édition. */ protected attachDoubleClickEdit(textElement: d3.Selection): void; protected abstract getTextClasses(): string[]; protected abstract getTextElementId(): string; protected applySpecialTextContent(_textElement: d3.Selection, _labelText: string): boolean; protected applyTextDragHandlers(_textElement: d3.Selection): void; protected cleanupPreviousLabel(): void; drawGenericLabel(): void; protected verticalText(_tspanWidths: number[], _textElement: d3.Selection): number | undefined; /** * Angle effectif de rotation du texte, en degrés (−180..180). * `text_angle` (widget d'angle) prime ; à 0, on retombe sur la rétro-compat * `vertical_text` → −90° (les liens surchargent pour tenir compte de is_vertical). * −90° emprunte la géométrie de colonne historique (alignée au bord) ; tout * autre angle non nul est une rotation générique autour du point d'ancrage. */ protected getEffectiveTextAngle(): number; protected applyMultilineAlignment(textElement: d3.Selection, tspanWidths: number[]): void; protected finalizeLabelCreation(_textElement: d3.Selection): void; } /** * Classe de base pour les labels de nodes */ export declare abstract class NodeDrawLabelBase extends DrawLabelBase { protected get node(): Class_NodeBase; constructor(node: Class_NodeBase, prefix: LabelPrefix); protected verticalText(tspanWidths: number[], textElement: d3.Selection): number | undefined; protected getIconSize(): [number, number]; protected getIconPos(): [number, number]; protected getD3Selection(): d3.Selection | null; protected getElementId(): string; /** * #377 — hauteur rendue du nœud (cadre englobant inclus : c'est alors la hauteur de son * enveloppe, donc bien « la hauteur de l'élément » demandée, quelle que soit la position du * libellé — à gauche et à l'extérieur pour un cadre). * * Restreint aux nœuds du graphe (Class_NodeElement), dont la hauteur est pilotée par les * données. Les autres formes qui héritent de Class_NodeBase — zones de texte, blocs de * légende (Class_ContainerElement), formes de stock (Class_StockShape) — ont une taille * décorative ou déjà filtrée par son propre seuil : la règle ne s'y applique pas. */ protected getFitAvailableHeight(): number | null; protected getLabelPos(): [number, number, string, string]; protected updateLabelPos(): void; protected applyTextStyle(selection: d3.Selection | undefined): void; protected getImageDimensions(icon_pos_x: number, icon_pos_y: number, icon_width: number, icon_height: number): [number, number, number, number]; protected getTextClasses(): string[]; protected getTextElementId(): string; protected editingEnabled(): boolean; protected finalizeLabelCreation(textElement: d3.Selection): void; setInputLabelInvisible(): void; protected createLabelGroup(): d3_selection_type | null; } export declare class NodeDrawNameLabel extends NodeDrawLabelBase { constructor(node: Class_NodeBase, prefix: 'name_label' | 'icon'); protected getLabelText(): string; protected getInputInitialValue(): string; protected shouldDrawLabel(): boolean; protected editingEnabled(): boolean; protected onInputChange(value: string): void; } export declare class NodeDrawValueLabel extends NodeDrawLabelBase { constructor(node: Class_NodeElement); protected get _nodeElement(): Class_NodeElement; protected getLabelText(): string; protected shouldDrawLabel(): boolean; protected getPeerNameLabel(): DrawLabelBase | null; protected getLabelPos(): [number, number, string, string]; } export declare abstract class LinkDrawLabelBase extends DrawLabelBase { protected readonly displayPrefix: DisplayPrefix; protected _link_control_points: LinkControlPoints; protected _link_control_points_internal: { readonly controlPoints: { starting_curve_point: Class_Handler; ending_curve_point: Class_Handler; starting_bezier_point: Class_Handler; ending_bezier_point: Class_Handler; middle_recycling_point: Class_Handler; is_dragged: boolean; }; }; private _specific_label_values; protected get link(): Class_LinkElement; constructor(link: Class_LinkElement, link_control_points: LinkControlPoints, prefix: LabelPrefix); /** * Rétro-compat liens : `vertical_text` historique vaut +90° sur un lien vertical * (texte top→bottom, sens du flux) et −90° sinon (bottom→top). Dès que l'angle * explicite `text_angle` est posé, il prime. */ protected getEffectiveTextAngle(): number; protected verticalText(_tspanWidths: number[], textElement: d3.Selection): number | undefined; protected getD3Selection(): d3.Selection | null; protected getElementId(): string; protected getTextSelector(): string; protected getStickNameTextSelector(): string; protected getStickValueTextSelector(): string; protected stickRecentersVertically(): boolean; protected getTextPathSelector(): string; protected getFontSize(): number; /** * Get the effective thickness at the label's horizontal position. * For tapered links, interpolates between source and target thickness. */ protected getThicknessAtLabelPos(): number; /** * #377 — hauteur disponible pour une étiquette de flux = ÉPAISSEUR du flux à l'endroit du * label. Vaut aussi quand `pos_auto` repousse le texte au-dessus/au-dessous du flux : c'est * l'épaisseur qui commande, pas la position du texte. */ protected getFitAvailableHeight(): number | null; /** * opensankey#1301 — milieu (abscisse curviligne) de la polyligne source → points de * contrôle → cible, pour ancrer le label « milieu » sur le tracé routé. null si pas * de waypoint (ou recyclage) → repli sur le calcul paramétrique historique. */ protected getWaypointLabelMidpoint(): [number, number] | null; protected getLabelPos(): [number, number, string, string]; protected updateLabelPos(): void; protected applyTextStyle(selection: d3.Selection | undefined): void; protected getTextPathSide(): 'left' | 'right'; protected getTextPathOffset(): [number, string, number, string]; protected updateTextPathOffset(): void; protected getTextClasses(): string[]; protected getTextElementId(): string; protected applySpecialTextContent(textElement: d3.Selection, labelText: string): boolean; protected applyTextDragHandlers(textElement: d3.Selection): void; protected createLabelGroup(): d3_selection_type | null; protected finalizeLabelCreation(_textElement: d3.Selection): void; protected dragTextPathStart(_event: d3.D3DragEvent): void; protected dragTextPathMove(event: d3.D3DragEvent): void; protected dragTextPathEnd(_event: d3.D3DragEvent): void; protected getIconSize(): [number, number]; protected getIconPos(): [number, number]; drawImage(): void; } export declare class LinkDrawNameLabel extends LinkDrawLabelBase { constructor(link: Class_LinkElement, link_control_points: LinkControlPoints, prefix: 'name_label' | 'icon'); protected getLabelText(): string; protected shouldDrawLabel(): boolean; } export declare class LinkDrawValueLabel extends LinkDrawLabelBase { constructor(link: Class_LinkElement, link_control_points: LinkControlPoints); protected getLabelText(): string; protected getLabelPos(): [number, number, string, string]; protected updateLabelPos(): void; protected getInputInitialValue(): string; protected getPeerNameLabel(): DrawLabelBase | null; /** * Vrai dès qu'une frappe a réellement changé la valeur du flux pendant la session * d'édition en cours. Sert à ne déclencher les effets de bord de fin d'édition * (échelle, menus) que sur une vraie saisie : ouvrir puis refermer l'éditeur sans * rien changer ne doit RIEN recalculer (une échelle réglée à la main resterait * sinon écrasée par le simple fait de double-cliquer sur une valeur). */ private _value_edited; protected onInputChange(value: string): void; setInputLabelInvisible(): void; protected finalizeLabelCreation(textElement: d3.Selection): void; protected shouldDrawLabel(): boolean; } export {};