| window.highlightColor = '#bf0bbf' |
|
|
| window.makeSliders = function(metrics, sets, c, selectSet, drawRow, onRender){ |
|
|
| var width = 180 |
| var height = 30 |
| var color = '#000' |
|
|
| var xScale = d3.scaleLinear().range([0, width]).domain([0, 1]) |
| .clamp(1) |
|
|
| var sliderSel = c.svg.appendMany('g', metrics) |
| .translate((d, i) => [-c.margin.left -10 , 130*i + 30]) |
| .on('click', function(d){ |
| d.target = xScale.invert(d3.mouse(this)[0]) |
| render() |
| }) |
| .classed('slider', true) |
| .st({cursor: 'pointer'}) |
|
|
| var textSel = sliderSel.append('text.slider-label-container') |
| .at({y: -20, fontWeight: 500, textAnchor: 'middle', x: 180/2}) |
|
|
| sliderSel.append('rect') |
| .at({width, height, y: -height/2, fill: 'rgba(0,0,0,0)'}) |
|
|
| sliderSel.append('path').at({ |
| d: `M 0 -.5 H ${width}`, |
| stroke: color, |
| strokeWidth: 1 |
| }) |
|
|
| var leftPathSel = sliderSel.append('path').at({ |
| d: `M 0 -.5 H ${width}`, |
| stroke: color, |
| strokeWidth: 3 |
| }) |
|
|
| var drag = d3.drag() |
| .on('drag', function(d){ |
| var x = d3.mouse(this)[0] |
| d.target = xScale.invert(x) |
| render() |
| }) |
|
|
| var circleSel = sliderSel.append('circle').call(drag) |
| .at({r: 7, stroke: '#000'}) |
|
|
|
|
| var exSel = c.svg.append('g').translate([-c.margin.left -10, 400]) |
| .st({fontSize: 13}) |
|
|
| var curY = 0 |
| exSel.append('g') |
| .append('text').text('The selected set is...') |
|
|
| var selectedSetG = exSel.append('g.selected').translate([-10, curY += 15]) |
| .datum(sets[0]) |
| .call(drawRow) |
|
|
| selectedSetG.select('.no-stroke').classed('selected', 1) |
|
|
| curY += 25 |
| var exMetrics = exSel.appendMany('g', metrics) |
| .translate(() => curY +=22, 1) |
| .append('text').html(d => '10% small, 10% more than target') |
|
|
| curY += 10 |
| var exMeanDiff = exSel.append('text').translate(() => curY +=22, 1) |
| .at({textAnchor: 'end', x: 190}) |
| var exMaxDiff = exSel.append('text').translate(() => curY +=22, 1) |
| .at({textAnchor: 'end', x: 190}) |
|
|
|
|
| |
| sliderSel.each(function(metric){ |
| var countKey = metric.key + '_count' |
| sets.forEach(set => { |
| var v = d3.sum(set, d => d[metric.field] == metric.key) |
| set[countKey] = v / set.length |
| }) |
|
|
| var byCountKey = d3.nestBy(sets, d => d[countKey]) |
|
|
| d3.range(.1, 1, .1).forEach(i => { |
| if (byCountKey.some(d => d.key*100 == Math.round(i*100))) return |
|
|
| var rv = [] |
| rv.key = i |
| byCountKey.push(rv) |
| }) |
|
|
| byCountKey.forEach(d => { |
| d.metric = metric |
| d.key = +d.key |
| }) |
|
|
| var countSel = d3.select(this).append('g.histogram').lower() |
| .translate(30, 1) |
| .appendMany('g', byCountKey) |
| .translate(d => xScale.clamp(0)(d.key - .05), 0) |
| xScale.clamp(1) |
|
|
| countSel.append('text') |
| |
| .at({fontSize: 11, opacity: .7, y: -8, textAnchor: 'middle', x: 9.5}) |
| .text(d => d.key*100) |
|
|
| countSel.append('path') |
| .at({d: 'M 9.5 -18 V -30', stroke: '#ccc'}) |
|
|
| countSel |
| .appendMany('rect.histogram-set', d => d) |
| .at({width: 16, height: 4, x: 1.5, y: (d, i) => i*6}) |
| |
| }) |
| var histogramSetSel = sliderSel.selectAll('rect.histogram-set') |
| .st({cursor: 'default'}) |
|
|
| var axisSel = sliderSel.selectAll('.histogram text') |
|
|
|
|
| var pinkSel = sliderSel.append('g') |
| .at({r: 4, fill: highlightColor}) |
| .st({pointerEvents: 'none', opacity:0}) |
| pinkSel.append('path').at({stroke: highlightColor, d: 'M .5 0 V 15'}) |
| pinkSel.append('text').at({y: 30, textAnchor: 'middle'}) |
| pinkSel.append('text.score').at({y: 50, textAnchor: 'middle'}) |
|
|
|
|
| function render(){ |
| circleSel.at({cx: d => xScale(d.target)}) |
| |
| textSel.text(d => (d.str + ' Target: ').replace('s ', ' ') + pctFmt(d.target)) |
|
|
| axisSel |
| .classed('selected', false) |
| |
| |
|
|
| |
| |
| |
| |
|
|
| |
| |
|
|
| leftPathSel.at({d: d => `M 0 -.5 H ${xScale(d.target)}`}) |
| metrics.forEach(d => { |
| d.scoreScale = d3.scaleLinear() |
| .domain([-.1, d.target, 1.1]) |
| .range([0, 1, 0]) |
| }) |
| histogramSetSel.st({fill: d => d === sets.selected ? highlightColor: '#bbb'}) |
|
|
| if (onRender) onRender() |
|
|
| var shapes = sets.selected |
|
|
| var metricVals = metrics.map(m => { |
| return d3.sum(shapes, (d, i) => shapes[i][m.field] == m.key)/shapes.length |
| }) |
|
|
| pinkSel.translate((d, i) => xScale(metricVals[i]), 0) |
| pinkSel.select('text').text((d, i) => pctFmt(metricVals[i])) |
| pinkSel.select('.score').text((d, i) => 'Difference: ' + Math.round(shapes.score[i]*100)) |
|
|
|
|
| selectedSetG.html('') |
| .datum(sets.selected) |
| .call(drawRow) |
|
|
| selectedSetG.select('.no-stroke').classed('selected', 1) |
|
|
| exMetrics |
| .html((d, i) => { |
| var target = d.target |
| var actual = sets.selected[d.key + '_count'] |
| var diff = sets.selected.score[i] |
|
|
| var str = d.str.replace('ls', 'l').replace('ns', 'n').toLowerCase() |
|
|
| return ` |
| ${pctFmt(actual)} |
| ${str}, |
| ${pctFmt(diff)} |
| ${actual < target ? 'less' : 'more'} than target |
| ` |
| }) |
| .at({textAnchor: 'end', x: 190}) |
|
|
| exMeanDiff |
| .text('Mean Difference: ' + d3.format('.2%')(sets.selected['Utilitarian']/100)) |
|
|
| exMaxDiff |
| .text('Max Difference: ' + measures[1].ppFn(sets.selected['score']).replace('%', '.00%')) |
|
|
| } |
|
|
| return {render} |
| } |
|
|
|
|
| |
| |
|
|