!(async function(){ var data = await util.getFile('mnist_train.csv') data.forEach(d => { delete d[''] d.i = +d.i }) var sel = d3.select('.umap-digit').html('') .at({role: 'graphics-document', 'aria-label': `Color coded UMAP of MNIST 1s showing that increasing privacy will misclassify slanted and serif “1” digits first.`}) var umapSel = sel.append('div') .append('div.chart-title').text('Sensitivity to higher privacy levels →') .parent() .st({maxWidth: 600, margin: '0 auto', marginBottom: 10}) .append('div') var buttonSel = sel.append('div.digit-button-container') .appendMany('div.button', d3.range(10)) .text(d => d) .on('click', d => drawDigitUmap(d)) drawDigitUmap(1) async function drawDigitUmap(digit){ buttonSel.classed('active', d => d == digit) // var umap = await util.getFile(`umap_train_${digit}.npy`) var umap = await util.getFile(`cns-cache/umap_train_784_${digit}.npy`) util.getFile(`cns-cache/mnist_train_raw_${digit}.npy`) var digitData = data .filter(d => d.y == digit) .map((d, i) => ({ rawPos: [umap.data[i*2 + 0], umap.data[i*2 + 1]], priv_order: d.priv_order, y: d.y, i: d.i })) var c = d3.conventions({ sel: umapSel.html(''), width: 600, height: 600, layers: 'sdc', margin: {top: 45} }) var nTicks = 200 c.svg.appendMany('rect', d3.range(nTicks)) .at({ height: 15, width: 1, fill: i => d3.interpolatePlasma(i/nTicks), }) .translate(i => [c.width/2 - nTicks/2 - 20 + i, -c.margin.top + 5]) c.x.domain(d3.extent(digitData, d => d.rawPos[0])) c.y.domain(d3.extent(digitData, d => d.rawPos[1]))//.range([0, c.height]) digitData.forEach(d => d.pos = [c.x(d.rawPos[0]), c.y(d.rawPos[1])]) c.sel.select('canvas').st({pointerEvents: 'none'}) var divSel = c.layers[1].st({pointerEvents: 'none'}) var ctx = c.layers[2] digitData.forEach(d => { ctx.beginPath() ctx.fillStyle = d3.interpolatePlasma(1 - d.priv_order/60000) ctx.rect(d.pos[0], d.pos[1], 2, 2) ctx.fill() }) var p = 10 c.svg .append('rect').at({width: c.width + p*2, height: c.height + p*2, x: -p, y: -p}) .parent() .call(d3.attachTooltip) .on('mousemove', function(){ var [px, py] = d3.mouse(this) var minPoint = _.minBy(digitData, d => { var dx = d.pos[0] - px var dy = d.pos[1] - py return dx*dx + dy*dy }) var s = 4 var c = d3.conventions({ sel: ttSel.html('').append('div'), width: 4*28, height: 4*28, layers: 'cs', margin: {top: 0, left: 0, right: 0, bottom: 0} }) //
Label: ${minPoint.y}
// ttSel.append('div').html(` //
Privacy Rank ${d3.format(',')(minPoint.priv_order)}
// `) ttSel.classed('tooltip-footnote', 0).st({width: 112}) util.drawDigit(c.layers[0], +minPoint.i, s) }) if (digit == 1){ var circleDigits = [ {r: 40, index: 1188}, {r: 53, index: 18698}, {r: 40, index: 1662} ] circleDigits.forEach(d => { d.pos = digitData.filter(e => e.priv_order == d.index)[0].pos }) c.svg.append('g') .appendMany('g', circleDigits) .translate(d => d.pos) .append('circle') .at({r: d => d.r, fill: 'none', stroke: '#fff', strokeDasharray: '2 3', strokeWidth: 1}) var {r, pos} = circleDigits[0] divSel .append('div').translate(pos) .append('div').translate([r + 20, -r + 10]) .st({width: 150, fontWeight: 300, fontSize: 14, color: '#fff', xbackground: 'rgba(255,0,0,.2)', lineHeight: '1.2em'}) .text('Increasing privacy will misclassify slanted and serif “1” digits first') } } })()