@@ -3,30 +3,22 @@ import {
33 sankey ,
44 sankeyJustify ,
55 sankeyLinkHorizontal ,
6+ type SankeyLink ,
7+ type SankeyNode ,
68} from "d3-sankey" ;
79import { SANKEY_HEIGHT , SANKEY_WIDTH } from "../config" ;
810import type { SankeyGraph as AppSankeyGraph } from "../types/election" ;
911import { orientacaoFromSankeyNodeName , orientacaoToColor } from "../utils/colors" ;
1012
11- interface LayoutNode {
12- name : string ;
13- x0 ?: number ;
14- x1 ?: number ;
15- y0 ?: number ;
16- y1 ?: number ;
17- value ?: number ;
18- }
19-
20- interface LayoutLink {
21- source : LayoutNode | number ;
22- target : LayoutNode | number ;
23- value : number ;
24- width ?: number ;
25- }
13+ type LayoutNode = SankeyNode < { } , { } > & { name : string } ;
14+ type LayoutLink = SankeyLink < LayoutNode , { } > ;
2615
2716const UNITS = "Cadeiras" ;
2817const MARGIN = { top : 20 , right : 10 , bottom : 50 , left : 10 } ;
2918const INNER_HEIGHT = SANKEY_HEIGHT - 100 ;
19+ /** Mesmo deslocamento do site legado (camara.js) */
20+ const CHART_OFFSET_X = 100 ;
21+ const CHART_OFFSET_Y = 55 ;
3022
3123export class CouncilSankey {
3224 private readonly svg : Selection < SVGSVGElement , unknown , null , undefined > ;
@@ -91,51 +83,50 @@ export class CouncilSankey {
9183 [ SANKEY_WIDTH - MARGIN . right , INNER_HEIGHT ] ,
9284 ] ) ;
9385
94- const laidOut = layout ( layoutGraph ) ;
86+ const { nodes, links } = layout ( layoutGraph ) ;
87+ const linkPath = sankeyLinkHorizontal ( ) ;
88+ const activeLinks = links . filter ( ( l ) => l . value > 0 ) ;
89+ const activeNodes = nodes . filter ( ( n ) => ( n . value ?? 0 ) > 0 ) ;
90+ const graphForUpdate = { nodes : activeNodes , links : activeLinks } ;
91+
92+ const innerG = this . chartG
93+ . append ( "g" )
94+ . attr ( "class" , "sankey-inner" )
95+ . attr ( "transform" , `translate(${ CHART_OFFSET_X } ,${ CHART_OFFSET_Y } )` ) ;
96+
97+ const dragContainer = innerG . node ( ) ! ;
9598
96- const linkG = this . chartG
99+ const linkSelection = innerG
97100 . append ( "g" )
98- . attr ( "transform " , "translate(100,55) " )
101+ . attr ( "class " , "sankey-links " )
99102 . attr ( "fill" , "none" )
100103 . attr ( "stroke" , "#000" )
101104 . attr ( "stroke-opacity" , 0.2 )
105+ . style ( "pointer-events" , "none" )
102106 . selectAll < SVGPathElement , LayoutLink > ( "path" )
103- . data ( laidOut . links . filter ( ( l ) => l . value > 0 ) )
107+ . data ( activeLinks )
104108 . join ( "path" )
105- . attr ( "class" , "link" )
106- . attr ( "d" , sankeyLinkHorizontal ( ) )
107- . attr ( "stroke-width" , ( d ) => Math . max ( 1 , d . width ?? 0 ) ) ;
109+ . attr ( "class" , ( d ) => ( d . value ? "link" : "link zero" ) )
110+ . attr ( "d" , linkPath )
111+ . attr ( "stroke-width" , ( d ) => Math . max ( 1 , d . width ?? 0 ) )
112+ . sort ( ( a , b ) => ( b . width ?? 0 ) - ( a . width ?? 0 ) ) ;
108113
109- linkG . append ( "title" ) . text ( ( d ) => {
114+ linkSelection . append ( "title" ) . text ( ( d ) => {
110115 const s = d . source as LayoutNode ;
111116 const t = d . target as LayoutNode ;
112117 return `${ s . name } → ${ t . name } \n${ d . value } ` ;
113118 } ) ;
114119
115- const nodeDrag = drag < SVGGElement , LayoutNode > ( )
116- . subject ( ( d ) => d )
117- . on ( "drag" , ( event , d ) => {
118- const height = ( d . y1 ?? 0 ) - ( d . y0 ?? 0 ) ;
119- d . y0 = Math . max ( 0 , Math . min ( INNER_HEIGHT - height , event . y ) ) ;
120- d . y1 = ( d . y0 ?? 0 ) + height ;
121- select ( event . sourceEvent . target . parentNode as SVGGElement ) . attr (
122- "transform" ,
123- `translate(${ d . x0 ?? 0 } ,${ d . y0 ?? 0 } )` ,
124- ) ;
125- linkG . attr ( "d" , sankeyLinkHorizontal ( ) ) ;
126- } ) ;
127-
128- const node = this . chartG
120+ const nodeSelection = innerG
129121 . append ( "g" )
130- . attr ( "transform " , "translate(100,55) " )
122+ . attr ( "class " , "sankey-nodes " )
131123 . selectAll < SVGGElement , LayoutNode > ( "g" )
132- . data ( laidOut . nodes . filter ( ( n ) => ( n . value ?? 0 ) > 0 ) )
124+ . data ( activeNodes )
133125 . join ( "g" )
134- . attr ( "class" , "node" )
135- . attr ( "transform" , ( d ) => `translate(${ d . x0 ?? 0 } ,${ d . y0 ?? 0 } )` )
136- . call ( nodeDrag ) ;
126+ . attr ( "class" , ( d ) => ( ( d . value ?? 0 ) > 0 ? "node" : "node zero" ) )
127+ . attr ( "transform" , ( d ) => `translate(${ d . x0 ?? 0 } ,${ d . y0 ?? 0 } )` ) ;
137128
138- node
129+ nodeSelection
139130 . append ( "rect" )
140131 . attr ( "height" , ( d ) => Math . max ( 1 , ( d . y1 ?? 0 ) - ( d . y0 ?? 0 ) ) )
141132 . attr ( "width" , ( d ) => Math . max ( 1 , ( d . x1 ?? 0 ) - ( d . x0 ?? 0 ) ) )
@@ -144,7 +135,7 @@ export class CouncilSankey {
144135 . append ( "title" )
145136 . text ( ( d ) => `${ d . name } \n${ formatValue ( d . value ?? 0 ) } ` ) ;
146137
147- node
138+ nodeSelection
148139 . append ( "text" )
149140 . attr ( "x" , - 6 )
150141 . attr ( "y" , ( d ) => ( ( d . y1 ?? 0 ) - ( d . y0 ?? 0 ) ) / 2 )
@@ -155,6 +146,32 @@ export class CouncilSankey {
155146 . attr ( "x" , ( d ) => ( d . x1 ?? 0 ) - ( d . x0 ?? 0 ) + 6 )
156147 . attr ( "text-anchor" , "start" ) ;
157148
149+ const nodeDrag = drag < SVGRectElement , LayoutNode > ( )
150+ . container ( ( ) => dragContainer )
151+ . subject ( function ( _event , d ) {
152+ return { x : d . x0 ?? 0 , y : d . y0 ?? 0 } ;
153+ } )
154+ . on ( "start" , function ( ) {
155+ select ( this . parentNode as SVGGElement ) . raise ( ) ;
156+ } )
157+ . on ( "drag" , function ( event , d ) {
158+ const nodeHeight = ( d . y1 ?? 0 ) - ( d . y0 ?? 0 ) ;
159+ const y = Math . max (
160+ MARGIN . top ,
161+ Math . min ( INNER_HEIGHT - nodeHeight , event . y ) ,
162+ ) ;
163+ d . y0 = y ;
164+ d . y1 = y + nodeHeight ;
165+ select ( this . parentNode as SVGGElement ) . attr (
166+ "transform" ,
167+ `translate(${ d . x0 ?? 0 } ,${ y } )` ,
168+ ) ;
169+ layout . update ( graphForUpdate ) ;
170+ linkSelection . attr ( "d" , linkPath ) ;
171+ } ) ;
172+
173+ nodeSelection . selectAll < SVGRectElement , LayoutNode > ( "rect" ) . call ( nodeDrag ) ;
174+
158175 this . chartG
159176 . append ( "text" )
160177 . attr ( "class" , "chartTitle2" )
0 commit comments