Skip to content

Instantly share code, notes, and snippets.

@russellsamora
Last active November 8, 2017 19:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save russellsamora/aba5f81a8bd0633a399d692289ab97eb to your computer and use it in GitHub Desktop.
Save russellsamora/aba5f81a8bd0633a399d692289ab97eb to your computer and use it in GitHub Desktop.
Weighted pivot scatter plot v3
license: mit
height: 500
border: no
*{box-sizing:border-box}body{color:#333;font-family:Helvetica,Arial,sans-serif}.graphic__hed{text-align:center;color:#333}.chart{max-width:40rem;margin:0 auto;text-align:center}.slider{max-width:20rem;margin:1rem auto;position:relative;padding-top:16.5px}.slider input{display:block;width:100%}.slider:before{content:'Quality';text-transform:uppercase;font-size:11px;letter-spacing:.05em;color:#888;display:inline-block;position:absolute;top:0;left:0}.slider:after{content:'Quantity';text-transform:uppercase;font-size:11px;letter-spacing:.05em;color:#888;display:inline-block;position:absolute;top:0;right:0}.g-axis line,.g-axis path{stroke:#ccc}.g-axis text{fill:#888}.g-axis .axis__label{text-transform:uppercase;font-size:11px;letter-spacing:.05em;fill:#888}.item{fill-opacity:.75}
"use strict";function weightData(t){var a=t.x,e=t.y;return dummyData.map(function(t){return _extends({},t,{score:t.x*a+t.y*e})}).sort(function(t,a){return d3.descending(t.score,a.score)}).map(function(t,a){return _extends({},t,{rank:a})}).reverse()}function getHypotenuse(t){var a=t.x,e=t.y,n=a*a,r=e*e;return Math.sqrt(n+r)}function resize(){var t=.8*Math.min(el.node().offsetWidth,window.innerHeight);chart.width(t).height(t),el.call(chart)}function scatterplot(){function t(t,a){return"translate("+t+", "+a+")"}function a(t){var a=t.container,e=t.data,n=a.selectAll("svg").data([e]),r=n.enter().append("svg"),i=r.append("g");i.append("g").attr("class","g-plot");var c=i.append("g").attr("class","g-axis"),s=c.append("g").attr("class","axis axis--x"),o=c.append("g").attr("class","axis axis--y");s.append("text").attr("class","axis__label").attr("text-anchor","start").text("Quantity"),o.append("text").attr("class","axis__label").attr("text-anchor","end").text("Quality")}function e(t){t.container,t.data}function n(t){var a=t.data;v=getHypotenuse({x:p,y:m});var e=p/v*g,n=m/v*x,r=Math.floor(1.5*FONT_SIZE);o.domain([0,MAX_VAL]).range([0,e]),l.domain([0,MAX_VAL]).range([n,0]),d.domain([0,a.length]).range([r,2]),u.domain(a.map(function(t){return t.rank})).range(COLORS)}function r(a){var e=a.container,n=(a.data,e.select("svg"));n.attr("width",h).attr("height",f);var r=n.select("g"),i=l.range()[0],c=g/2,m=x-i,y=Math.acos(p/v),_=90-180*y/Math.PI,M="rotate("+-_+" 0 "+l.range()[0]+")",w=t(1.5*s+c,s+m),A=w+" "+M;r.attr("transform",A);var L=r.select(".g-plot"),O=L.selectAll(".item").data(function(t){return t},function(t){return t.index});O.enter().append("circle").attr("class","item").merge(O).attr("x",0).attr("y",0).attr("r",function(t){return d(t.rank)}).style("fill",function(t){return u(t.rank)}).style("stroke",function(t){return d3.color(u(t.rank)).darker(.7)}).attr("transform",function(a){return t(o(a.x),l(a.y))})}function i(a){var e=a.container,n=(a.data,e.select(".g-axis")),r=d3.axisLeft(l),i=d3.axisBottom(o);r.ticks(Math.max(0,Math.floor(m/10))),i.ticks(Math.max(0,Math.floor(p/10)));var c=n.select(".axis--x"),d=l.range()[0],u=d,h=Math.ceil(s/2);c.attr("transform",t(0,h+u)).call(i);var f=n.select(".axis--y");f.attr("transform",t(-h,0)).call(r),c.select(".axis__label").attr("y",s-1),f.select(".axis__label").attr("x",u).attr("y",s-1).attr("transform","rotate(90)")}function c(t){var c=t.datum();a({container:t,data:c}),e({container:t,data:c}),n({container:t,data:c}),r({container:t,data:c}),i({container:t,data:c})}var s=3*FONT_SIZE,o=d3.scaleLinear(),l=d3.scaleLinear(),d=d3.scaleSqrt(),u=d3.scaleQuantile(),h=0,f=0,g=0,x=0,p=50,m=50,v=0;return c.width=function(){return arguments.length?(h=arguments.length<=0?void 0:arguments[0],g=h-2.5*s,c):h},c.height=function(){return arguments.length?(f=arguments.length<=0?void 0:arguments[0],x=f-2.5*s,c):f},c.weight=function(t){var a=t.x,e=t.y;return p=a,m=e,c},c}function handleInput(){var t=+this.value,a=t,e=100-t,n=weightData({x:a,y:e});chart.weight({x:a,y:e}),el.datum(n),el.call(chart)}function init(){el.datum(weightData({x:50,y:50})),el.call(chart),resize(),window.addEventListener("resize",resize),graphic.select(".slider input").on("input",handleInput)}var _extends=Object.assign||function(t){for(var a=1;a<arguments.length;a++){var e=arguments[a];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])}return t},graphic=d3.select(".graphic--3"),COLORS=["#ff3814","#fe5c34","#fc764f","#f88d69","#f2a385","#e8b8a0","#dbcdbd"],FONT_SIZE=11,MAX_VAL=10,dummyData=d3.range(0,50).map(function(t){return{index:t,x:.5+Math.random()*(MAX_VAL-1),y:.5+Math.random()*(MAX_VAL-1)}}),chart=scatterplot(),el=graphic.select(".chart");init();
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJ3ZWlnaHREYXRhIiwiX3JlZiIsIngiLCJ5IiwiZHVtbXlEYXRhIiwibWFwIiwiZCIsIl9leHRlbmRzIiwic2NvcmUiLCJjaGFydCIsInNjYXR0ZXJwbG90Iiwic29ydCIsImEiLCJiIiwiZDMiLCJkZXNjZW5kaW5nIiwicmFuayIsImkiLCJyZXZlcnNlIiwiZ2V0SHlwb3RlbnVzZSIsIl9yZWYyIiwieTIiLCJNYXRoIiwic3FydCIsInJlc2l6ZSIsIm1pbiIsImVsIiwibm9kZSIsIm9mZnNldFdpZHRoIiwid2luZG93IiwiaW5uZXJIZWlnaHQiLCJ3aWR0aCIsInN6IiwiaGVpZ2h0IiwiY2FsbCIsInNjYWxlWCIsInNjYWxlTGluZWFyIiwiZW50ZXIiLCJfcmVmMyIsImNvbnRhaW5lciIsImRhdGEiLCJzZWxlY3RBbGwiLCJzdmciLCJhcHBlbmQiLCJjaGFydFdpZHRoIiwic3ZnRW50ZXIiLCJnRW50ZXIiLCJ3ZWlnaHRYIiwiYXR0ciIsImh5cG90ZW51c2UiLCJ0cmFuc2xhdGUiLCJheGlzIiwidGV4dCIsImV4aXQiLCJfcmVmNCIsIl9yZWY1Iiwid2VpZ2h0WSIsInJhbmdlWSIsImNoYXJ0SGVpZ2h0IiwiRk9OVF9TSVpFIiwiZG9tYWluIiwiTUFYX1ZBTCIsInJhbmdlIiwicmFuZ2VYIiwic2NhbGVZIiwic2NhbGVSIiwibWF4UiIsInNjYWxlQyIsInVwZGF0ZURvbSIsIl9yZWY2IiwiQ09MT1JTIiwibWF4WSIsIm9mZnNldFgiLCJhbmdsZSIsInJhZCIsIlBJIiwidHJhbnNsYXRpb24iLCJtYXJnaW4iLCJvZmZzZXRZIiwicm90YXRpb24iLCJnIiwidHJhbnNmb3JtIiwicGxvdCIsInNlbGVjdCIsImluZGV4IiwiaXRlbSIsIm1lcmdlIiwic3R5bGUiLCJjb2xvciIsImRhcmtlciIsInVwZGF0ZUF4aXMiLCJfcmVmNyIsImF4aXNMZWZ0IiwiYXhpc0JvdHRvbSIsInRpY2tzIiwibWF4IiwiZmxvb3IiLCJvZmZzZXQiLCJidWZmZXIiLCJjZWlsIiwieDIiLCJzY2FsZVF1YW50aWxlIiwiYXJndW1lbnRzIiwibGVuZ3RoIiwiZGF0dW0iLCJ1cGRhdGVTY2FsZXMiLCJoYW5kbGVJbnB1dCIsIndlaWdodCIsInRoaXMiLCJ2YWx1ZSIsInZhbCIsIndlaWdodGVkIiwiaW5pdCIsImdyYXBoaWMiLCJvbiIsInJhbmRvbSJdLCJtYXBwaW5ncyI6IllBSzRDLFNBQU5BLFlBQU1DLEdBQU4sR0FBQUMsR0FBQUQsRUFBQUMsRUFBQUMsRUFBQUYsRUFBQUUsQ0FBcEIsT0FBbEJDLFdBQUFDLElBQUEsU0FBQUMsR0FBQSxNQUFBQyxhQVdLRCxHQUxMRSxNQUFNQyxFQUFBQSxFQUFRQyxFQUFBQSxFQUFBQSxFQUFkUCxNQVFFUSxLQUFLLFNBQUNDLEVBQUdDLEdBQUosTUFBVUMsSUFBR0MsV0FBV0gsRUFBRUosTUFBT0ssRUFBRUwsU0FMMUNILElBQUEsU0FBQUMsRUFBU04sR0FBVCxNQUFBTyxhQUFzQkwsR0FBUWMsS0FBQUMsTUFDN0JDLFVBQXFCLFFBQUFDLGVBQUFDLEdBQUEsR0FBQWxCLEdBQUFrQixFQUFBbEIsRUFBQUMsRUFBQWlCLEVBQUFqQixFQUlwQlEsRUFBS1QsRUFBQUEsRUFBQW1CLEVBQWFOLEVBQUFBLENBQWIsT0FDRE8sTUFBQUMsS0FBQ2pCLEVBQURlLEdBQUEsUUFBQUcsVUFBQSxHQUlKTixHQVRELEdBQU9JLEtBQVBHLElBQUFDLEdBQUFDLE9BQUFDLFlBQUFDLE9BQUFDLFlBVUFyQixPQUFBc0IsTUFBQUMsR0FBQUMsT0FBQUQsR0FXQU4sR0FBR1EsS0FBS3pCLE9BVHdCLFFBQUFDLGVBY2hDLFFBQU15QixHQUFZQyxFQUFBQSxHQUNsQixNQUFBLGFBQWtCQSxFQUFsQixLQUFlakMsRUFBZixJQWdCQSxRQUFTa0MsR0FBVEMsR0FBb0MsR0FBbkJDLEdBQW1CRCxFQUFuQkMsVUFBV0MsRUFBUUYsRUFBUkUsS0FaeEJULEVBQUpRLEVBQUFFLFVBQUEsT0FBQUQsTUFBQUEsSUFDSVAsRUFBSlMsRUFBQUwsUUFBQU0sT0FBQSxPQUNJQyxFQUFKQyxFQUFBRixPQUFBLElBRUFHLEdBQUlDLE9BQVUsS0FBZEMsS0FBQSxRQUFBLFNBRUEsSUFBSUMsR0FBQUEsRUFBSk4sT0FBQSxLQUFBSyxLQUFBLFFBQUEsVUFFQTlDLEVBQVNnRCxFQUFBQSxPQUFULEtBQXlCRixLQUFBLFFBQUEsZ0JBRXhCN0MsRUFBQWdELEVBQUFSLE9BQUEsS0FBQUssS0FBQSxRQUFBLGVBRUQ5QyxHQUFBeUMsT0FBU04sUUFBVFcsS0FBb0MsUUFBQSxlQUFBQSxLQUFuQlQsY0FBQUEsU0FBbUJhLEtBQVJaLFlBQzNCckMsRUFBQXdDLE9BQU1ELFFBQU1ILEtBQVVFLFFBQVUsZUFDaENPLEtBQU1ILGNBQWVSLE9BQ2hCZSxLQUFNTixXQW1CWixRQUFTTyxHQUFUQyxHQUFtQ0EsRUFBbkJmLFVBQW1CZSxFQUFSZCxLQWIxQixRQUFVVyxHQUFWSSxHQUFnQyxHQUF0QmYsR0FBc0JlLEVBQXRCZixJQWlCVlMsR0FBYTlCLGVBQWdCakIsRUFBRzZDLEVBQVM1QyxFQUFHcUQsR0FmNUMsSUFBTXJELEdBQVN3QyxFQUFZSyxFQUFqQkosRUFpQkphLEVBQVNELEVBQVVQLEVBQWFTLEVBZnBDZixFQUFPckIsS0FBUTBCLE1BQWMsSUFBVFcsVUFJdEJ4RCxHQUdBeUQsUUFBQSxFQUFBQyxVQWFFQyxPQUFPLEVBQUdDLElBWHNCQyxFQUFBSixRQUFBLEVBQUFDLFVBQ2xDQyxPQUFBTCxFQUFBLElBRURRLEVBQWdDTCxRQUFBLEVBQUFwQixFQUFSQSxTQWdCckJzQixPQUFPSSxFQUFNLElBZGZDLEVBQ0FQLE9BQU1ILEVBQVNELElBQUFBLFNBQUFBLEdBQUFBLE1BQVVQLEdBQUFBLFFBQ3pCYSxNQUFNSSxRQW1CUCxRQUFTRSxHQUFUQyxHQUF3QyxHQUFuQjlCLEdBQW1COEIsRUFBbkI5QixVQWJwQnlCLEdBYXVDSyxFQUFSN0IsS0FaN0JvQixFQUFXQyxPQUNYQyxPQUVGRyxHQWFFakIsS0FBSyxRQUFTakIsR0FUaEJvQyxLQUNFUCxTQUFPcEIsRUFBUyxJQUNoQnNCLEdBQU1RLEVBQUFBLE9BRlIsS0FjTUMsRUFBT1AsRUFBT0YsUUFBUSxHQVQ3QlUsRUFBU0osRUFBK0IsRUFBbkI3QixFQUFtQm1CLEVBQW5CbkIsRUFBV0MsRUFBUWxCLEtBQVJrQixLQUFRTyxFQUFBRSxHQWFqQ3dCLEVBQVEsR0FBWSxJQUFOQyxFQUFZcEQsS0FBS3FELEdBWi9CakMsRUFBQUEsV0FBdUIrQixFQUF2Qi9CLE1BQU5zQixFQUFBRixRQUFBLEdBQU1wQixJQWNBa0MsRUFBYzFCLEVBQW1CLElBQVQyQixFQUFlTCxFQUFTSyxFQUFTQyxHQVg3RDlCLEVBQWNqQixFQUFkaUIsSUFDSytCLENBWVBDLEdBQUVoQyxLQUFLLFlBQWFpQyxFQUVwQixJQUFNQyxHQUFPRixFQUFFRyxPQUFPLFdBVGhCWCxFQUFBQSxFQUFVNUIsVUFBaEIsU0FBQUosS0FBQSxTQUFBbEMsR0FBQSxNQUFBQSxJQUFBLFNBQUFBLEdBQUEsTUFBQUEsR0FBQThFLE9BRUFDLEdBQU1YLFFBQU1wRCxPQUFVeUIsVUFDdEJDLEtBQU15QixRQUFRLFFBQ2RhLE1BQU1QLEdBQ04vQixLQUFNNEIsSUFBQUEsR0FDTjVCLEtBQU1pQyxJQUFBQSxHQUNKakMsS0FBSyxJQUFBLFNBQUExQyxHQUFBLE1BQUEyRCxHQUFhZ0IsRUFBQUEsUUFZbEJNLE1BQU0sT0FBUSxTQUFBakYsR0FBQSxNQUFLNkQsR0FBTzdELEVBQUVVLFFBVjlCdUUsTUFBTUwsU0FBU0MsU0FBQUEsR0FBQUEsTUFBT3JFLElBQUEwRSxNQUF0QnJCLEVBQUE3RCxFQUFBVSxPQUFBeUUsT0FBQSxNQVlFekMsS0FBSyxZQUFjLFNBQUExQyxHQUFBLE1BQUs0QyxHQUFVZixFQUFPN0IsRUFBRUosR0FBSThELEVBQU8xRCxFQUFFSCxNQVZoQixRQUFRdUYsR0FBUkMsR0FBUSxHQUFBcEQsR0FBQW9ELEVBQUFwRCxVQUFBWSxHQUFBd0MsRUFBQW5ELEtBQU80QyxFQUFQRCxPQUFBLFlBZ0I1Q1MsRUFBVzlFLEdBQUc4RSxTQUFTNUIsR0FkeEIzQixFQUFMdkIsR0FBb0IrRSxXQUNsQjdDLEVBSVU0QyxHQUNKRSxNQU5SeEUsS0FNZ0J5RSxJQUFBLEVBQUF6RSxLQUFBMEUsTUFBQXhDLEVBQUEsTUFBQXFDLEVBQUsxQixNQUFTbkQsS0FBZCtFLElBQUEsRUFBQXpFLEtBQUEwRSxNQUFBakQsRUFBQSxLQUFBLElBQ2R3QyxHQUFNcEMsRUFBQWdDLE9BQVUsWUFDaEJuQyxFQUFLZ0IsRUFSUEYsUUFRcUIsR0FBQW1DLEVBQUsvQyxFQUMxQmdELEVBQUE1RSxLQUFBNkUsS0FBQXRCLEVBQUEsRUFnQkEzRSxHQUFFOEMsS0FBSyxZQUFhRSxFQUFVLEVBQUdnRCxFQUFTRCxJQWQzQy9ELEtBQUEyRCxFQUF5QyxJQUFSckQsR0FBUVcsRUFBQWdDLE9BQUEsV0FDeENoRixHQUFBNkMsS0FBTUcsWUFBT1osR0FBaUIyRCxFQUE5QixJQW1CRWhFLEtBQUswRCxHQWhCUDFGLEVBQUFpRixPQUFNVSxnQkFtQko3QyxLQUFLLElBQUs2QixFQUFTLEdBaEJyQmdCLEVBQUFBLE9BQUFBLGdCQUNBN0MsS0FBTTlDLElBQUlpRCxHQW1CUkgsS0FBSyxJQUFLNkIsRUFBUyxHQWpCckI3QixLQUFNdUIsWUFITnNCLGNBTUEsUUFBTUssR0FBUzVFLEdBQ2ZwQixHQUFBc0MsR0FBT0QsRUFBYVcsT0FHcEJiLElBQVVjLFVBQUFBLEVBQUFYLEtBQUFBLElBa0JWYSxHQUFPZCxVQUFBQSxFQUFXQyxLQUFBQSxJQWhCbEJyQyxHQUFPb0MsVUFBQUEsRUFBYVcsS0FBQUEsSUFrQnBCa0IsR0FBWTdCLFVBQUFBLEVBQVdDLEtBQUFBLElBZnZCdEMsR0FBU3FDLFVBQUFBLEVBQVRDLEtBQUFBLElBMUhELEdBQU1xQyxHQUFxQixFQUFabEIsVUFaVHlDLEVBQU50RixHQUFBc0IsY0FDTWYsRUFBTlAsR0FBQXNCLGNBQ0E2QixFQUFZMUMsR0FBSzZFLFlBQ2pCakMsRUFBQXJELEdBQUF1RixnQkFFRHRFLEVBQVNQLEVBQ0ZRLEVBQUtWLEVBQ1hiLEVBQWdCd0IsRUFDYkMsRUFBSCxFQUNBYSxFQUFBLEdBY0lTLEVBQVUsR0FaZlAsRUFBU3ZDLENBd0pQZ0QsT0F0QkFqRCxHQUFBc0IsTUFBQSxXQWFBLE1BQUt1RSxXQUFLQyxRQVhYeEUsRUFBQUEsVUFBQUEsUUFBQUEsRUFBQUEsT0FBQUEsVUFBQUEsR0FDQ2EsRUFBYUwsRUFBYixJQUF1QmlFLEVBYWhCL0YsR0FIa0JzQixHQU56QjBFLEVBQUFBLE9BQWEsV0FDYnJDLE1BQUFBLFdBQVVtQyxRQUNWYixFQUFBQSxVQUFBQSxRQUFBQSxFQUFBQSxPQUFBQSxVQUFBQSxHQUNBaEMsRUFBQXpCLEVBQUEsSUFBQTRDLEVBYU9wRSxHQWZLOEIsR0FNWlIsRUFBQUEsT0FBQUEsU0FBQUEsR0FBQUEsR0FBQUEsR0FBQUEsRUFBQUEsRUFBQUEsRUFBQUEsRUFBQUEsQ0FGRCxPQUdDYSxHQUFBQSxFQUNBWSxFQUFPL0MsRUFKUkEsR0FVQ2lELEVBZUYsUUFBU2dELGVBWFJqRyxHQUFNa0csSUFBTkMsS0FBZUMsTUFBbUIzRyxFQUFBNEcsRUFBQTNHLEVBQUEsSUFBQTJHLEVBZTVCQyxFQUFXL0csWUFBYUUsRUFBQUEsRUFBR0MsRUFBQUEsR0FiaENxRCxPQUFBQSxRQUFBdEQsRUFBQUEsRUFBQUMsRUFBQUEsSUFDQXVCLEdBQUE4RSxNQUFPL0YsR0FDUGlCLEdBSkRRLEtBQUF6QixPQVNBLFFBQUF1RyxRQWNBdEYsR0FBRzhFLE1BQU14RyxZQUFhRSxFQUFHLEdBQUlDLEVBQUcsTUFaakN1QixHQUFBUSxLQUFTd0UsT0FDUmxGLFNBQ0FLLE9BQU0zQixpQkFBTixTQUFBc0IsUUFDQXlGLFFBQVU5QixPQUFNMkIsaUJBQWhCSSxHQUFBLFFBQUFSLDRMQWpOS08sUUFBVW5HLEdBQUdxRSxPQUFPLGVBRXBCYixRQUFVLFVBQVcsVUFBVyxVQUFXLFVBQVcsVUFBVyxVQUFXLFdBQzVFWCxVQUFZLEdBSFpzRCxRQUFVbkcsR0FLVlYsVUFBWVUsR0FBR2dELE1BQU0sRUFBRyxJQUFJekQsSUFBSSxTQUFBQyxHQUFBLE9BSHRDOEUsTUFBTWQsRUFDTnBFLEVBQU15RCxHQUFBQSxLQUFBQSxVQUFORSxRQUFBLEdBQ0ExRCxFQUFNMEQsR0FBQUEsS0FBVXNELFVBQWhCdEQsUUFBQSxNQUVDdUIsTUFEMkMxRSxjQUV4Q2dCLEdBQU1KLFFBQUs2RixPQUFMLFNBNk1UMUciLCJmaWxlIjoic2NyaXB0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgZ3JhcGhpYyA9IGQzLnNlbGVjdCgnLmdyYXBoaWMtLTMnKVxuXG5jb25zdCBDT0xPUlMgPSBbJyNmZjM4MTQnLCAnI2ZlNWMzNCcsICcjZmM3NjRmJywgJyNmODhkNjknLCAnI2YyYTM4NScsICcjZThiOGEwJywgJyNkYmNkYmQnXVxuY29uc3QgRk9OVF9TSVpFID0gMTFcbmNvbnN0IE1BWF9WQUwgPSAxMFxuY29uc3QgZHVtbXlEYXRhID0gZDMucmFuZ2UoMCwgNTApLm1hcChkID0+ICh7XG5cdGluZGV4OiBkLFxuXHR4OiAwLjUgKyBNYXRoLnJhbmRvbSgpICogKE1BWF9WQUwgLSAxKSxcblx0eTogMC41ICsgTWF0aC5yYW5kb20oKSAqIChNQVhfVkFMIC0gMSksXG59KSlcblxuY29uc3QgY2hhcnQgPSBzY2F0dGVycGxvdCgpXG5jb25zdCBlbCA9IGdyYXBoaWMuc2VsZWN0KCcuY2hhcnQnKVxuXG5mdW5jdGlvbiB3ZWlnaHREYXRhKHsgeCwgeSB9KSB7XG5cdHJldHVybiBkdW1teURhdGEubWFwKGQgPT4gKHtcblx0XHQuLi5kLFxuXHRcdHNjb3JlOiBkLnggKiB4ICsgZC55ICogeSxcblx0fSkpXG5cdC5zb3J0KChhLCBiKSA9PiBkMy5kZXNjZW5kaW5nKGEuc2NvcmUsIGIuc2NvcmUpKVxuXHQubWFwKChkLCBpKSA9PiAoe1xuXHRcdC4uLmQsXG5cdFx0cmFuazogaSxcblx0fSkpXG5cdC5yZXZlcnNlKClcbn1cblxuZnVuY3Rpb24gZ2V0SHlwb3RlbnVzZSh7IHgsIHkgfSkge1xuXHRjb25zdCB4MiA9IHggKiB4XG5cdGNvbnN0IHkyID0geSAqIHlcblx0cmV0dXJuIE1hdGguc3FydCh4MiArIHkyKVxufVxuXG5mdW5jdGlvbiByZXNpemUoKSB7XG5cdGNvbnN0IHN6ID0gTWF0aC5taW4oZWwubm9kZSgpLm9mZnNldFdpZHRoLCB3aW5kb3cuaW5uZXJIZWlnaHQpICogMC44XG5cdGNoYXJ0LndpZHRoKHN6KS5oZWlnaHQoc3opXG5cdGVsLmNhbGwoY2hhcnQpXG59XG5cbmZ1bmN0aW9uIHNjYXR0ZXJwbG90KCkge1xuXHRjb25zdCBtYXJnaW4gPSBGT05UX1NJWkUgKiAzXG5cdGNvbnN0IHNjYWxlWCA9IGQzLnNjYWxlTGluZWFyKClcblx0Y29uc3Qgc2NhbGVZID0gZDMuc2NhbGVMaW5lYXIoKVxuXHRjb25zdCBzY2FsZVIgPSBkMy5zY2FsZVNxcnQoKVxuXHRjb25zdCBzY2FsZUMgPSBkMy5zY2FsZVF1YW50aWxlKClcblxuXHRsZXQgd2lkdGggPSAwXG5cdGxldCBoZWlnaHQgPSAwXG5cdGxldCBjaGFydFdpZHRoID0gMFxuXHRsZXQgY2hhcnRIZWlnaHQgPSAwXG5cdGxldCB3ZWlnaHRYID0gNTBcblx0bGV0IHdlaWdodFkgPSA1MFxuXHRsZXQgaHlwb3RlbnVzZSA9IDBcblxuXHRmdW5jdGlvbiB0cmFuc2xhdGUoeCwgeSkge1xuXHRcdHJldHVybiBgdHJhbnNsYXRlKCR7eH0sICR7eX0pYFxuXHR9XG5cblx0ZnVuY3Rpb24gZW50ZXIoeyBjb250YWluZXIsIGRhdGEgfSkge1xuXHRcdGNvbnN0IHN2ZyA9IGNvbnRhaW5lci5zZWxlY3RBbGwoJ3N2ZycpLmRhdGEoW2RhdGFdKVxuXHRcdGNvbnN0IHN2Z0VudGVyID0gc3ZnLmVudGVyKCkuYXBwZW5kKCdzdmcnKVxuICAgICAgXHRjb25zdCBnRW50ZXIgPSBzdmdFbnRlci5hcHBlbmQoJ2cnKVxuXHRcdFxuXHRcdGdFbnRlci5hcHBlbmQoJ2cnKS5hdHRyKCdjbGFzcycsICdnLXBsb3QnKVxuXG5cdFx0Y29uc3QgYXhpcyA9IGdFbnRlci5hcHBlbmQoJ2cnKS5hdHRyKCdjbGFzcycsICdnLWF4aXMnKVxuXG5cdFx0Y29uc3QgeCA9IGF4aXMuYXBwZW5kKCdnJykuYXR0cignY2xhc3MnLCAnYXhpcyBheGlzLS14JylcblxuXHRcdGNvbnN0IHkgPSBheGlzLmFwcGVuZCgnZycpLmF0dHIoJ2NsYXNzJywgJ2F4aXMgYXhpcy0teScpXG5cblx0XHR4LmFwcGVuZCgndGV4dCcpLmF0dHIoJ2NsYXNzJywgJ2F4aXNfX2xhYmVsJylcblx0XHRcdC5hdHRyKCd0ZXh0LWFuY2hvcicsICdzdGFydCcpXG5cdFx0XHQudGV4dCgnUXVhbnRpdHknKVxuXG5cdFx0eS5hcHBlbmQoJ3RleHQnKS5hdHRyKCdjbGFzcycsICdheGlzX19sYWJlbCcpXG5cdFx0XHQuYXR0cigndGV4dC1hbmNob3InLCAnZW5kJylcblx0XHRcdC50ZXh0KCdRdWFsaXR5JylcdFxuXHR9XG5cblx0ZnVuY3Rpb24gZXhpdCh7IGNvbnRhaW5lciwgZGF0YSB9KSB7XG5cdH1cblxuXHRmdW5jdGlvbiB1cGRhdGVTY2FsZXMoeyBkYXRhIH0pIHtcblx0XHRoeXBvdGVudXNlID0gZ2V0SHlwb3RlbnVzZSh7IHg6IHdlaWdodFgsIHk6IHdlaWdodFkgfSlcblx0XHRjb25zdCByYW5nZVggPSB3ZWlnaHRYIC8gaHlwb3RlbnVzZSAqIGNoYXJ0V2lkdGhcblx0XHRjb25zdCByYW5nZVkgPSB3ZWlnaHRZIC8gaHlwb3RlbnVzZSAqIGNoYXJ0SGVpZ2h0XG5cdFx0Y29uc3QgbWF4UiA9IE1hdGguZmxvb3IoRk9OVF9TSVpFICogMS41KVxuXG5cdFx0c2NhbGVYXG5cdFx0XHQuZG9tYWluKFswLCBNQVhfVkFMXSlcblx0XHRcdC5yYW5nZShbMCwgcmFuZ2VYXSlcblxuXHRcdHNjYWxlWVxuXHRcdFx0LmRvbWFpbihbMCwgTUFYX1ZBTF0pXG5cdFx0XHQucmFuZ2UoW3JhbmdlWSwgMF0pXG5cblx0XHRzY2FsZVJcblx0XHRcdC5kb21haW4oWzAsIGRhdGEubGVuZ3RoXSlcblx0XHRcdC5yYW5nZShbbWF4UiwgMl0pXG5cblx0XHRzY2FsZUNcblx0XHRcdC5kb21haW4oZGF0YS5tYXAoZCA9PiBkLnJhbmspKVxuXHRcdFx0LnJhbmdlKENPTE9SUylcblx0fVxuXG5cdGZ1bmN0aW9uIHVwZGF0ZURvbSh7IGNvbnRhaW5lciwgZGF0YSB9KSB7XG5cdFx0Y29uc3Qgc3ZnID0gY29udGFpbmVyLnNlbGVjdCgnc3ZnJylcblx0XHRcblx0XHRzdmdcblx0XHRcdC5hdHRyKCd3aWR0aCcsIHdpZHRoKVxuXHRcdFx0LmF0dHIoJ2hlaWdodCcsIGhlaWdodClcblxuXHRcdGNvbnN0IGcgPSBzdmcuc2VsZWN0KCdnJylcblx0XHRcblx0XHRjb25zdCBtYXhZID0gc2NhbGVZLnJhbmdlKClbMF1cblx0XHRjb25zdCBvZmZzZXRYID0gY2hhcnRXaWR0aCAvIDJcblx0XHRjb25zdCBvZmZzZXRZID0gY2hhcnRIZWlnaHQgLSBtYXhZXG5cdFx0Y29uc3QgcmFkID0gTWF0aC5hY29zKHdlaWdodFggLyBoeXBvdGVudXNlKVxuXHRcdGNvbnN0IGFuZ2xlID0gOTAgLSAocmFkICogMTgwIC8gTWF0aC5QSSlcblx0XHRjb25zdCByb3RhdGlvbiA9IGByb3RhdGUoJHstYW5nbGV9IDAgJHtzY2FsZVkucmFuZ2UoKVswXX0pYFxuXHRcdGNvbnN0IHRyYW5zbGF0aW9uID0gdHJhbnNsYXRlKG1hcmdpbiAqIDEuNSArIG9mZnNldFgsIG1hcmdpbiArIG9mZnNldFkpXG5cdFx0Y29uc3QgdHJhbnNmb3JtID0gYCR7dHJhbnNsYXRpb259ICR7cm90YXRpb259YFxuXHRcdGcuYXR0cigndHJhbnNmb3JtJywgdHJhbnNmb3JtKVxuXG5cdFx0Y29uc3QgcGxvdCA9IGcuc2VsZWN0KCcuZy1wbG90JylcblxuXHRcdGNvbnN0IGl0ZW0gPSBwbG90LnNlbGVjdEFsbCgnLml0ZW0nKS5kYXRhKGQgPT4gZCwgZCA9PiBkLmluZGV4KVxuXHRcdFxuXHRcdGl0ZW0uZW50ZXIoKS5hcHBlbmQoJ2NpcmNsZScpXG5cdFx0XHQuYXR0cignY2xhc3MnLCAnaXRlbScpXG5cdFx0Lm1lcmdlKGl0ZW0pXG5cdFx0XHQuYXR0cigneCcsIDApXG5cdFx0XHQuYXR0cigneScsIDApXG5cdFx0XHQuYXR0cigncicsIGQgPT4gc2NhbGVSKGQucmFuaykpXG5cdFx0XHQuc3R5bGUoJ2ZpbGwnLCBkID0+IHNjYWxlQyhkLnJhbmspKVxuXHRcdFx0LnN0eWxlKCdzdHJva2UnLCBkID0+IGQzLmNvbG9yKHNjYWxlQyhkLnJhbmspKS5kYXJrZXIoMC43KSlcblx0XHRcdC5hdHRyKCd0cmFuc2Zvcm0nLCAgZCA9PiB0cmFuc2xhdGUoc2NhbGVYKGQueCksIHNjYWxlWShkLnkpKSlcblx0fVxuXG5cdGZ1bmN0aW9uIHVwZGF0ZUF4aXMoeyBjb250YWluZXIsIGRhdGEgfSkge1xuXHRcdGNvbnN0IGF4aXMgPSBjb250YWluZXIuc2VsZWN0KCcuZy1heGlzJylcblxuXHRcdGNvbnN0IGF4aXNMZWZ0ID0gZDMuYXhpc0xlZnQoc2NhbGVZKVxuXHRcdGNvbnN0IGF4aXNCb3R0b20gPSBkMy5heGlzQm90dG9tKHNjYWxlWClcblxuXHRcdGF4aXNMZWZ0LnRpY2tzKE1hdGgubWF4KDAsIE1hdGguZmxvb3Iod2VpZ2h0WSAvIDEwKSkpXG5cdFx0YXhpc0JvdHRvbS50aWNrcyhNYXRoLm1heCgwLCBNYXRoLmZsb29yKHdlaWdodFggLyAxMCkpKVxuXHRcdGNvbnN0IHggPSBheGlzLnNlbGVjdCgnLmF4aXMtLXgnKVxuXHRcdFxuXHRcdGNvbnN0IG1heFkgPSBzY2FsZVkucmFuZ2UoKVswXVxuXHRcdGNvbnN0IG9mZnNldCA9IG1heFlcblxuXHRcdGNvbnN0IGJ1ZmZlciA9IE1hdGguY2VpbChtYXJnaW4gLyAyKVxuXHRcdHguYXR0cigndHJhbnNmb3JtJywgdHJhbnNsYXRlKDAsIGJ1ZmZlciArIG9mZnNldCkpXG5cdFx0XHQuY2FsbChheGlzQm90dG9tKVxuXG5cdFx0Y29uc3QgeSA9IGF4aXMuc2VsZWN0KCcuYXhpcy0teScpXG5cblx0XHR5LmF0dHIoJ3RyYW5zZm9ybScsIHRyYW5zbGF0ZSgtYnVmZmVyLCAwKSlcblx0XHRcdC5jYWxsKGF4aXNMZWZ0KVxuXG5cdFx0eC5zZWxlY3QoJy5heGlzX19sYWJlbCcpXG5cdFx0XHQuYXR0cigneScsIG1hcmdpbiAtIDEpXG5cblx0XHR5LnNlbGVjdCgnLmF4aXNfX2xhYmVsJylcblx0XHRcdC5hdHRyKCd4Jywgb2Zmc2V0KVxuXHRcdFx0LmF0dHIoJ3knLCBtYXJnaW4gLSAxKVxuXHRcdFx0LmF0dHIoJ3RyYW5zZm9ybScsIGByb3RhdGUoOTApYClcblx0fVxuXG5cdGZ1bmN0aW9uIGNoYXJ0KGNvbnRhaW5lcikge1xuXHRcdGNvbnN0IGRhdGEgPSBjb250YWluZXIuZGF0dW0oKVxuXHRcdFxuXHRcdGVudGVyKHsgY29udGFpbmVyLCBkYXRhIH0pXG5cdFx0ZXhpdCh7IGNvbnRhaW5lciwgZGF0YSB9KVxuXHRcdHVwZGF0ZVNjYWxlcyh7IGNvbnRhaW5lciwgZGF0YSB9KVxuXHRcdHVwZGF0ZURvbSh7IGNvbnRhaW5lciwgZGF0YSB9KVxuXHRcdHVwZGF0ZUF4aXMoeyBjb250YWluZXIsIGRhdGEgfSlcblx0fVxuXG5cdGNoYXJ0LndpZHRoID0gZnVuY3Rpb24oLi4uYXJncykge1xuXHRcdGlmICghYXJncy5sZW5ndGgpIHJldHVybiB3aWR0aFxuXHRcdHdpZHRoID0gYXJnc1swXVxuXHRcdGNoYXJ0V2lkdGggPSB3aWR0aCAtIG1hcmdpbiAqIDIuNVxuXHRcdHJldHVybiBjaGFydFxuXHR9XG5cblx0Y2hhcnQuaGVpZ2h0ID0gZnVuY3Rpb24oLi4uYXJncykge1xuXHRcdGlmICghYXJncy5sZW5ndGgpIHJldHVybiBoZWlnaHRcblx0XHRoZWlnaHQgPSBhcmdzWzBdXG5cdFx0Y2hhcnRIZWlnaHQgPSBoZWlnaHQgLSBtYXJnaW4gKiAyLjVcblx0XHRyZXR1cm4gY2hhcnRcblx0fVxuXG5cdGNoYXJ0LndlaWdodCA9IGZ1bmN0aW9uKHsgeCwgeSB9KSB7XG5cdFx0d2VpZ2h0WCA9IHhcblx0XHR3ZWlnaHRZID0geVxuXHRcdHJldHVybiBjaGFydFxuXHR9XG5cblxuXG5cdHJldHVybiBjaGFydFxufVxuXG5mdW5jdGlvbiBoYW5kbGVJbnB1dCgpIHtcblx0Y29uc3QgdmFsID0gK3RoaXMudmFsdWVcblx0Y29uc3QgeCA9IHZhbFxuXHRjb25zdCB5ID0gMTAwIC0gdmFsXG5cdGNvbnN0IHdlaWdodGVkID0gd2VpZ2h0RGF0YSh7IHgsIHkgfSlcblxuXHRjaGFydC53ZWlnaHQoeyB4LCB5IH0pXG5cdGVsLmRhdHVtKHdlaWdodGVkKVxuXHRlbC5jYWxsKGNoYXJ0KVxufVxuXG5mdW5jdGlvbiBpbml0KCkge1xuXHRlbC5kYXR1bSh3ZWlnaHREYXRhKHsgeDogNTAsIHk6IDUwIH0pKVxuXHRlbC5jYWxsKGNoYXJ0KVxuXHRyZXNpemUoKVxuXHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigncmVzaXplJywgcmVzaXplKVxuXHRncmFwaGljLnNlbGVjdCgnLnNsaWRlciBpbnB1dCcpLm9uKCdpbnB1dCcsIGhhbmRsZUlucHV0KVxufVxuXG5pbml0KClcblxuIl19
<!DOCTYPE html>
<title>weighted pivot scatter plot v3</title>
<link href='dist.css' rel='stylesheet' />
<body>
<div class='graphic graphic--3'>
<div class='chart'></div>
<div class='slider'><input type='range' min='0' max='100' value='50'></div>
</div>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='dist.js'></script>
</body>
const graphic = d3.select('.graphic--3')
const COLORS = ['#ff3814', '#fe5c34', '#fc764f', '#f88d69', '#f2a385', '#e8b8a0', '#dbcdbd']
const FONT_SIZE = 11
const MAX_VAL = 10
const dummyData = d3.range(0, 50).map(d => ({
index: d,
x: 0.5 + Math.random() * (MAX_VAL - 1),
y: 0.5 + Math.random() * (MAX_VAL - 1),
}))
const chart = scatterplot()
const el = graphic.select('.chart')
function weightData({ x, y }) {
return dummyData.map(d => ({
...d,
score: d.x * x + d.y * y,
}))
.sort((a, b) => d3.descending(a.score, b.score))
.map((d, i) => ({
...d,
rank: i,
}))
.reverse()
}
function getHypotenuse({ x, y }) {
const x2 = x * x
const y2 = y * y
return Math.sqrt(x2 + y2)
}
function resize() {
const sz = Math.min(el.node().offsetWidth, window.innerHeight) * 0.8
chart.width(sz).height(sz)
el.call(chart)
}
function scatterplot() {
const margin = FONT_SIZE * 3
const scaleX = d3.scaleLinear()
const scaleY = d3.scaleLinear()
const scaleR = d3.scaleSqrt()
const scaleC = d3.scaleQuantile()
let width = 0
let height = 0
let chartWidth = 0
let chartHeight = 0
let weightX = 50
let weightY = 50
let hypotenuse = 0
function translate(x, y) {
return `translate(${x}, ${y})`
}
function enter({ container, data }) {
const svg = container.selectAll('svg').data([data])
const svgEnter = svg.enter().append('svg')
const gEnter = svgEnter.append('g')
gEnter.append('g').attr('class', 'g-plot')
const axis = gEnter.append('g').attr('class', 'g-axis')
const x = axis.append('g').attr('class', 'axis axis--x')
const y = axis.append('g').attr('class', 'axis axis--y')
x.append('text').attr('class', 'axis__label')
.attr('text-anchor', 'start')
.text('Quantity')
y.append('text').attr('class', 'axis__label')
.attr('text-anchor', 'end')
.text('Quality')
}
function exit({ container, data }) {
}
function updateScales({ data }) {
hypotenuse = getHypotenuse({ x: weightX, y: weightY })
const rangeX = weightX / hypotenuse * chartWidth
const rangeY = weightY / hypotenuse * chartHeight
const maxR = Math.floor(FONT_SIZE * 1.5)
scaleX
.domain([0, MAX_VAL])
.range([0, rangeX])
scaleY
.domain([0, MAX_VAL])
.range([rangeY, 0])
scaleR
.domain([0, data.length])
.range([maxR, 2])
scaleC
.domain(data.map(d => d.rank))
.range(COLORS)
}
function updateDom({ container, data }) {
const svg = container.select('svg')
svg
.attr('width', width)
.attr('height', height)
const g = svg.select('g')
const maxY = scaleY.range()[0]
const offsetX = chartWidth / 2
const offsetY = chartHeight - maxY
const rad = Math.acos(weightX / hypotenuse)
const angle = 90 - (rad * 180 / Math.PI)
const rotation = `rotate(${-angle} 0 ${scaleY.range()[0]})`
const translation = translate(margin * 1.5 + offsetX, margin + offsetY)
const transform = `${translation} ${rotation}`
g.attr('transform', transform)
const plot = g.select('.g-plot')
const item = plot.selectAll('.item').data(d => d, d => d.index)
item.enter().append('circle')
.attr('class', 'item')
.merge(item)
.attr('x', 0)
.attr('y', 0)
.attr('r', d => scaleR(d.rank))
.style('fill', d => scaleC(d.rank))
.style('stroke', d => d3.color(scaleC(d.rank)).darker(0.7))
.attr('transform', d => translate(scaleX(d.x), scaleY(d.y)))
}
function updateAxis({ container, data }) {
const axis = container.select('.g-axis')
const axisLeft = d3.axisLeft(scaleY)
const axisBottom = d3.axisBottom(scaleX)
axisLeft.ticks(Math.max(0, Math.floor(weightY / 10)))
axisBottom.ticks(Math.max(0, Math.floor(weightX / 10)))
const x = axis.select('.axis--x')
const maxY = scaleY.range()[0]
const offset = maxY
const buffer = Math.ceil(margin / 2)
x.attr('transform', translate(0, buffer + offset))
.call(axisBottom)
const y = axis.select('.axis--y')
y.attr('transform', translate(-buffer, 0))
.call(axisLeft)
x.select('.axis__label')
.attr('y', margin - 1)
y.select('.axis__label')
.attr('x', offset)
.attr('y', margin - 1)
.attr('transform', `rotate(90)`)
}
function chart(container) {
const data = container.datum()
enter({ container, data })
exit({ container, data })
updateScales({ container, data })
updateDom({ container, data })
updateAxis({ container, data })
}
chart.width = function(...args) {
if (!args.length) return width
width = args[0]
chartWidth = width - margin * 2.5
return chart
}
chart.height = function(...args) {
if (!args.length) return height
height = args[0]
chartHeight = height - margin * 2.5
return chart
}
chart.weight = function({ x, y }) {
weightX = x
weightY = y
return chart
}
return chart
}
function handleInput() {
const val = +this.value
const x = val
const y = 100 - val
const weighted = weightData({ x, y })
chart.weight({ x, y })
el.datum(weighted)
el.call(chart)
}
function init() {
el.datum(weightData({ x: 50, y: 50 }))
el.call(chart)
resize()
window.addEventListener('resize', resize)
graphic.select('.slider input').on('input', handleInput)
}
init()
$gray = #888
$gray-light = #aaa
$gray-lighter = #ccc
$gray-dark = #333
$sans = Helvetica, Arial, sans-serif
$small-font = 11px
*
box-sizing border-box
body
color $gray-dark
font-family $sans
.graphic__hed
text-align center
color $gray-dark
.chart
max-width 40rem
margin 0 auto
text-align center
.slider
max-width 20rem
margin 1rem auto
position relative
padding-top $small-font * 1.5
input
display block
width 100%
&:before
content 'Quality'
text-transform uppercase
font-size $small-font
letter-spacing 0.05em
color $gray
display inline-block
position absolute
top 0
left 0
&:after
content 'Quantity'
text-transform uppercase
font-size $small-font
letter-spacing 0.05em
color $gray
display inline-block
position absolute
top 0
right 0
.g-axis
line, path
stroke $gray-lighter
text
fill $gray
.g-axis .axis__label
text-transform uppercase
font-size $small-font
letter-spacing 0.05em
fill $gray
.item
fill-opacity 0.75
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment