1- // Store chart data
2- const states = new WeakMap ( ) ;
3- const getState = ( chart ) => {
4- const state = states . get ( chart ) ;
5- return state || null ;
6- }
7- const setState = ( chart , updatedState ) => {
8- const originalState = getState ( chart ) ;
9- states . set (
10- chart ,
11- updatedState == null ? null : Object . assign ( { } , originalState , updatedState )
12- ) ;
13- return updatedState ;
14- }
15-
16- // Store options
17- const pluginOptions = {
18- output : 'label' ,
19- highlight : true ,
20- colors : {
21- selection : "#e8eff6" ,
22- selected : "#1f77b4" , // Unused if backgroundColorDefault set on dataset
23- unselected : "#cccccc"
24- }
25- } ;
1+ import { Selection } from "./selection" ;
2+ import { getOptions , highlightChartData } from "./utils" ;
263
27- // Export main plugin
4+ const states = new WeakMap ( ) ;
285export default {
296 id : "selectdrag" ,
307
@@ -36,126 +13,89 @@ export default {
3613
3714 // Get chart canvas
3815 const canvasElement = chart . canvas ;
16+ canvasElement . style . cursor = 'crosshair' ;
3917
40- // Draw begin
18+ // Add listen events
4119 canvasElement . addEventListener ( "mousedown" , ( e ) => {
42- // Get elements
43- const axisElements = chart . getElementsAtEventForMode ( e , "index" , { intersect : false } ) ;
44- if ( axisElements . length === 0 ) { return ; }
45-
46- // Create state
47- const state = {
48- selectionXY : {
49- drawing : true ,
50- start : { axisValue : null , axisIndex : null , x : e . offsetX , y : e . offsetY } ,
51- end : { }
52- }
53- } ;
54-
55- // Get axis value
56- const output = chart ?. config ?. options ?. plugins ?. selectdrag ?. output || pluginOptions . output ;
57- ( {
58- 'label' : ( ) => {
59- const axisIndex = chart . getElementsAtEventForMode ( e , "index" , { intersect : false } ) [ 0 ] . index ;
60- state . selectionXY . start . axisIndex = axisIndex ;
61- state . selectionXY . start . axisValue = chart . data . labels [ axisIndex ] ;
62- } ,
63- 'value' : ( ) => {
64- // Get value by scale
65- state . selectionXY . start . axisValue = chart . scales . x . getValueForPixel ( e . offsetX ) ;
66- } ,
67- } ) [ output ] ( ) ;
20+ // Get state
21+ const selection : Selection = states . get ( chart ) || new Selection ( ) ;
22+
23+ // Check for drag?
24+ if ( selection . isDrag ( e ) ) {
25+ selection . handleDragStart ( chart , e ) ;
26+ } else {
27+ selection . handleSelectStart ( chart , e ) ;
28+ }
6829
69- // Set selection origin
70- setState ( chart , state ) ;
30+ states . set ( chart , selection ) ;
7131 } ) ;
7232
73- // Draw end
74- window . addEventListener ( "mouseup" , ( e ) => {
75- // Check drawing status
76- const state = getState ( chart ) ;
77- if ( ! state || state ?. selectionXY ?. drawing === false ) {
78- return ;
33+ canvasElement . addEventListener ( 'mousemove' , ( e ) => {
34+ // Get existing selection
35+ const selection : Selection = states . get ( chart ) ;
36+ if ( ! selection ) { return ; }
37+
38+ if ( selection . isSelecting === true ) {
39+ selection . handleSelectMove ( chart , e ) ;
7940 }
8041
81- // Get axis value
82- const output = chart ?. config ?. options ?. plugins ?. selectdrag ?. output || pluginOptions . output ;
83- ( {
84- 'label' : ( ) => {
85- // Get value by label
86- const axisElements = chart . getElementsAtEventForMode ( e , "index" , { intersect : false } ) ;
87- const axisIndex = axisElements . length > 0 ? axisElements [ 0 ] . index : chart . data . labels . length - 1 ;
88- const axisValue = chart . data . labels [ axisIndex ] ;
42+ if ( selection . isDragging == true ) {
43+ selection . handleDragMove ( chart , e ) ;
44+ }
8945
90- // Check values & set end origin
91- if ( state . selectionXY . start . axisIndex > axisIndex ) {
92- // Switch values - user has selected opposite way
93- state . selectionXY . end = JSON . parse ( JSON . stringify ( state . selectionXY . start ) ) ;
94- state . selectionXY . start = { axisValue, axisIndex, x : e . offsetX , y : e . offsetY }
95- } else {
96- // Set end origin
97- state . selectionXY . end = { axisValue, axisIndex, x : e . offsetX , y : e . offsetY } ;
98- }
99- } ,
100- 'value' : ( ) => {
101- // Get value by scale
102- const axisValue = chart . scales . x . getValueForPixel ( e . offsetX ) ;
103-
104- // Check values & set end origin
105- if ( state . selectionXY . start . axisValue > axisValue ) {
106- // Switch values - user has selected opposite way
107- state . selectionXY . end = JSON . parse ( JSON . stringify ( state . selectionXY . start ) ) ;
108- state . selectionXY . start = { axisValue, axisIndex : null , x : e . offsetX , y : e . offsetY }
109- } else {
110- // Set end origin
111- state . selectionXY . end = { axisValue, axisIndex : null , x : e . offsetX , y : e . offsetY } ;
112- }
113- } ,
114- } ) [ output ] ( ) ;
46+ if ( ! selection . isDragging && ! selection . isSelecting ) {
47+ selection . handleSelectHover ( chart , e ) ;
48+ }
11549
116- // End drawing
117- state . selectionXY . drawing = false ;
118- setState ( chart , state ) ;
50+ states . set ( chart , selection ) ;
51+ } ) ;
11952
120- // Render rectangle
121- chart . update ( ) ;
53+ // Draw end
54+ let mouseUpTimeout ;
55+ window . addEventListener ( "mouseup" , ( e ) => {
56+ // Get existing selection
57+ const selection : Selection = states . get ( chart ) ;
58+ if ( ! selection ) { return ; }
59+
60+ const selectComplete = ( selection ) => {
61+ clearTimeout ( mouseUpTimeout ) ;
62+ mouseUpTimeout = setTimeout ( ( ) => {
63+ const pluginOptions = getOptions ( chart ) ;
64+ if ( pluginOptions . onSelectComplete ) {
65+ const range = selection . values . getRange ( ) ;
66+ if ( range . length > 0 ) {
67+ pluginOptions . onSelectComplete ( {
68+ range : range ,
69+ boundingBox : [
70+ selection . selection . start . x ,
71+ [
72+ selection . selection . end . x ,
73+ selection . selection . start . y ,
74+ ] ,
75+ selection . selection . end ,
76+ [
77+ selection . selection . start . x ,
78+ selection . selection . end . y ,
79+ ]
80+ ]
81+ } ) ;
82+ }
83+ }
84+ } , 500 ) ;
85+ }
12286
123- // Emit event
124- const selectCompleteCallback = chart ?. config ?. options ?. plugins ?. selectdrag ?. onSelectComplete ;
125- if ( selectCompleteCallback ) {
126- selectCompleteCallback ( {
127- range : [
128- state . selectionXY . start . axisValue ,
129- state . selectionXY . end . axisValue
130- ] ,
131- boundingBox : [
132- state . selectionXY . start ,
133- [
134- state . selectionXY . end . x ,
135- state . selectionXY . start . y ,
136- ] ,
137- state . selectionXY . end ,
138- [
139- state . selectionXY . start . x ,
140- state . selectionXY . end . y ,
141- ]
142- ]
143- } ) ;
87+ if ( selection . isSelecting == true ) {
88+ selection . handleSelectEnd ( chart , e ) ;
89+ selectComplete ( selection ) ;
14490 }
145- } ) ;
14691
147- // Draw extend
148- canvasElement . addEventListener ( "mousemove" , ( e ) => {
149- // Check drawing status
150- const state = getState ( chart ) ;
151- if ( ! state || state ?. selectionXY ?. drawing === false ) {
152- return ;
92+ if ( selection . isDragging == true ) {
93+ selection . handleDragEnd ( chart , e ) ;
94+ selectComplete ( selection ) ;
15395 }
15496
155- // Set end origin
156- state . selectionXY . end = { x : e . offsetX , y : e . offsetY } ;
157- chart . render ( ) ;
158- setState ( chart , state ) ;
97+ states . set ( chart , selection ) ;
98+ chart . update ( ) ;
15999 } ) ;
160100 } ,
161101
@@ -170,72 +110,17 @@ export default {
170110 if ( highlight !== undefined && highlight == false ) { return ; }
171111
172112 // Check drawing status
173- const state = getState ( chart ) ;
174-
175- // Color based on output
176- const output = chart ?. config ?. options ?. plugins ?. selectdrag ?. output || pluginOptions . output ;
177- const colors = chart ?. config ?. options ?. plugins ?. selectdrag ?. colors || pluginOptions . colors ;
178- const backgroundColorCallback = {
179- 'label' : ( value , index , defaultColor ) => {
180- // Show selected/unselected
181- if ( index >= state . selectionXY . start ?. axisIndex && index <= state . selectionXY . end ?. axisIndex ) {
182- return defaultColor ;
183- } else {
184- return colors . unselected ;
185- }
186- } ,
187- 'value' : ( value , index , defaultColor ) => {
188- // Show selected/unselected
189- const v = value . x || value ;
190- if ( v >= state . selectionXY . start ?. axisValue && v <= state . selectionXY . end ?. axisValue ) {
191- return defaultColor ;
192- } else {
193- return colors . unselected ;
194- }
195- }
196- } [ output ] ;
197-
198- // Set highlighted
199- chart . data . datasets = chart . data . datasets . map ( ( dataset ) => {
200- dataset . backgroundColor = (
201- output == 'value' ? dataset . data : chart . data . labels
202- ) . map ( ( value , index ) => {
203- if ( ! state || ! state ?. selectionXY ?. start ?. x || ! state ?. selectionXY ?. end ?. x ) {
204- // Show default
205- return dataset . backgroundColorDefault || colors . selected ;
206- } else {
207- // Show selected/unselected
208- return backgroundColorCallback ( value , index , dataset . backgroundColorDefault || colors . selected ) ;
209- }
210- } ) ;
211- return dataset ;
212- } ) ;
113+ highlightChartData ( chart , states . get ( chart ) || null ) ;
213114 } ,
214115
215116 afterDraw : ( chart , args , options ) => {
216117 // Check drawing status
217- const state = getState ( chart ) ;
218- if ( ! state || ( state ?. selectionXY ?. drawing === false && ! state . selectionXY . end ?. x ) ) {
118+ const selection : Selection = states . get ( chart ) ;
119+ if ( ! selection ?. selection || ( selection ?. isSelecting === false && ! selection . selection . end ?. x ) ) {
219120 return ;
220121 }
221122
222- // Save canvas state
223- const { ctx} = chart ;
224- ctx . save ( ) ;
225-
226- // Draw user rectangle
227- ctx . globalCompositeOperation = "destination-over" ;
228-
229- // Draw selection
230- ctx . fillStyle = pluginOptions . colors . selection ;
231- ctx . fillRect (
232- ( state . selectionXY . start ?. x || 0 ) , chart . chartArea . top ,
233- ( state . selectionXY . end ?. x || 0 ) - ( state . selectionXY . start ?. x || 0 ) ,
234- chart . chartArea . height
235- ) ;
236-
237- // Restore canvas
238- ctx . restore ( ) ;
123+ selection . draw ( chart , getOptions ( chart ) ) ;
239124 } ,
240125
241126 setSelection : ( chart , range = [ ] ) => {
@@ -247,56 +132,24 @@ export default {
247132 // Check if new data blank
248133 if ( range . length === 0 ) {
249134 // Clear selection
250- setState ( chart , null ) ;
135+ states . delete ( chart ) ;
251136 chart . update ( ) ;
137+ return ;
252138 }
253139
254- // Create state
255- const state = {
256- selectionXY : {
257- drawing : false ,
258- start : { } ,
259- end : { }
260- }
261- } ;
140+ // Creat new selection
141+ const selection = new Selection ( ) ;
262142
263- // Get output type
264- const output = chart ?. config ?. options ?. plugins ?. selectdrag ?. output || pluginOptions . output ;
265- const getValue = {
266- 'label' : ( v ) => {
267- const axisIndex = chart . data . labels . findIndex ( ( item ) => item === v ) ;
268- return { i : axisIndex , v : chart . data . labels [ axisIndex ] } ;
269- } ,
270- 'value' : ( v ) => {
271- return { i : null , v : v . x || v } ;
272- }
273- } [ output ] ;
274-
275- // Set start axis
276- const startValue = getValue ( range [ 0 ] ) ;
277- state . selectionXY . start = {
278- axisValue : startValue . v ,
279- axisIndex : startValue . i ,
280- x : chart . scales . x . getPixelForValue ( startValue . v ) ,
281- y : 0
282- }
283-
284- // Set end axis
285- const endValue = getValue ( range [ 1 ] ) ;
286- state . selectionXY . end = {
287- axisValue : endValue . v ,
288- axisIndex : endValue . i ,
289- x : chart . scales . x . getPixelForValue ( endValue . v ) ,
290- y : chart . chartArea . height
291- }
143+ // Set range and store
144+ selection . set ( chart , range ) ; states . set ( chart , selection ) ;
292145
293- setState ( chart , state ) ;
146+ // Update chart with selection
294147 chart . update ( ) ;
295148 } ,
296149
297150 clearSelection : ( chart ) => {
298151 // Clear state
299- setState ( chart , null ) ;
152+ states . delete ( chart ) ;
300153 chart . update ( ) ;
301154 }
302155}
0 commit comments