Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.goallabels{
font-family:Helvetica;
font-size:0.9rem;
}
.dimensionheaders{
font-family:Helvetica;
font-size:1.5rem;
dominant-baseline:central;
}
.numlabels{
pointer-events:none;
cursor:none;
}
.projectCircles{
cursor:pointer;
}
</style>
</head>
<body>
<script>
var goals = [{"dimension":"Financial","project":"Increase 401k contribution to 8%","relativeCost":null,"relativeValue":null}
,{"dimension":"Financial","project":"Offer financial consulting services","relativeCost":null,"relativeValue":null}
,{"dimension":"Financial","project":"Offer an retirement educational session","relativeCost":null,"relativeValue":null}
,{"dimension":"Financial","project":"Increase participation in 401k","relativeCost":null,"relativeValue":null}
,{"dimension":"Financial","project":"Adjust XXXX positions to market value","relativeCost":null,"relativeValue":null}
,{"dimension":"Health","project":"Offer a yoga program","relativeCost":null,"relativeValue":null}
,{"dimension":"Health","project":"Offer healthy food prep solutions","relativeCost":null,"relativeValue":null}
,{"dimension":"Health","project":"Have a wellness committee","relativeCost":null,"relativeValue":null}
,{"dimension":"Health","project":"Offer a smoking cessation program","relativeCost":null,"relativeValue":null}
]
var dimensions = d3.nest().key(function(d){return d.dimension}).entries(goals)
var maxLength = d3.max(dimensions.map(function(d){return d.values.length}))
//set up a scaleband that works for the longest dimension
var scalePoint = d3.scalePoint()
.domain(d3.range(maxLength))
.range([100,400])
var colors = d3.scaleOrdinal()
.domain(["Financial","Health"])
.range(["rgb(183,32,51)","rgb(0,62,126)"])
var svg = d3.select("body").selectAll("svg").data(dimensions)
svg.enter()
.append("svg")
.attr("width", 900)
.attr("height", 500)
.each(function(d){
d3.select(this)
.append("text")
.attr("class","dimensionheaders")
.text(d.key + " Dimension")
.attr("x",5)
.attr("y",15)
.style("fill",colors(d.key))
})
var numlabelsBckgrnd = d3.selectAll("svg").selectAll(".numlabelsBckgrnd")
.data(function(d){return d.values})
numlabelsBckgrnd.enter()
.append("text")
.attr("class","numlabelsBckgrnd")
.attr("x",20)
.attr("y",function(d,i){return scalePoint(i)})
.text(function(d,i){return (i+1)})
.style("text-anchor","middle")
.style("dominant-baseline","central")
.style("fill","black")
var circles = d3.selectAll("svg").selectAll("circles")
.data(function(d){return d.values})
circles.enter()
.append("circle")
.attr("class","projectCircles")
.attr("cx",20)
.attr("cy",function(d,i){return scalePoint(i)})
.attr("r",10)
.style("fill",function(d){
return colors(d.dimension)
})
.style("stroke","black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
var labels = d3.selectAll("svg").selectAll(".goallabels")
.data(function(d){return d.values})
labels.enter()
.append("text")
.attr("class","goallabels")
.attr("x",35)
.attr("y",function(d,i){
d.x = d3.select(this).attr("cx");
d.y = scalePoint(i);
return scalePoint(i)})
.text(function(d){return d.project})
.style("dominant-baseline","central")
var numlabels = d3.selectAll("svg").selectAll(".numlabels")
.data(function(d){return d.values})
numlabels.enter()
.append("text")
.attr("class","numlabels")
.attr("x",20)
.attr("y",function(d,i){return scalePoint(i)})
.text(function(d,i){return (i+1)})
.style("text-anchor","middle")
.style("dominant-baseline","central")
.style("fill","white")
var scalePointVertical = d3.scalePoint()
.range([450,50])
.domain(['Lowest Cost','Average Cost','Largest Cost'])
var scalePointHorizontal = d3.scalePoint()
.range([450,900])
.domain(['Least Value','Average Value','Most Value'])
d3.selectAll("svg")
.append("g")
.attr("transform","translate(450,0)")
.call(d3.axisLeft(scalePointVertical))
.selectAll("text")
.style("font-size","1rem")
d3.selectAll("svg")
.append("g")
.attr("class","xAxis")
.attr("transform","translate(0,450)")
.call(d3.axisBottom(scalePointHorizontal))
.selectAll("text")
.style("font-size","1rem")
.style("text-anchor",function(d,i){
if(i==0){return "start"}
if(i==2){
return "end"
}
})
function dragstarted(d) {
//capture original location in case the end location is invalid.
d3.select(this).each(function(d){
d.orig_x = d3.select(this).attr("cx");
d.orig_y = d3.select(this).attr("cy");
})
}
function dragged(d) {
d3.select(this).attr("cx",d3.event.x).attr("cy",d3.event.y);
d3.select(this.parentNode)
.selectAll(".numlabels")
.filter(function(z){
return z.project == d.project
})
.attr("x",d3.event.x)
.attr("y",d3.event.y);
}
function dragended(d) {
//evaluate if the circle is in the charting region.
var svgcoords = d3.mouse(this);
if(svgcoords[0]>scalePointHorizontal.range()[0] && svgcoords[0]<scalePointHorizontal.range()[1])
{
d3.select(this)
.each(function(d){
d.scored = true;
d.x = d3.select(this).attr("cx")
d.y = d3.select(this).attr("cy")
})
//node is in the chart range. save results to whats bounded to the SVG.
d3.select(this.parentNode)
.each(function(d){
d.results = d3.select(this).selectAll("circle").data()
.filter(function(d){return d.scored == true})
.map(function(d){
//calculate relative cost
var minval = scalePointVertical.range()[1];
var maxval = scalePointVertical.range()[0];
d.relativeCost = (maxval-d.y)/(maxval-minval);
var minval = scalePointHorizontal.range()[1];
var maxval = scalePointHorizontal.range()[0];
d.relativeValue = (maxval-d.x)/(maxval-minval);
return d})
})
updateBenchmarks()
}
else
{
//node is not in the chart range. reset.
d3.select(this)
.transition()
.attr("cx",function(d){return d.orig_x})
.attr("cy",function(d){return d.orig_y})
d3.select(this.parentNode)
.selectAll(".numlabels")
.filter(function(z){return z.project == d.project})
.transition()
.attr("x",function(d){return d.orig_x})
.attr("y",function(d){return d.orig_y})
}
}
function updateBenchmarks(){
//walk down all SVGs and find the max value in terms of cost/value to provide as a benchmark. Allow adjustment of this
//to adjust for new cost/value information.
var allresults = []
d3.selectAll("svg")
.each(function(d,i){
var benchmarkgoal = null;
if(d.results){
//go through results, normalize them against both dimensions, and find the largest node to use as a benchmark.
var sorted = d.results.sort(function(a,b){
a.totalIndex = a.relativeCost + a.relativeValue;
b.totalIndex = b.relativeCost + b.relativeValue;
return d3.descending(a.totalIndex,b.totalIndex)
})
benchmarkgoal = sorted[0]
}
allresults.push({"dimension":d.key,"results":d.results,"benchmark":benchmarkgoal})
})
//figure out the benchmark with the max value on all other charts.
d3.selectAll("svg")
.each(function(d,i){
//find valid goals in other dimensions.
var contains_goals = allresults.filter(function(z){return z.benchmark && (z.dimension != d.key)}).map(function(d){return d.benchmark})
if(contains_goals.length>0){
//valid project/goal found, go render it.
var bnchCircle = d3.select(this)
.selectAll(".benchmarknode")
.data([contains_goals[0]])
bnchCircle.enter()
.append("circle")
.attr("class","benchmarknode")
.merge(bnchCircle)
.attr("cx",function(d){
return d3.interpolateNumber(scalePointHorizontal.range()[0],scalePointHorizontal.range()[1])(d.relativeValue)
})
.attr("cy",function(d){
return d3.interpolateNumber(scalePointVertical.range()[0],scalePointVertical.range()[1])(d.relativeCost)
})
.attr("r",10)
.style("fill","cyan")
.call(d3.drag()
.on("start", benchdragstarted)
.on("drag", benchdragged)
.on("end", benchdragended))
}
})
}
function benchdragstarted(d) {
//record original position.
d3.select(this).each(function(d){
d.orig_x = d3.select(this).attr("cx");
d.orig_y = d3.select(this).attr("cy");
})
}
function benchdragged(d) {
//while dragging, scale the other charts goals based on the delta of the drag.
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function benchdragended(d) {
}
</script>
</body>
https://d3js.org/d3.v4.min.js