1
+ 'use client' ;
2
+
1
3
import * as React from 'react' ;
2
4
import { SyntheticEvent } from 'react' ;
3
5
import $ from 'jquery' ;
@@ -38,6 +40,7 @@ export type LollipopPlotNoTooltipProps = LollipopPlotProps & {
38
40
) => void ;
39
41
onMouseLeave ?: ( ) => void ;
40
42
onBackgroundMouseMove ?: ( ) => void ;
43
+ onZoomOrMove ?: ( ) => void ; // Add this prop for zoom/move events
41
44
} ;
42
45
43
46
const DELETE_FOR_DOWNLOAD_CLASS = 'delete-for-download' ;
@@ -55,7 +58,8 @@ export default class LollipopPlotNoTooltip extends React.Component<
55
58
private sequenceComponents : Sequence [ ] = [ ] ;
56
59
57
60
private svg : SVGElement | undefined ;
58
- private shiftPressed : boolean = false ;
61
+ private shiftPressed = false ;
62
+ private zoomBehavior : any ; // Store zoom behavior
59
63
60
64
private lollipopLabelPadding = 20 ;
61
65
private domainPadding = 5 ;
@@ -82,6 +86,20 @@ export default class LollipopPlotNoTooltip extends React.Component<
82
86
showYAxis : true ,
83
87
} ;
84
88
89
+ public getLollipopComponent ( index : string ) : Lollipop | undefined {
90
+ return this . lollipopComponents [ index ] ;
91
+ }
92
+
93
+ public getAllLollipopComponents ( ) : { [ key : string ] : Lollipop } {
94
+ return this . lollipopComponents ;
95
+ }
96
+
97
+ public findMirrorLollipop ( codon : number ) : Lollipop | undefined {
98
+ return Object . values ( this . lollipopComponents ) . find (
99
+ l => l . props . spec . codon === codon
100
+ ) ;
101
+ }
102
+
85
103
constructor ( props : any ) {
86
104
super ( props ) ;
87
105
makeObservable ( this ) ;
@@ -90,6 +108,47 @@ export default class LollipopPlotNoTooltip extends React.Component<
90
108
@autobind
91
109
protected ref ( svg : SVGElement ) {
92
110
this . svg = svg ;
111
+
112
+ // Set up zoom behavior after the SVG is rendered
113
+ if ( svg ) {
114
+ this . setupZoom ( ) ;
115
+ }
116
+ }
117
+
118
+ // Set up D3 zoom behavior
119
+ private setupZoom ( ) {
120
+ if ( this . svg ) {
121
+ const d3 = require ( 'd3' ) ;
122
+
123
+ // Create zoom behavior
124
+ this . zoomBehavior = d3
125
+ . zoom ( )
126
+ . scaleExtent ( [ 0.5 , 8 ] ) // Allow zooming from 0.5x to 8x
127
+ . on ( 'zoom' , this . onZoom ) ;
128
+
129
+ // Apply zoom behavior to SVG
130
+ d3 . select ( this . svg ) . call ( this . zoomBehavior ) ;
131
+ }
132
+ }
133
+
134
+ @action . bound
135
+ private onZoom ( event : any ) {
136
+ // Apply the zoom transform to the content group
137
+ if ( this . svg ) {
138
+ const d3 = require ( 'd3' ) ;
139
+ const contentGroup = d3
140
+ . select ( this . svg )
141
+ . select ( 'g.lollipop-content-group' ) ;
142
+
143
+ if ( ! contentGroup . empty ( ) ) {
144
+ contentGroup . attr ( 'transform' , event . transform ) ;
145
+
146
+ // Notify parent component about zoom/move
147
+ if ( this . props . onZoomOrMove ) {
148
+ this . props . onZoomOrMove ( ) ;
149
+ }
150
+ }
151
+ }
93
152
}
94
153
95
154
@action . bound
@@ -118,14 +177,14 @@ export default class LollipopPlotNoTooltip extends React.Component<
118
177
}
119
178
120
179
@action . bound
121
- protected onKeyDown ( e : JQueryKeyEventObject ) {
180
+ protected onKeyDown ( e : JQuery . KeyDownEvent ) {
122
181
if ( e . which === 16 ) {
123
182
this . shiftPressed = true ;
124
183
}
125
184
}
126
185
127
186
@action . bound
128
- protected onKeyUp ( e : JQueryKeyEventObject ) {
187
+ protected onKeyUp ( e : JQuery . KeyUpEvent ) {
129
188
if ( e . which === 16 ) {
130
189
this . shiftPressed = false ;
131
190
}
@@ -266,7 +325,7 @@ export default class LollipopPlotNoTooltip extends React.Component<
266
325
return ( codon / this . props . xMax ) * this . props . vizWidth ;
267
326
}
268
327
269
- private countToHeight ( count : number , yMax : number , zeroHeight : number = 0 ) {
328
+ private countToHeight ( count : number , yMax : number , zeroHeight = 0 ) {
270
329
return zeroHeight + Math . min ( 1 , count / yMax ) * this . yAxisHeight ;
271
330
}
272
331
@@ -554,7 +613,7 @@ export default class LollipopPlotNoTooltip extends React.Component<
554
613
555
614
let start = 0 ;
556
615
557
- let segments = _ . map ( this . props . domains , ( domain : DomainSpec ) => {
616
+ const segments = _ . map ( this . props . domains , ( domain : DomainSpec ) => {
558
617
const segment = {
559
618
start,
560
619
end : this . codonToX ( domain . startCodon ) , // segment ends at the start of the current domain
@@ -710,7 +769,7 @@ export default class LollipopPlotNoTooltip extends React.Component<
710
769
ticks : Tick [ ] ,
711
770
placement ?: LollipopPlacement ,
712
771
groupName ?: string ,
713
- symbol : string = '#'
772
+ symbol = '#'
714
773
) {
715
774
let label ;
716
775
if ( this . props . yAxisLabelFormatter ) {
@@ -862,47 +921,52 @@ export default class LollipopPlotNoTooltip extends React.Component<
862
921
onClick = { this . onBackgroundClick }
863
922
onMouseMove = { this . onBackgroundMouseMove }
864
923
/>
865
- {
866
- // Originally this had tooltips by having separate segments
867
- // with hit zones. We disabled those separate segments with
868
- // tooltips (this.sequenceSegments) and instead just draw
869
- // one rectangle
870
- // this.sequenceSegments
871
- }
872
- < rect
873
- fill = "#BABDB6"
874
- x = { this . geneX }
875
- y = { this . geneY }
876
- height = { this . geneHeight }
877
- width = {
878
- // the x-axis start from 0, so the rectangle size should be (width + 1)
879
- this . props . vizWidth + 1
924
+
925
+ { /* Wrap all content in a group for zooming */ }
926
+ < g className = "lollipop-content-group" >
927
+ {
928
+ // Originally this had tooltips by having separate segments
929
+ // with hit zones. We disabled those separate segments with
930
+ // tooltips (this.sequenceSegments) and instead just draw
931
+ // one rectangle
932
+ // this.sequenceSegments
880
933
}
881
- />
882
- { this . lollipops }
883
- { this . domains }
884
- { this . xAxisOnTop && this . xAxis ( 0 , LollipopPlacement . TOP ) }
885
- { this . xAxisOnBottom &&
886
- this . xAxis ( this . xAxisY , LollipopPlacement . BOTTOM ) }
887
- { this . props . showYAxis &&
888
- this . yAxis (
889
- this . yAxisY ,
890
- this . yMax ,
891
- this . yTicks ,
892
- LollipopPlacement . TOP ,
893
- this . topGroupName ,
894
- this . topGroupSymbol
895
- ) }
896
- { this . props . showYAxis &&
897
- this . needBottomPlacement &&
898
- this . yAxis (
899
- this . bottomYAxisY ,
900
- this . bottomYMax ,
901
- this . bottomYTicks ,
902
- LollipopPlacement . BOTTOM ,
903
- this . bottomGroupName ,
904
- this . bottomGroupSymbol
905
- ) }
934
+ < rect
935
+ fill = "#BABDB6"
936
+ x = { this . geneX }
937
+ y = { this . geneY }
938
+ height = { this . geneHeight }
939
+ width = {
940
+ // the x-axis start from 0, so the rectangle size should be (width + 1)
941
+ this . props . vizWidth + 1
942
+ }
943
+ />
944
+ { this . lollipops }
945
+ { this . domains }
946
+ { this . xAxisOnTop &&
947
+ this . xAxis ( 0 , LollipopPlacement . TOP ) }
948
+ { this . xAxisOnBottom &&
949
+ this . xAxis ( this . xAxisY , LollipopPlacement . BOTTOM ) }
950
+ { this . props . showYAxis &&
951
+ this . yAxis (
952
+ this . yAxisY ,
953
+ this . yMax ,
954
+ this . yTicks ,
955
+ LollipopPlacement . TOP ,
956
+ this . topGroupName ,
957
+ this . topGroupSymbol
958
+ ) }
959
+ { this . props . showYAxis &&
960
+ this . needBottomPlacement &&
961
+ this . yAxis (
962
+ this . bottomYAxisY ,
963
+ this . bottomYMax ,
964
+ this . bottomYTicks ,
965
+ LollipopPlacement . BOTTOM ,
966
+ this . bottomGroupName ,
967
+ this . bottomGroupSymbol
968
+ ) }
969
+ </ g >
906
970
</ svg >
907
971
</ div >
908
972
) ;
0 commit comments