1+ function donutChart ( ) {
2+ var width ,
3+ height ,
4+ margin = { top : 10 , right : 10 , bottom : 10 , left : 10 } ,
5+ colour = d3 . scaleOrdinal ( d3 . schemeCategory20 ) , // colour scheme
6+ variable , // value in data that will dictate proportions on chart
7+ category , // compare data by
8+ padAngle , // effectively dictates the gap between slices
9+ floatFormat = d3 . format ( '.4r' ) ,
10+ cornerRadius , // sets how rounded the corners are on each slice
11+ percentFormat = d3 . format ( ',.2%' ) ;
12+
13+ function chart ( selection ) {
14+ selection . each ( function ( data ) {
15+ // generate chart
16+
17+ // ===========================================================================================
18+ // Set up constructors for making donut. See https://github.com/d3/d3-shape/blob/master/README.md
19+ var radius = 140 ;
20+
21+ // creates a new pie generator
22+ var pie = d3 . pie ( )
23+ . value ( function ( d ) { return floatFormat ( d [ variable ] ) ; } )
24+ . sort ( null ) ;
25+
26+ // contructs and arc generator. This will be used for the donut. The difference between outer and inner
27+ // radius will dictate the thickness of the donut
28+ var arc = d3 . arc ( )
29+ . outerRadius ( radius * 0.9 )
30+ . innerRadius ( radius * 0.6 )
31+ . cornerRadius ( cornerRadius )
32+ . padAngle ( padAngle ) ;
33+
34+ // this arc is used for aligning the text labels
35+ var outerArc = d3 . arc ( )
36+ . outerRadius ( radius * 0.9 )
37+ . innerRadius ( radius * 0.9 ) ;
38+ // ===========================================================================================
39+
40+ // ===========================================================================================
41+ // append the svg object to the selection
42+ var svg = selection . append ( 'svg' )
43+ . attr ( 'width' , width + margin . left + margin . right )
44+ . attr ( 'height' , height + margin . top + margin . bottom )
45+ . append ( 'g' )
46+ . attr ( 'transform' , 'translate(' + width / 2 + ',' + height / 2 + ')' ) ;
47+ // ===========================================================================================
48+
49+ // ===========================================================================================
50+ // g elements to keep elements within svg modular
51+ svg . append ( 'g' ) . attr ( 'class' , 'slices' ) ;
52+ svg . append ( 'g' ) . attr ( 'class' , 'labelName' ) ;
53+ svg . append ( 'g' ) . attr ( 'class' , 'lines' ) ;
54+ // ===========================================================================================
55+
56+ // ===========================================================================================
57+ // add and colour the donut slices
58+ var path = svg . select ( '.slices' )
59+ . datum ( data ) . selectAll ( 'path' )
60+ . data ( pie )
61+ . enter ( ) . append ( 'path' )
62+ . attr ( 'fill' , function ( d ) { return colour ( d . data [ category ] ) ; } )
63+ . attr ( 'd' , arc ) ;
64+ // ===========================================================================================
65+
66+ // ===========================================================================================
67+ // add text labels
68+ var label = svg . select ( '.labelName' ) . selectAll ( 'text' )
69+ . data ( pie )
70+ . enter ( ) . append ( 'text' )
71+ . attr ( 'dy' , '.35em' )
72+ . html ( function ( d ) {
73+ // add "key: value" for given category. Number inside tspan is bolded in stylesheet.
74+ return ' <tspan class="siteName">' + d . data [ category ] + ' </tspan>' ;
75+ } )
76+ . attr ( 'transform' , function ( d ) {
77+
78+ // effectively computes the centre of the slice.
79+ // see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid
80+ var pos = outerArc . centroid ( d ) ;
81+
82+ // changes the point to be on left or right depending on where label is.
83+ pos [ 0 ] = radius * 0.95 * ( midAngle ( d ) < Math . PI ? 1 : - 1 ) ;
84+ return 'translate(' + pos + ')' ;
85+ } )
86+ . style ( 'text-anchor' , function ( d ) {
87+ // if slice centre is on the left, anchor text to start, otherwise anchor to end
88+ return ( midAngle ( d ) ) < Math . PI ? 'start' : 'end' ;
89+ } ) ;
90+ // ===========================================================================================
91+
92+ // ===========================================================================================
93+ // add lines connecting labels to slice. A polyline creates straight lines connecting several points
94+ var polyline = svg . select ( '.lines' )
95+ . selectAll ( 'polyline' )
96+ . data ( pie )
97+ . enter ( ) . append ( 'polyline' )
98+ . attr ( 'points' , function ( d ) {
99+
100+ // see label transform function for explanations of these three lines.
101+ var pos = outerArc . centroid ( d ) ;
102+ pos [ 0 ] = radius * 0.95 * ( midAngle ( d ) < Math . PI ? 1 : - 1 ) ;
103+ return [ arc . centroid ( d ) , outerArc . centroid ( d ) , pos ]
104+ } ) ;
105+ // ===========================================================================================
106+
107+ // ===========================================================================================
108+ // add tooltip to mouse events on slices and labels
109+ d3 . selectAll ( '.labelName text, .slices path' ) . call ( toolTip ) ;
110+ // ===========================================================================================
111+
112+ // ===========================================================================================
113+ // Functions
114+
115+ // calculates the angle for the middle of a slice
116+ function midAngle ( d ) { return d . startAngle + ( d . endAngle - d . startAngle ) / 2 ; }
117+
118+ // function that creates and adds the tool tip to a selected element
119+ function toolTip ( selection ) {
120+
121+ // add tooltip (svg circle element) when mouse enters label or slice
122+ selection . on ( 'mouseenter' , function ( data ) {
123+
124+ svg . append ( 'text' )
125+ . attr ( 'class' , 'toolCircle' )
126+ . attr ( 'dy' , - 15 ) // hard-coded. can adjust this to adjust text vertical alignment in tooltip
127+ . html ( toolTipHTML ( data ) ) // add text to the circle.
128+ . style ( 'font-size' , '.9em' )
129+ . style ( 'text-anchor' , 'middle' ) ; // centres text in tooltip
130+
131+ svg . append ( 'circle' )
132+ . attr ( 'class' , 'toolCircle' )
133+ . attr ( 'r' , radius * 0.55 ) // radius of tooltip circle
134+ . style ( 'fill' , colour ( data . data [ category ] ) ) // colour based on category mouse is over
135+ . style ( 'fill-opacity' , 0.35 ) ;
136+
137+ } ) ;
138+
139+ // remove the tooltip when mouse leaves the slice/label
140+ selection . on ( 'mouseout' , function ( ) {
141+ d3 . selectAll ( '.toolCircle' ) . remove ( ) ;
142+ } ) ;
143+ }
144+
145+ // function to create the HTML string for the tool tip. Loops through each key in data object
146+ // and returns the html string key: value
147+ function toolTipHTML ( data ) {
148+
149+ var tip = '' ,
150+ i = 0 ;
151+
152+ for ( var key in data . data ) {
153+
154+ // if value is a number, format it as a percentage
155+ var value = ( ! isNaN ( parseFloat ( data . data [ key ] ) ) ) ? percentFormat ( data . data [ key ] ) : data . data [ key ] ;
156+ if ( key === 'summary' )
157+ value = convertSummaryTimeToString ( data . data [ key ] ) ;
158+ var className = '' ;
159+ if ( key === 'percentage' )
160+ className = 'class="percentageValue"' ;
161+
162+ // leave off 'dy' attr for first tspan so the 'dy' attr on text element works. The 'dy' attr on
163+ // tspan effectively imitates a line break.
164+ if ( i === 0 ) tip += '<tspan x="0">' + value + '</tspan>' ;
165+ else tip += '<tspan x="0" dy="1.2em"' + className + '>' + value + '</tspan>' ;
166+ i ++ ;
167+ }
168+
169+ return tip ;
170+ }
171+ // ===========================================================================================
172+
173+ } ) ;
174+ }
175+
176+ // getter and setter functions. See Mike Bostocks post "Towards Reusable Charts" for a tutorial on how this works.
177+ chart . width = function ( value ) {
178+ if ( ! arguments . length ) return width ;
179+ width = value ;
180+ return chart ;
181+ } ;
182+
183+ chart . height = function ( value ) {
184+ if ( ! arguments . length ) return height ;
185+ height = value ;
186+ return chart ;
187+ } ;
188+
189+ chart . margin = function ( value ) {
190+ if ( ! arguments . length ) return margin ;
191+ margin = value ;
192+ return chart ;
193+ } ;
194+
195+ chart . radius = function ( value ) {
196+ if ( ! arguments . length ) return radius ;
197+ radius = value ;
198+ return chart ;
199+ } ;
200+
201+ chart . padAngle = function ( value ) {
202+ if ( ! arguments . length ) return padAngle ;
203+ padAngle = value ;
204+ return chart ;
205+ } ;
206+
207+ chart . cornerRadius = function ( value ) {
208+ if ( ! arguments . length ) return cornerRadius ;
209+ cornerRadius = value ;
210+ return chart ;
211+ } ;
212+
213+ chart . colour = function ( value ) {
214+ if ( ! arguments . length ) return colour ;
215+ colour = value ;
216+ return chart ;
217+ } ;
218+
219+ chart . variable = function ( value ) {
220+ if ( ! arguments . length ) return variable ;
221+ variable = value ;
222+ return chart ;
223+ } ;
224+
225+ chart . category = function ( value ) {
226+ if ( ! arguments . length ) return category ;
227+ category = value ;
228+ return chart ;
229+ } ;
230+
231+ return chart ;
232+ }
0 commit comments