11/* eslint-disable testing-library/no-node-access */
2- import React from 'react' ;
32import * as d3 from 'd3' ;
4-
3+ import React from 'react' ;
54import { Button , Modal } from 'react-bootstrap' ;
6- import styles from './PeopleReport/PeopleReport.module.css' ;
75import { boxStyle , boxStyleDark } from '../../styles' ;
6+ import styles from './PeopleReport/PeopleReport.module.css' ;
7+ import {
8+ createAxes ,
9+ createDots ,
10+ createLabels ,
11+ createLegend ,
12+ createLine ,
13+ createSvgRoot ,
14+ createTooltip ,
15+ } from './d3GraphUtils' ;
816
917function InfringementsViz ( { infringements, fromDate, toDate, darkMode } ) {
1018 const [ graphVisible , setGraphVisible ] = React . useState ( false ) ;
@@ -18,197 +26,97 @@ function InfringementsViz({ infringements, fromDate, toDate, darkMode }) {
1826
1927 const handleModalShow = d => {
2028 setFocusedInf ( d ) ;
21- if ( graphVisible === false ) {
22- setModalVisible ( ! modalVisible ) ;
23- }
24- setGraphVisible ( ! graphVisible ) ; // Open the graph when opening the modal
29+ if ( graphVisible === false ) setModalVisible ( ! modalVisible ) ;
30+ setGraphVisible ( ! graphVisible ) ;
2531 } ;
32+
2633 function displayGraph ( bsCount , maxSquareCount ) {
2734 if ( ! graphVisible ) {
2835 d3 . selectAll ( '#infplot > *' ) . remove ( ) ;
2936 } else {
3037 d3 . selectAll ( '#infplot > *' ) . remove ( ) ;
38+
3139 const margin = { top : 30 , right : 20 , bottom : 30 , left : 20 } ;
3240 const containerWidth = '1000' ;
33- // Adjusted width based on the available space
3441 const width = Math . min ( containerWidth - margin . left - margin . right , 1000 ) ;
35-
3642 const height = 400 - margin . top - margin . bottom ;
3743
38- const tooltipEl = function tooltipEl ( d ) {
39- return (
40- `${ '<div class="tip__container">' +
41- '<div class="close">' +
42- '<button>×</button>' +
43- '</div>' +
44- '<div>' +
45- 'Exact date: ' } ${ d3 . timeFormat ( '%A, %B %e, %Y' ) ( d . date ) } <br>` +
46- `Count: ${ d . count === 1 ? d . count : `${ d . count } <span class="detailsModal"><a>See All</a></span>`
47- } <br>` +
48- `Description: ${ d . des [ 0 ] } </div>` +
49- `</div>`
50- ) ;
51- } ;
52-
53- const legendEl = function legendEl ( ) {
54- return (
55- '<div class="lengendSubContainer">' +
56- '<div class="infLabelsOff">' +
57- '<button>Labels Off</button>' +
58- '</div>' +
59- '<div class="infCountLabelsOn">' +
60- '<button>Show Squares</button>' +
61- '</div>' +
62- '<div class="infDateLabelsOn">' +
63- '<button>Show Dates</button>' +
64- '</div>' +
65- '</div>'
66- ) ;
67- } ;
68-
69- const svg = d3
70- . select ( '#infplot' )
71- . append ( 'svg' )
72- . attr ( 'width' , '100%' )
73- . attr ( 'height' , height + margin . top + margin . bottom )
74- . attr ( 'viewBox' , `0 0 ${ containerWidth } ${ height + margin . top + margin . bottom } ` )
75- . append ( 'g' )
76- . attr ( 'transform' , `translate(${ margin . left } ,${ margin . top } )` ) ;
77-
78- const x = d3
79- . scaleTime ( )
80- . domain ( d3 . extent ( bsCount , d => d . date ) )
81- . range ( [ 0 , width ] ) ;
82- svg
83- . append ( 'g' )
84- . attr ( 'transform' , `translate(0, ${ height } )` )
85- . call ( d3 . axisBottom ( x ) ) ;
86-
87- const y = d3
88- . scaleLinear ( )
89- . domain ( [ 0 , maxSquareCount + 2 ] )
90- . range ( [ height , 0 ] ) ;
91- svg . append ( 'g' ) . call (
92- d3
93- . axisLeft ( y )
94- . ticks ( 5 )
95- . tickFormat ( d3 . format ( 'd' ) ) ,
96- ) ;
44+ const textColor = darkMode ? `color: #f9fafb;` : '' ;
45+ const legendHtml =
46+ `<div class="lengendSubContainer" style="${ textColor } ">` +
47+ `<div class="infLabelsOff"><button style="${ textColor } ">Labels Off</button></div>` +
48+ `<div class="infCountLabelsOn"><button style="${ textColor } ">Show Squares</button></div>` +
49+ `<div class="infDateLabelsOn"><button style="${ textColor } ">Show Dates</button></div>` +
50+ `</div>` ;
9751
98- svg
99- . append ( 'path' )
100- . datum ( bsCount )
101- . attr ( 'fill' , 'none' )
102- . attr ( 'stroke' , 'black' )
103- . attr ( 'stroke-width' , 1.5 )
104- . attr (
105- 'd' ,
106- d3
107- . line ( )
108- . x ( d => x ( d . date ) )
109- . y ( d => y ( d . count ) ) ,
110- ) ;
111-
112- svg
113- . append ( 'g' )
114- . selectAll ( 'dot' )
115- . data ( bsCount )
116- . join ( 'circle' )
117- . attr ( 'class' , 'myCircle' )
118- . attr ( 'cx' , d => x ( d . date ) )
119- . attr ( 'cy' , d => y ( d . count ) )
120- . attr ( 'r' , 3 )
121- . attr ( 'stroke' , '#69b3a2' )
122- . attr ( 'stroke-width' , 3 )
123- . attr ( 'fill' , 'white' )
124- . on ( 'click' , function handleCircleClick ( event , d ) {
125- const prevTooltip = d3 . select ( `.inf${ d . id } ` ) ;
52+ const svgRoot = createSvgRoot ( '#infplot' , containerWidth , height , margin , darkMode ) ;
53+ const svg = svgRoot . append ( 'g' ) . attr ( 'transform' , `translate(${ margin . left } ,${ margin . top } )` ) ;
12654
127- if ( prevTooltip . empty ( ) ) {
128- const Tooltip = d3
129- . select ( '#infplot' )
130- . append ( 'div' )
131- . style ( 'opacity' , 0 )
132- . attr ( 'class' , `tooltip inf${ d . id } ` )
133- . style ( 'background-color' , 'white' )
134- . style ( 'border' , 'solid' )
135- . style ( 'border-width' , '2px' )
136- . style ( 'border-radius' , '5px' )
137- . style ( 'padding' , '5px' )
138- . style ( 'max-width' , '500px' )
139- . style ( 'z-index' , 1000 ) ;
55+ const x = d3 . scaleTime ( ) . domain ( d3 . extent ( bsCount , d => d . date ) ) . range ( [ 0 , width ] ) ;
56+ const y = d3 . scaleLinear ( ) . domain ( [ 0 , maxSquareCount + 2 ] ) . range ( [ height , 0 ] ) ;
14057
141- Tooltip . html ( tooltipEl ( d ) )
142- . style ( 'left' , `${ event . pageX + 10 } px` )
143- . style ( 'top' , `${ event . pageY } px` )
144- . style ( 'opacity' , 1 ) ;
145-
146- Tooltip . select ( '.close' ) . on ( 'click' , function handleCloseClick ( ) {
147- Tooltip . remove ( ) ;
148- } ) ;
149-
150- Tooltip . select ( '.detailsModal' ) . on ( 'click' , function handleDetailsModalClick ( ) {
151- handleModalShow ( d ) ;
152- } ) ;
153- }
154- } ) ;
58+ createAxes ( svg , x , y , height , darkMode ) ;
15559
15660 svg
15761 . append ( 'g' )
62+ . call ( d3 . axisLeft ( y ) . ticks ( 5 ) . tickFormat ( d3 . format ( 'd' ) ) )
15863 . selectAll ( 'text' )
159- . data ( bsCount )
160- . join ( 'text' )
161- . attr ( 'class' , 'infCountLabel' )
162- . attr ( 'x' , d => x ( d . date ) + 10 )
163- . attr ( 'y' , d => y ( d . count ) - 5 )
164- . attr ( 'fill' , 'black' )
165- . style ( 'z-index' , 999 )
166- . style ( 'font-weight' , 700 )
167- . style ( 'display' , 'none' )
168- . text ( d => parseInt ( d . count , 10 ) ) ;
64+ . attr ( 'fill' , darkMode ? '#f9fafb' : 'black' ) ;
65+
66+ createLine ( svg , bsCount , x , y , darkMode ) ;
67+
68+ const dots = createDots ( svg , bsCount , x , y ) ;
69+ dots . on ( 'click' , function handleCircleClick ( event , d ) {
70+ const prevTooltip = d3 . select ( `.inf${ d . id } ` ) ;
71+ if ( prevTooltip . empty ( ) ) {
72+ const Tooltip = createTooltip ( '#infplot' , d , darkMode ) ;
73+ Tooltip . attr ( 'class' , `tooltip inf${ d . id } ` )
74+ . style ( 'max-width' , '500px' )
75+ . html (
76+ `<div class="tip__container"><div class="close">` +
77+ `<button style="color: ${ darkMode ? '#f9fafb' : 'black' } ; background: transparent; border: none;">×</button>` +
78+ `</div><div>Exact date: ${ d3 . timeFormat ( '%A, %B %e, %Y' ) ( d . date ) } <br>` +
79+ `Count: ${ d . count === 1 ? d . count : `${ d . count } <span class="detailsModal"><a>See All</a></span>` } <br>` +
80+ `Description: ${ d . des [ 0 ] } </div></div>`
81+ )
82+ . style ( 'left' , `${ event . pageX + 10 } px` )
83+ . style ( 'top' , `${ event . pageY } px` )
84+ . style ( 'opacity' , 1 ) ;
85+
86+ Tooltip . select ( '.close' ) . on ( 'click' , function handleCloseClick ( ) {
87+ Tooltip . remove ( ) ;
88+ } ) ;
89+ Tooltip . select ( '.detailsModal' ) . on ( 'click' , function handleDetailsModalClick ( ) {
90+ handleModalShow ( d ) ;
91+ } ) ;
92+ }
93+ } ) ;
16994
170- svg
171- . append ( 'g' )
172- . selectAll ( 'text' )
173- . data ( bsCount )
174- . join ( 'text' )
175- . attr ( 'class' , 'infDateLabel' )
176- . attr ( 'x' , d => x ( d . date ) + 10 )
177- . attr ( 'y' , d => y ( d . count ) - 5 )
178- . attr ( 'fill' , 'black' )
179- . style ( 'z-index' , 999 )
180- . style ( 'font-weight' , 700 )
181- . style ( 'display' , 'none' )
182- . text ( d => d3 . timeFormat ( '%m/%d/%Y' ) ( d . date ) ) ;
95+ createLabels ( svg , bsCount , x , y , 'infCountLabel' , darkMode , d => parseInt ( d . count , 10 ) ) ;
96+ createLabels ( svg , bsCount , x , y , 'infDateLabel' , darkMode , d => d3 . timeFormat ( '%m/%d/%Y' ) ( d . date ) ) ;
18397
184- const legend = d3
185- . select ( '#infplot' )
186- . append ( 'div' )
187- . attr ( 'class' , 'legendContainer' ) ;
188- legend . html ( legendEl ( ) ) ;
98+ const legend = createLegend ( '#infplot' , legendHtml ) ;
18999
190100 legend . select ( '.infLabelsOff' ) . on ( 'click' , function handleLabelsOffClick ( ) {
191101 d3 . selectAll ( '.infCountLabel' ) . style ( 'display' , 'none' ) ;
192102 d3 . selectAll ( '.infDateLabel' ) . style ( 'display' , 'none' ) ;
193103 } ) ;
194-
195104 legend . select ( '.infCountLabelsOn' ) . on ( 'click' , function handleCountLabelsOnClick ( ) {
196105 d3 . selectAll ( '.infCountLabel' ) . style ( 'display' , 'block' ) ;
197106 d3 . selectAll ( '.infDateLabel' ) . style ( 'display' , 'none' ) ;
198107 } ) ;
199-
200108 legend . select ( '.infDateLabelsOn' ) . on ( 'click' , function handleDateLabelsOnClick ( ) {
201109 d3 . selectAll ( '.infDateLabel' ) . style ( 'display' , 'block' ) ;
202110 d3 . selectAll ( '.infCountLabel' ) . style ( 'display' , 'none' ) ;
203111 } ) ;
204112 }
205113 }
114+
206115 const generateGraph = ( ) => {
207116 const dict = { } ;
208117 const value = [ ] ;
209118 let maxSquareCount = 0 ;
210119
211- // aggregate infringements
212120 for ( let i = 0 ; i < infringements . length ; i += 1 ) {
213121 if ( infringements [ i ] . date in dict ) {
214122 dict [ infringements [ i ] . date ] . ids . push ( infringements [ i ] . _id ) ;
@@ -223,11 +131,8 @@ function InfringementsViz({ infringements, fromDate, toDate, darkMode }) {
223131 }
224132 }
225133
226- // filter infringements by date
227134 if ( fromDate === '' || toDate === '' ) {
228- // condition no longer needed
229135 Object . keys ( dict ) . forEach ( key => {
230- // Use if statement to filter unwanted properties from the prototype chain
231136 if ( Object . prototype . hasOwnProperty . call ( dict , key ) ) {
232137 value . push ( {
233138 date : d3 . timeParse ( '%Y-%m-%d' ) ( key ) ,
@@ -236,9 +141,7 @@ function InfringementsViz({ infringements, fromDate, toDate, darkMode }) {
236141 type : 'Infringement' ,
237142 ids : dict [ key ] . ids ,
238143 } ) ;
239- if ( dict [ key ] . count > maxSquareCount ) {
240- maxSquareCount = dict [ key ] . count ;
241- }
144+ if ( dict [ key ] . count > maxSquareCount ) maxSquareCount = dict [ key ] . count ;
242145 }
243146 } ) ;
244147 } else {
@@ -253,9 +156,7 @@ function InfringementsViz({ infringements, fromDate, toDate, darkMode }) {
253156 type : 'Infringement' ,
254157 ids : dict [ key ] . ids ,
255158 } ) ;
256- if ( dict [ key ] . count > maxSquareCount ) {
257- maxSquareCount = dict [ key ] . count ;
258- }
159+ if ( dict [ key ] . count > maxSquareCount ) maxSquareCount = dict [ key ] . count ;
259160 counter += 1 ;
260161 }
261162 } ) ;
@@ -273,40 +174,38 @@ function InfringementsViz({ infringements, fromDate, toDate, darkMode }) {
273174 < Button onClick = { handleModalShow } aria-expanded = { graphVisible } style = { darkMode ? boxStyleDark : boxStyle } >
274175 { graphVisible ? 'Hide Infringements Graph' : 'Show Infringements Graph' }
275176 </ Button >
276- < div className = { `${ styles . kaitest } ${ darkMode ? 'bg-light mt-2' : '' } ` } id = "infplot" data-testid = "infplot" />
177+ < div className = { `${ styles . kaitest } ${ darkMode ? 'mt-2' : '' } ` } id = "infplot" data-testid = "infplot" />
277178
278179 < Modal size = "lg" show = { modalVisible } onHide = { handleModalClose } >
279- < Modal . Header closeButton >
180+ < Modal . Header closeButton style = { darkMode ? { backgroundColor : '#1b2a41' , color : '#f9fafb' , borderColor : '#374151' } : { } } >
280181 < Modal . Title > { focusedInf . date ? focusedInf . date . toString ( ) : 'Infringement' } </ Modal . Title >
281182 </ Modal . Header >
282- < Modal . Body >
183+ < Modal . Body style = { darkMode ? { backgroundColor : '#1b2a41' , color : '#f9fafb' } : { } } >
283184 < div id = "inf" >
284- < table >
185+ < table style = { darkMode ? { backgroundColor : '#1b2a41' , color : '#f9fafb' , width : '100%' } : { width : '100%' } } >
285186 < thead >
286- < tr >
287- < th > Descriptions</ th >
187+ < tr style = { darkMode ? { backgroundColor : '#1b2a41' } : { } } >
188+ < th style = { darkMode ? { backgroundColor : '#1b2a41' , color : '#f9fafb' } : { } } > Descriptions</ th >
288189 </ tr >
289190 </ thead >
290191 < tbody >
291192 { focusedInf . des
292- ? focusedInf . des . map ( ( desc ) => (
293- < tr key = { desc } >
294- < td > { desc } </ td >
193+ ? focusedInf . des . map ( desc => (
194+ < tr key = { desc } style = { darkMode ? { backgroundColor : '#1b2a41' } : { } } >
195+ < td style = { darkMode ? { backgroundColor : '#1b2a41' , color : '#f9fafb' } : { } } > { desc } </ td >
295196 </ tr >
296197 ) )
297198 : null }
298199 </ tbody >
299200 </ table >
300201 </ div >
301202 </ Modal . Body >
302- < Modal . Footer >
303- < Button variant = "secondary" onClick = { handleModalClose } >
304- Close
305- </ Button >
203+ < Modal . Footer style = { darkMode ? { backgroundColor : '#1b2a41' , borderColor : '#374151' } : { } } >
204+ < Button variant = "secondary" onClick = { handleModalClose } > Close</ Button >
306205 </ Modal . Footer >
307206 </ Modal >
308207 </ div >
309208 ) ;
310209}
311210
312- export default InfringementsViz ;
211+ export default InfringementsViz ;
0 commit comments