function setupBandline(tsers) { var contextValuesSorted = [].concat.apply([], R.flatten(tsers.map(R.prop('contextValue'))).map(R.prop('value'))).sort(d3.ascending) var bandThresholds = [ d3.min(contextValuesSorted), d3.min(contextValuesSorted), d3.quantile(contextValuesSorted, 1/4), d3.quantile(contextValuesSorted, 2/4), d3.quantile(contextValuesSorted, 3/4), d3.max(contextValuesSorted), d3.max(contextValuesSorted) ] var outlierClassifications = ['lowOutlier', 'normal', 'highOutlier'] function makeOutlierScale(sortedValues) { var iqrDistanceMultiplier = 1.5 // As per Stephen Few's specification var iqr = [d3.quantile(sortedValues, 0.25), d3.quantile(sortedValues, 0.75)] var midspread = iqr[1] - iqr[0] return d3.scale.threshold() .domain([ iqr[0] - iqrDistanceMultiplier * midspread, iqr[1] + iqrDistanceMultiplier * midspread ]) .range(outlierClassifications) } function medianLineBand(sortedValues) { // The median line is approximated as a band of 0 extent (CSS styling is via 'stroke'). // This 'band' is to be tacked on last so it isn't occluded by other bands // (SVG uses the painter's algorithm for Z ordering). var median = d3.median(sortedValues) return [median, median] } var timestamps = R.flatten(tsers.map(value)).map(key) var temporalDomain = [d3.min(timestamps), d3.max(timestamps)] // Setting up the bandLine with the domain dependent values only (FP curry style applied on // 'functional objects'). This helps decouple the Model and the viewModel (MVC-like principle). return bandLine() .bands(window2(bandThresholds).concat([medianLineBand(contextValuesSorted)])) .valueAccessor(R.prop('value')) .contextValueAccessor(R.prop('contextValue')) .pointStyleAccessor(makeOutlierScale(contextValuesSorted)) .xScaleOfBandLine(d3.scale.linear().domain(temporalDomain)) .xScaleOfSparkStrip(d3.scale.linear().domain(d3.extent(bandThresholds))) .rScaleOfBandLine(d3.scale.ordinal().domain(outlierClassifications)) }