Skip to content

Instantly share code, notes, and snippets.

@jgeryk
Created January 18, 2017 17:16
Show Gist options
  • Select an option

  • Save jgeryk/5b605a168c7d62310828cd8aacbf98ff to your computer and use it in GitHub Desktop.

Select an option

Save jgeryk/5b605a168c7d62310828cd8aacbf98ff to your computer and use it in GitHub Desktop.
Dates/Hours Prototype w/ Area Fill
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Date/Hours Sculpter</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.4.0/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.19.0/d3-legend.min.js" charset="utf-8"></script>
<style>
body {
font: 13px sans-serif;
position: relative;
width: 960px;
height: 500px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.area{
pointer-events: none;
}
path {
stroke-width: 2;
fill: none;
}
</style>
</head>
<body>
<div style="position: absolute; margin-top: 15px; margin-left: 100px; color: #F3A174"> Project Manager </div>
<div style="position: absolute; margin-top: 30px; margin-left: 100px; color: #6ba2ea"> Developer </div>
<script>
var now = new Date
var future = new Date
future = future.setDate(future.getDate() + 7)
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 50, left: 70},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleTime()
.domain([now, future])
.nice()
.range([0, width])
var xAxis = d3.axisBottom(x).ticks(8)
var dates = xAxis.scale().ticks(xAxis.ticks()[0])
var scaleMax = 12
var clicked = undefined
var dragged = null,
selected = null
var project = {
budgetedHours: 85,
roles: [{title: 'Project Manager', percent: 60, color: '#F3A174'},
{title: 'Developer', percent: 40, color: "#6ba2ea"}]
}
// project.roles.forEach((r, i) => {r.dates = dates.map( (d, index) => { return { date: d, hours: ( (r.percent/100) * project.budgetedHours)/dates.length, percent: r.percent/dates.length, locked: false, roleIndex: i, index: index } } )})
project.roles.forEach( (r,i) => {r.dates = dates.map(
(d, index) => {
return {
date: d,
hours: ( (r.percent/100) * project.budgetedHours)/dates.length,
percent: r.percent/dates.length,
locked: false,
roleIndex: i,
index: index
}
}
)})
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup)
.on("keydown", keydown);
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.hours); })
.curve(d3.curveCardinal);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.hours); })
.curve(d3.curveCardinal);
// valueline.interpolate('cardinal-open')
var y = d3.scaleLinear().range([0, height]).domain([scaleMax, 0])
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
svg.append("g")
.attr("transform", "translate(0," + height + ")")
// .call(d3.axisLeft(y))
.call(xAxis)
svg.append("g")
.call(d3.axisLeft(y))
function redraw(){
svg.selectAll('.line').remove()
svg.selectAll('.dot').remove()
svg.selectAll('.area').remove()
svg.selectAll('circle').remove()
project.roles.forEach( r=> {
svg.append("path")
.data([r.dates])
.attr("class", "area")
.attr("d", area)
// .style("stroke-width", 0)
.style("fill", (d,i) => {return project.roles[d[i].roleIndex].color})
.style("opacity", 0.3)
var line = svg.append("path")
.data([r.dates])
.attr("stroke", (d,i) => {return project.roles[d[i].roleIndex].color})
.attr("class", "line")
.attr("d", valueline)
.on("mousedown", function(d){ selected = dragged = d; });
svg.selectAll("dot")
.data(r.dates)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.hours); })
.attr("fill", d => { return d.locked ? "#000" : project.roles[d.roleIndex].color})
.on("mousedown", function(d){ selected = dragged = d; })
.on("click", point => {
if(point == clicked){
point.locked = !point.locked
redraw()
} else {
clicked = point
}
});
})
}
function mousedown() {
console.log('mousedown')
console.log(d3.event)
}
function mousemove() {
if(!dragged || dragged.locked) return;
else {
var m = d3.mouse(svg.node());
// getDay(m[0])
day = dragged
oldPct = day.percent
day.hours = scaleHours(m[1])
day.percent = 100*day.hours/project.budgetedHours
balance(project.roles[day.roleIndex].dates, dragged.index, oldPct)
// dragged[1] = Math.max(0, Math.min(height, m[1]));
redraw();
}
}
function mouseup() {
console.log(d3.mouse(svg.node()))
if(!dragged) return;
// mousemove();
dragged = null;
}
function keydown(){
console.log('keydown')
}
redraw()
function getDay(xPos){
dayLength = width / data.length
return data[Math.round((xPos/dayLength) - 0.8)]
}
function scaleHours(yPos){
var invertedPct = 1-(yPos / height)
var hours = invertedPct * scaleMax
return hours < 0 ? 0 : (hours > scaleMax ? scaleMax : hours)
}
function balance(array, index, oldPct){
var totalPct = project.roles[array[index].roleIndex].percent
var unlockedTotal = totalPct - array.reduce((a,b) => {
var l = b.locked ? b.percent : 0
return a + l
}, 0)
var balancedOldPct = totalPct * oldPct / unlockedTotal;
var div = 1 - balancedOldPct / totalPct
var balancedPct = totalPct * array[index].percent / unlockedTotal
for(var i=0; i<array.length; i++){
if (i != index && !array[i].locked){
array[i].percent = totalPct * (((1 - (balancedPct / totalPct)) * (array[i].percent / totalPct)) / div);
array[i].hours = project.budgetedHours * array[i].percent/100
}
}
return array
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment