@@ -4,7 +4,7 @@ var w = 120,
44 min = Infinity ,
55 max = - Infinity ;
66
7- var chart = d3 . chart . box ( )
7+ var chart = boxChart ( )
88 . whiskers ( iqr ( 1.5 ) )
99 . width ( w - m [ 1 ] - m [ 3 ] )
1010 . height ( h - m [ 0 ] - m [ 2 ] ) ;
@@ -66,3 +66,301 @@ function iqr(k) {
6666 return [ i , j ] ;
6767 } ;
6868}
69+
70+ // Inspired by http://informationandvisualization.de/blog/box-plot
71+ function boxChart ( ) {
72+ var width = 1 ,
73+ height = 1 ,
74+ duration = 0 ,
75+ domain = null ,
76+ value = Number ,
77+ whiskers = boxWhiskers ,
78+ quartiles = boxQuartiles ,
79+ tickFormat = null ;
80+
81+ // For each small multiple…
82+ function box ( g ) {
83+ g . each ( function ( d , i ) {
84+ d = d . map ( value ) . sort ( d3 . ascending ) ;
85+ var g = d3 . select ( this ) ,
86+ n = d . length ,
87+ min = d [ 0 ] ,
88+ max = d [ n - 1 ] ;
89+
90+ // Compute quartiles. Must return exactly 3 elements.
91+ var quartileData = d . quartiles = quartiles ( d ) ;
92+
93+ // Compute whiskers. Must return exactly 2 elements, or null.
94+ var whiskerIndices = whiskers && whiskers . call ( this , d , i ) ,
95+ whiskerData = whiskerIndices && whiskerIndices . map ( function ( i ) { return d [ i ] ; } ) ;
96+
97+ // Compute outliers. If no whiskers are specified, all data are "outliers".
98+ // We compute the outliers as indices, so that we can join across transitions!
99+ var outlierIndices = whiskerIndices
100+ ? d3 . range ( 0 , whiskerIndices [ 0 ] ) . concat ( d3 . range ( whiskerIndices [ 1 ] + 1 , n ) )
101+ : d3 . range ( n ) ;
102+
103+ // Compute the new x-scale.
104+ var x1 = d3 . scale . linear ( )
105+ . domain ( domain && domain . call ( this , d , i ) || [ min , max ] )
106+ . range ( [ height , 0 ] ) ;
107+
108+ // Retrieve the old x-scale, if this is an update.
109+ var x0 = this . __chart__ || d3 . scale . linear ( )
110+ . domain ( [ 0 , Infinity ] )
111+ . range ( x1 . range ( ) ) ;
112+
113+ // Stash the new scale.
114+ this . __chart__ = x1 ;
115+
116+ // Note: the box, median, and box tick elements are fixed in number,
117+ // so we only have to handle enter and update. In contrast, the outliers
118+ // and other elements are variable, so we need to exit them! Variable
119+ // elements also fade in and out.
120+
121+ // Update center line: the vertical line spanning the whiskers.
122+ var center = g . selectAll ( "line.center" )
123+ . data ( whiskerData ? [ whiskerData ] : [ ] ) ;
124+
125+ center . enter ( ) . insert ( "svg:line" , "rect" )
126+ . attr ( "class" , "center" )
127+ . attr ( "x1" , width / 2 )
128+ . attr ( "y1" , function ( d ) { return x0 ( d [ 0 ] ) ; } )
129+ . attr ( "x2" , width / 2 )
130+ . attr ( "y2" , function ( d ) { return x0 ( d [ 1 ] ) ; } )
131+ . style ( "opacity" , 1e-6 )
132+ . transition ( )
133+ . duration ( duration )
134+ . style ( "opacity" , 1 )
135+ . attr ( "y1" , function ( d ) { return x1 ( d [ 0 ] ) ; } )
136+ . attr ( "y2" , function ( d ) { return x1 ( d [ 1 ] ) ; } ) ;
137+
138+ center . transition ( )
139+ . duration ( duration )
140+ . style ( "opacity" , 1 )
141+ . attr ( "y1" , function ( d ) { return x1 ( d [ 0 ] ) ; } )
142+ . attr ( "y2" , function ( d ) { return x1 ( d [ 1 ] ) ; } ) ;
143+
144+ center . exit ( ) . transition ( )
145+ . duration ( duration )
146+ . style ( "opacity" , 1e-6 )
147+ . attr ( "y1" , function ( d ) { return x1 ( d [ 0 ] ) ; } )
148+ . attr ( "y2" , function ( d ) { return x1 ( d [ 1 ] ) ; } )
149+ . remove ( ) ;
150+
151+ // Update innerquartile box.
152+ var box = g . selectAll ( "rect.box" )
153+ . data ( [ quartileData ] ) ;
154+
155+ box . enter ( ) . append ( "svg:rect" )
156+ . attr ( "class" , "box" )
157+ . attr ( "x" , 0 )
158+ . attr ( "y" , function ( d ) { return x0 ( d [ 2 ] ) ; } )
159+ . attr ( "width" , width )
160+ . attr ( "height" , function ( d ) { return x0 ( d [ 0 ] ) - x0 ( d [ 2 ] ) ; } )
161+ . transition ( )
162+ . duration ( duration )
163+ . attr ( "y" , function ( d ) { return x1 ( d [ 2 ] ) ; } )
164+ . attr ( "height" , function ( d ) { return x1 ( d [ 0 ] ) - x1 ( d [ 2 ] ) ; } ) ;
165+
166+ box . transition ( )
167+ . duration ( duration )
168+ . attr ( "y" , function ( d ) { return x1 ( d [ 2 ] ) ; } )
169+ . attr ( "height" , function ( d ) { return x1 ( d [ 0 ] ) - x1 ( d [ 2 ] ) ; } ) ;
170+
171+ // Update median line.
172+ var medianLine = g . selectAll ( "line.median" )
173+ . data ( [ quartileData [ 1 ] ] ) ;
174+
175+ medianLine . enter ( ) . append ( "svg:line" )
176+ . attr ( "class" , "median" )
177+ . attr ( "x1" , 0 )
178+ . attr ( "y1" , x0 )
179+ . attr ( "x2" , width )
180+ . attr ( "y2" , x0 )
181+ . transition ( )
182+ . duration ( duration )
183+ . attr ( "y1" , x1 )
184+ . attr ( "y2" , x1 ) ;
185+
186+ medianLine . transition ( )
187+ . duration ( duration )
188+ . attr ( "y1" , x1 )
189+ . attr ( "y2" , x1 ) ;
190+
191+ // Update whiskers.
192+ var whisker = g . selectAll ( "line.whisker" )
193+ . data ( whiskerData || [ ] ) ;
194+
195+ whisker . enter ( ) . insert ( "svg:line" , "circle, text" )
196+ . attr ( "class" , "whisker" )
197+ . attr ( "x1" , 0 )
198+ . attr ( "y1" , x0 )
199+ . attr ( "x2" , width )
200+ . attr ( "y2" , x0 )
201+ . style ( "opacity" , 1e-6 )
202+ . transition ( )
203+ . duration ( duration )
204+ . attr ( "y1" , x1 )
205+ . attr ( "y2" , x1 )
206+ . style ( "opacity" , 1 ) ;
207+
208+ whisker . transition ( )
209+ . duration ( duration )
210+ . attr ( "y1" , x1 )
211+ . attr ( "y2" , x1 )
212+ . style ( "opacity" , 1 ) ;
213+
214+ whisker . exit ( ) . transition ( )
215+ . duration ( duration )
216+ . attr ( "y1" , x1 )
217+ . attr ( "y2" , x1 )
218+ . style ( "opacity" , 1e-6 )
219+ . remove ( ) ;
220+
221+ // Update outliers.
222+ var outlier = g . selectAll ( "circle.outlier" )
223+ . data ( outlierIndices , Number ) ;
224+
225+ outlier . enter ( ) . insert ( "svg:circle" , "text" )
226+ . attr ( "class" , "outlier" )
227+ . attr ( "r" , 5 )
228+ . attr ( "cx" , width / 2 )
229+ . attr ( "cy" , function ( i ) { return x0 ( d [ i ] ) ; } )
230+ . style ( "opacity" , 1e-6 )
231+ . transition ( )
232+ . duration ( duration )
233+ . attr ( "cy" , function ( i ) { return x1 ( d [ i ] ) ; } )
234+ . style ( "opacity" , 1 ) ;
235+
236+ outlier . transition ( )
237+ . duration ( duration )
238+ . attr ( "cy" , function ( i ) { return x1 ( d [ i ] ) ; } )
239+ . style ( "opacity" , 1 ) ;
240+
241+ outlier . exit ( ) . transition ( )
242+ . duration ( duration )
243+ . attr ( "cy" , function ( i ) { return x1 ( d [ i ] ) ; } )
244+ . style ( "opacity" , 1e-6 )
245+ . remove ( ) ;
246+
247+ // Compute the tick format.
248+ var format = tickFormat || x1 . tickFormat ( 8 ) ;
249+
250+ // Update box ticks.
251+ var boxTick = g . selectAll ( "text.box" )
252+ . data ( quartileData ) ;
253+
254+ boxTick . enter ( ) . append ( "svg:text" )
255+ . attr ( "class" , "box" )
256+ . attr ( "dy" , ".3em" )
257+ . attr ( "dx" , function ( d , i ) { return i & 1 ? 6 : - 6 } )
258+ . attr ( "x" , function ( d , i ) { return i & 1 ? width : 0 } )
259+ . attr ( "y" , x0 )
260+ . attr ( "text-anchor" , function ( d , i ) { return i & 1 ? "start" : "end" ; } )
261+ . text ( format )
262+ . transition ( )
263+ . duration ( duration )
264+ . attr ( "y" , x1 ) ;
265+
266+ boxTick . transition ( )
267+ . duration ( duration )
268+ . text ( format )
269+ . attr ( "y" , x1 ) ;
270+
271+ // Update whisker ticks. These are handled separately from the box
272+ // ticks because they may or may not exist, and we want don't want
273+ // to join box ticks pre-transition with whisker ticks post-.
274+ var whiskerTick = g . selectAll ( "text.whisker" )
275+ . data ( whiskerData || [ ] ) ;
276+
277+ whiskerTick . enter ( ) . append ( "svg:text" )
278+ . attr ( "class" , "whisker" )
279+ . attr ( "dy" , ".3em" )
280+ . attr ( "dx" , 6 )
281+ . attr ( "x" , width )
282+ . attr ( "y" , x0 )
283+ . text ( format )
284+ . style ( "opacity" , 1e-6 )
285+ . transition ( )
286+ . duration ( duration )
287+ . attr ( "y" , x1 )
288+ . style ( "opacity" , 1 ) ;
289+
290+ whiskerTick . transition ( )
291+ . duration ( duration )
292+ . text ( format )
293+ . attr ( "y" , x1 )
294+ . style ( "opacity" , 1 ) ;
295+
296+ whiskerTick . exit ( ) . transition ( )
297+ . duration ( duration )
298+ . attr ( "y" , x1 )
299+ . style ( "opacity" , 1e-6 )
300+ . remove ( ) ;
301+ } ) ;
302+ d3 . timer . flush ( ) ;
303+ }
304+
305+ box . width = function ( x ) {
306+ if ( ! arguments . length ) return width ;
307+ width = x ;
308+ return box ;
309+ } ;
310+
311+ box . height = function ( x ) {
312+ if ( ! arguments . length ) return height ;
313+ height = x ;
314+ return box ;
315+ } ;
316+
317+ box . tickFormat = function ( x ) {
318+ if ( ! arguments . length ) return tickFormat ;
319+ tickFormat = x ;
320+ return box ;
321+ } ;
322+
323+ box . duration = function ( x ) {
324+ if ( ! arguments . length ) return duration ;
325+ duration = x ;
326+ return box ;
327+ } ;
328+
329+ box . domain = function ( x ) {
330+ if ( ! arguments . length ) return domain ;
331+ domain = x == null ? x : d3 . functor ( x ) ;
332+ return box ;
333+ } ;
334+
335+ box . value = function ( x ) {
336+ if ( ! arguments . length ) return value ;
337+ value = x ;
338+ return box ;
339+ } ;
340+
341+ box . whiskers = function ( x ) {
342+ if ( ! arguments . length ) return whiskers ;
343+ whiskers = x ;
344+ return box ;
345+ } ;
346+
347+ box . quartiles = function ( x ) {
348+ if ( ! arguments . length ) return quartiles ;
349+ quartiles = x ;
350+ return box ;
351+ } ;
352+
353+ return box ;
354+ } ;
355+
356+ function boxWhiskers ( d ) {
357+ return [ 0 , d . length - 1 ] ;
358+ }
359+
360+ function boxQuartiles ( d ) {
361+ return [
362+ d3 . quantile ( d , .25 ) ,
363+ d3 . quantile ( d , .5 ) ,
364+ d3 . quantile ( d , .75 )
365+ ] ;
366+ }
0 commit comments