D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
mforando
Full window
Github gist
Ternary Plot
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v4.min.js"></script> <style> body { margin:25px; top:0; right:0; bottom:0; left:0; background:rgb(35,35,35); background:none; color:black; } #rankingBrushSVG{ border:1px solid cyan; } #flexContainer{ display:flex; flex-basis:row; } #rankingBrush{ flex:0; display:none; } #ternaryPanel{ flex:100; } svg{ isolation: isolate; } circle{ mix-blend-mode:Multiply; } </style> </head> <div id="flexContainer"> <div id="rankingBrush"> <p>WR Rank Filter:</p> <svg id="rankingBrushSVG"> </svg> </div> <div id="ternaryPanel"> </div> </div> <body> <script> //d3.scale-cluster //https://github.com/schnerd/d3-scale-cluster !function(r,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("d3scaleCluster",[],n):"object"==typeof exports?exports.d3scaleCluster=n():r.d3scaleCluster=n()}("undefined"!=typeof self?self:this,function(){return function(r){function n(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return r[t].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=r,n.c=e,n.d=function(r,e,t){n.o(r,e)||Object.defineProperty(r,e,{configurable:!1,enumerable:!0,get:t})},n.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return n.d(e,"a",e),e},n.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},n.p="",n(n.s=0)}([function(r,n,e){function t(){function r(){if(!(a.length<=2)){var r=o(e,Math.min(e.length,a.length));n=0!==r.length,u=[];for(var t=0;t<r.length;t++)u.push(r[t])}}var n=!1,e=[],a=[],u=[],f=function(r){if(n){for(var e=u.length-1;e>=0;e--)if(r>=u[e])return a[e];return a[0]}};return f.domain=function(){return arguments.length?(e=arguments[0],r(),f):e},f.range=function(){if(arguments.length){var n=arguments[0],e=n.length!==a.length;return a=n,e&&r(),f}return a},f.invertExtent=function(r){for(var n=NaN,e=NaN,t=0;t<a.length;t++)if(a[t]===r){n=u[t],e=t+1<a.length?u[t+1]:NaN;break}return[n,e]},f.clusters=function(){return u.slice(1)},f.export=function(){return{isReady:n,domain:e,range:a,breakpoints:u}},f.import=function(r){if(!r)throw new Error("Import requires parameters");return n=r.isReady,e=r.domain,a=r.range,u=r.breakpoints,f},f.copy=function(){return t().domain(e).range(a)},f}var o=e(1);"object"==typeof d3&&(d3.scaleCluster=t),r.exports=t},function(r,n,e){var t=e(2);r.exports=t},function(r,n){function e(r){return r.slice().sort(function(r,n){return r-n})}function t(r){for(var n,e=0,t=0;t<r.length;t++)0!==t&&r[t]===n||(n=r[t],e++);return e}function o(r,n){for(var e=[],t=0;t<r;t++){for(var o=[],a=0;a<n;a++)o.push(0);e.push(o)}return e}function a(r,n,e,t){var o;if(r>0){var a=(e[n]-e[r-1])/(n-r+1);o=t[n]-t[r-1]-(n-r+1)*a*a}else o=t[n]-e[n]*e[n]/(n+1);return o<0?0:o}function u(r,n,e,t,o,f,i){if(!(r>n)){var l=Math.floor((r+n)/2);t[e][l]=t[e-1][l-1],o[e][l]=l;var c=e;r>e&&(c=Math.max(c,o[e][r-1]||0)),c=Math.max(c,o[e-1][l]||0);var s=l-1;n<t.length-1&&(s=Math.min(s,o[e][n+1]||0));for(var h,p,g,v,d=s;d>=c&&!((h=a(d,l,f,i))+t[e-1][c-1]>=t[e][l]);--d)p=a(c,l,f,i),g=p+t[e-1][c-1],g<t[e][l]&&(t[e][l]=g,o[e][l]=c),c++,(v=h+t[e-1][d-1])<t[e][l]&&(t[e][l]=v,o[e][l]=d);u(r,l-1,e,t,o,f,i),u(l+1,n,e,t,o,f,i)}}function f(r,n,e){for(var t=n[0].length,o=new Array(t),f=new Array(t),i=r[Math.floor(t/2)],l=0;l<t;++l)0===l?(o[0]=r[0]-i,f[0]=(r[0]-i)*(r[0]-i)):(o[l]=o[l-1]+r[l]-i,f[l]=f[l-1]+(r[l]-i)*(r[l]-i)),n[0][l]=a(0,l,o,f),e[0][l]=0;for(var c,s=1;s<n.length;++s)c=s<n.length-1?s:t-1,u(c,t-1,s,n,e,o,f)}function i(r,n){if(n>r.length)throw new Error("Cannot generate more classes than there are data values");var a=r.length,u=e(r),i=t(u);if(1===i)return[u];n=Math.min(i,n);var l=o(n,a),c=o(n,a);f(u,l,c);for(var s=[],h=c[0].length-1,p=c.length-1;p>=0;p--){var g=c[p][h];s[p]=u[g],p>0&&(h=g-1)}return s}r.exports=i}])}); </script> <script> var width = 200; var height = 10; d3.select("#rankingBrushSVG") .attr("height",height) .attr("width",width) d3.select("#rankingBrushSVG").append("g") .attr("class", "brush") .call(d3.brushX() .extent([[0, 0], [width, height]]) .on("end", brushended)); var scaleRank = d3.scaleLinear() .range([0,width]); var scaleYards = d3.scaleLinear(); var scaleScores = d3.scaleLinear(); var scaleReceptions = d3.scaleLinear(); var cnt = 0; var rankarray = [300] //d3.range(150).map(function(d){return d*5+4}) var svg = d3.select("#ternaryPanel").append("svg") .style("overflow","visible") .style("background","white") var axis1 = svg.append("g"); var axis2 = svg.append("g"); var axis3 = svg.append("g"); d3.csv("wr.csv",function(projections){ parseCSV(projections) setSlider(projections) updateTernary(projections,rankarray[cnt]) }) function parseCSV(projections){ //parse numeric fields. projections.forEach(function(player){ player.Rank = +player.Rank; player.ReceivingTD = +player.ReceivingTD; player.ReceivingYards = +player.ReceivingYards; player.ReceivingYardsPerCatch = +player.ReceivingYardsPerCatch; player.Receptions = +player.Receptions; player.RushingAttempts = +player.RushingAttempts; player.RushingTD = +player.RushingTD; player.RushingYards = +player.RushingYards; player.RushingYardsPerAttempt = +player.RushingYardsPerAttempt; player.Targets = +player.Targets; player.TotalPoints_PPR = +player.TotalPoints_PPR; //calculate metrics for ternary plot player.TotalYards = player.ReceivingYards/10 + player.RushingYards/10; player.TotalScores = player.ReceivingTD*6 + player.RushingTD*6; player.TotalReceptions = player.Receptions; //sum PPR points and check against raw data. player.PPR = player.TotalYards + player.TotalScores + player.TotalReceptions; player.percentYards = player.TotalYards/player.PPR; player.percentScores = player.TotalScores/player.PPR; player.percentReceptions = player.TotalReceptions/player.PPR; }) } function setSlider(projections){ scaleRank.domain(d3.extent(projections,function(d){return d.Rank})) } function brushended() { if (!d3.event.sourceEvent) return; // Only transition after input. if (!d3.event.selection) return; // Ignore empty selections. var d0 = d3.event.selection.map(x.invert), d1 = d0.map(d3.timeDay.round); // If empty when rounded, use floor & ceil instead. if (d1[0] >= d1[1]) { d1[0] = d3.timeDay.floor(d0[0]); d1[1] = d3.timeDay.offset(d1[0]); } d3.select(this).transition().call(d3.event.target.move, d1.map(x)); } var ternaryWidth = 600; var ternaryHeight = 500; var centre = { "x": (ternaryWidth/2), "y": ternaryHeight/2 } var ternaryMargin = {top: 30, left: 30, bottom: 30, right: 30} svg.attr("width",ternaryWidth + ternaryMargin.left + ternaryMargin.right) .attr("height",ternaryHeight + ternaryMargin.top + ternaryMargin.bottom) var axes = svg.append('g').attr('class','axes'); var corners = [ [ternaryMargin.left, ternaryHeight + ternaryMargin.top], [ternaryWidth + ternaryMargin.left, ternaryHeight + ternaryMargin.top], [(ternaryWidth/2) + ternaryMargin.left, ternaryMargin.top], ] svg.append("polygon") .attr("points",corners.join(" ")) .style("fill","none") .style("stroke","black") .style("stroke-width",2) function updateTernary(projections,index){ var filtered = projections.filter(function(d){return d.Rank <index}) var pad = 30; scaleYards .domain(d3.extent(projections,function(d){return d.percentYards})) .range([corners[0][0] + pad,corners[1][0]-pad]) .clamp(true) .domain([0,1]) var sideDistance = Math.sqrt(Math.pow((corners[0][0]-corners[1][0]), 2) + Math.pow((corners[1][1]-corners[1][1]), 2)) scaleScores .domain([d3.min(projections,function(d){return d.percentScores}),.45]) .clamp(true) .range([pad,sideDistance-pad*2]) .domain([0.25,.45]) scaleReceptions .domain(d3.extent(projections,function(d){return d.percentReceptions})) .clamp(true) .range([pad,sideDistance-pad*2]) .domain([0,.55]) var circles = svg.selectAll("circle").data(filtered,function(d){return d.Name}) circles.enter() .append("circle") .style("fill","black") .attr("cx",function(d){ d.x = coord(d)[0]; d.y = coord(d)[1]; return coord(d)[0] }) .attr("cy",function(d){ return coord(d)[1] }) .attr("r",.1) .merge(circles) .on("mouseover",function(d){ var cx = d3.select(this).attr("cx") var cy = d3.select(this).attr("cy") svg.append("text") .attr("x",function(d){return cx}) .attr("y",function(d){return cy}) .text(d.Name) }) .transition() .ease(d3.easeCubic) // .delay(function(d,i){return i*40}) .duration(1000) .attr("r",5) .attr("cx",function(d){ return coord(d)[0] }) .attr("cy",function(d){ return coord(d)[1] }) .style("stroke","black") .style("fill",function(d){ let x = Math.abs(d.x - centre.x) let y = Math.abs(d.y - centre.y) if (d.y < centre.y && d.x > centre.x) { d.angle = angleTan(x,y) * (180 / Math.PI) } if (d.y <= centre.y && d.x <= centre.x) { d.angle = 360 - (angleTan(x,y) * (180 / Math.PI)) } if (d.y > centre.y && d.x < centre.x) { d.angle = 180 + (angleTan(x,y) * (180 / Math.PI)) } if (d.y >= centre.y && d.x >= centre.x) { d.angle = 180 - (angleTan(x,y) * (180 / Math.PI)) } d.hAngle = Math.floor(d.angle) d.sat = 0.6 - (d.distance / 2) d.lum = 0.2 + (d.distance * 0.8) return d3.hsl(d.hAngle, 1,.5) if(d.Position=="WR"){ return "red" } else {return "blue"} }) function angleTan(opposite, adjacent) { return Math.atan(opposite/adjacent); } circles.exit().remove(); /* var labels = svg.selectAll(".labels").data(filtered) labels.enter() .append("text") .attr("class","labels") .style("fill","black") .merge(labels) .transition() .ease(d3.easeCubic) .delay(function(d,i){return i*40}) .duration(1000) .text(function(d){return d.Name}) .attr("x",function(d){ return coord(d)[0] }) .attr("y",function(d){ return coord(d)[1] }) labels.exit().remove(); */ var scaleWidth = d3.scaleLinear().range([corners[0][0],corners[1][0]]) axis1 .attr("transform","translate(0," + corners[0][1]+")") .style("font-family","Helvetica") .style("font-size",".85rem") .transition() .ease(d3.easeCubic) .duration(1500) .call(d3.axisBottom(scaleYards).tickFormat(d3.format("0.0%"))) axis2 .attr("transform","translate(" + ternaryMargin.left+"," + corners[0][1]+") rotate(301)") .style("font-family","Helvetica") .style("font-size",".85rem") .transition() .ease(d3.easeCubic) .duration(1500) .call(d3.axisTop(scaleReceptions).tickFormat(d3.format("0.0%"))) axis3 .attr("transform","translate(" +(ternaryMargin.left+ternaryWidth/2)+"," + ternaryMargin.top+") rotate(59)") .style("font-family","Helvetica") .style("font-size",".85rem") .transition() .ease(d3.easeCubic) .duration(1500) .call(d3.axisTop(scaleScores).tickFormat(d3.format("0.0%"))) function coord(d){ var a = scaleYards(d.percentYards)/(scaleYards.range()[1]-scaleYards.range()[0]), b = scaleReceptions(d.percentReceptions)/(scaleReceptions.range()[1]-scaleReceptions.range()[0]), c = scaleScores(d.percentScores)/(scaleScores.range()[1]-scaleScores.range()[0]); var sum, pos = [0,0]; sum = a + b + c; if(sum !== 0) { a /= sum; b /= sum; c /= sum; pos[0] = corners[0][0] * a + corners[1][0] * b + corners[2][0] * c; pos[1] = corners[0][1] * a + corners[1][1] * b + corners[2][1] * c; } return pos; } /* setTimeout(function(){ cnt = cnt + 1; updateTernary(projections,rankarray[cnt]) },1000) */ } </script> </body>
https://d3js.org/d3.v4.min.js