2015-02-08 54 views
1

我正在实施使用此甘特图的日历小部件Dimitry Kudryavtsev developed使用d3。我有广告系列 - 在这种情况下这将是任务。我需要根据日历视图/日期提供事件的时间表。D3中的任务甘特图默认重叠 - 需要将它们向下移动

Currently it overlaps tasks

我需要的解决方案,以避免落在同一时间线上重叠任务。我们该怎么做?

例如,在本实施方式中的任务重叠按预期:

(A)(B)

| ------ [---- | ----]

我想在这样

(一)

| --------- |

。 ..。 。 [-------] (B)

Fiddle Here

官方例如here

我们将必须修改D3甘特码

/** 
* @author Dimitry Kudrayvtsev 
* @version 2.0 
*/ 

d3.gantt = function() { 
    var FIT_TIME_DOMAIN_MODE = "fit"; 
    var FIXED_TIME_DOMAIN_MODE = "fixed"; 

    var margin = { 
    top : 20, 
    right : 40, 
    bottom : 20, 
    left : 150 
    }; 
    var timeDomainStart = d3.time.day.offset(new Date(),-3); 
    var timeDomainEnd = d3.time.hour.offset(new Date(),+3); 
    var timeDomainMode = FIT_TIME_DOMAIN_MODE;// fixed or fit 
    var taskTypes = []; 
    var taskStatus = []; 
    var height = document.body.clientHeight - margin.top - margin.bottom-5; 
    var width = document.body.clientWidth - margin.right - margin.left-5; 

    var tickFormat = "%H:%M"; 

    var keyFunction = function(d) { 
    return d.startDate + d.taskName + d.endDate; 
    }; 

    var rectTransform = function(d) { 
    return "translate(" + x(d.startDate) + "," + y(d.taskName) + ")"; 
    }; 

    var x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width ]).clamp(true); 

    var y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1); 

    var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true) 
     .tickSize(8).tickPadding(8); 

    var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0); 

    var initTimeDomain = function() { 
    if (timeDomainMode === FIT_TIME_DOMAIN_MODE) { 
     if (tasks === undefined || tasks.length < 1) { 
     timeDomainStart = d3.time.day.offset(new Date(), -3); 
     timeDomainEnd = d3.time.hour.offset(new Date(), +3); 
     return; 
     } 
     tasks.sort(function(a, b) { 
     return a.endDate - b.endDate; 
     }); 
     timeDomainEnd = tasks[tasks.length - 1].endDate; 
     tasks.sort(function(a, b) { 
     return a.startDate - b.startDate; 
     }); 
     timeDomainStart = tasks[0].startDate; 
    } 
    }; 

    var initAxis = function() { 
    x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width ]).clamp(true); 
    y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1); 
    xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true) 
     .tickSize(8).tickPadding(8); 

    yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0); 
    }; 

    function gantt(tasks) { 

    initTimeDomain(); 
    initAxis(); 

    var svg = d3.select("body") 
    .append("svg") 
    .attr("class", "chart") 
    .attr("width", width + margin.left + margin.right) 
    .attr("height", height + margin.top + margin.bottom) 
    .append("g") 
     .attr("class", "gantt-chart") 
    .attr("width", width + margin.left + margin.right) 
    .attr("height", height + margin.top + margin.bottom) 
    .attr("transform", "translate(" + margin.left + ", " + margin.top + ")"); 

     svg.selectAll(".chart") 
    .data(tasks, keyFunction).enter() 
    .append("rect") 
    .attr("rx", 5) 
     .attr("ry", 5) 
    .attr("class", function(d){ 
     if(taskStatus[d.status] == null){ return "bar";} 
     return taskStatus[d.status]; 
     }) 
    .attr("y", 0) 
    .attr("transform", rectTransform) 
    .attr("height", function(d) { return y.rangeBand(); }) 
    .attr("width", function(d) { 
     return (x(d.endDate) - x(d.startDate)); 
     }); 


    svg.append("g") 
    .attr("class", "x axis") 
    .attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")") 
    .transition() 
    .call(xAxis); 

    svg.append("g").attr("class", "y axis").transition().call(yAxis); 

    return gantt; 

    }; 

    gantt.redraw = function(tasks) { 

    initTimeDomain(); 
    initAxis(); 

     var svg = d3.select("svg"); 

     var ganttChartGroup = svg.select(".gantt-chart"); 
     var rect = ganttChartGroup.selectAll("rect").data(tasks, keyFunction); 

     rect.enter() 
     .insert("rect",":first-child") 
     .attr("rx", 5) 
     .attr("ry", 5) 
    .attr("class", function(d){ 
     if(taskStatus[d.status] == null){ return "bar";} 
     return taskStatus[d.status]; 
     }) 
    .transition() 
    .attr("y", 0) 
    .attr("transform", rectTransform) 
    .attr("height", function(d) { return y.rangeBand(); }) 
    .attr("width", function(d) { 
     return (x(d.endDate) - x(d.startDate)); 
     }); 

     rect.transition() 
      .attr("transform", rectTransform) 
    .attr("height", function(d) { return y.rangeBand(); }) 
    .attr("width", function(d) { 
     return (x(d.endDate) - x(d.startDate)); 
     }); 

    rect.exit().remove(); 

    svg.select(".x").transition().call(xAxis); 
    svg.select(".y").transition().call(yAxis); 

    return gantt; 
    }; 

    gantt.margin = function(value) { 
    if (!arguments.length) 
     return margin; 
    margin = value; 
    return gantt; 
    }; 

    gantt.timeDomain = function(value) { 
    if (!arguments.length) 
     return [ timeDomainStart, timeDomainEnd ]; 
    timeDomainStart = +value[0], timeDomainEnd = +value[1]; 
    return gantt; 
    }; 

    /** 
    * @param {string} 
    *    vale The value can be "fit" - the domain fits the data or 
    *    "fixed" - fixed domain. 
    */ 
    gantt.timeDomainMode = function(value) { 
    if (!arguments.length) 
     return timeDomainMode; 
     timeDomainMode = value; 
     return gantt; 

    }; 

    gantt.taskTypes = function(value) { 
    if (!arguments.length) 
     return taskTypes; 
    taskTypes = value; 
    return gantt; 
    }; 

    gantt.taskStatus = function(value) { 
    if (!arguments.length) 
     return taskStatus; 
    taskStatus = value; 
    return gantt; 
    }; 

    gantt.width = function(value) { 
    if (!arguments.length) 
     return width; 
    width = +value; 
    return gantt; 
    }; 

    gantt.height = function(value) { 
    if (!arguments.length) 
     return height; 
    height = +value; 
    return gantt; 
    }; 

    gantt.tickFormat = function(value) { 
    if (!arguments.length) 
     return tickFormat; 
    tickFormat = value; 
    return gantt; 
    }; 



    return gantt; 
}; 

我们这个数据传递到该代码:

var tasks = [ 
{"startDate":new Date("Sun Dec 08 04:36:45 EST 2012"),"endDate":new Date("Sun Dec 09 02:36:45 EST 2012"),"taskName":"E Job","status":"RUNNING"}, 
    {"startDate":new Date("Sun Dec 07 01:36:45 EST 2012"),"endDate":new Date("Sun Dec 09 02:36:45 EST 2012"),"taskName":"E Job","status":"FAILED"}]; 

var taskStatus = { 
    "SUCCEEDED" : "bar", 
    "FAILED" : "bar-failed", 
    "RUNNING" : "bar-running", 
    "KILLED" : "bar-killed" 
}; 

var taskNames = [ "D Job", "P Job", "E Job", "A Job", "N Job" ]; 

我不需要显示y轴数据,所以我们可以在那个轴上玩耍 - 我们可以隐藏这些嘀嗒声。 (Y轴的数据对我来说并不重要,我们可以对它做点技巧)

回答

1

我想你应该看看http://codepen.io/jey/pen/jmClJ/

var w = 800; 
    var h = 400; 


    var svg = d3.selectAll(".svg") 
    //.selectAll("svg") 
    .append("svg") 
    .attr("width", w) 
    .attr("height", h) 
    .attr("class", "svg"); 


    var taskArray = [ 
    { 
    task: "conceptualize", 
    type: "development", 
    startTime: "2013-1-28", //year/month/day 
    endTime: "2013-2-1", 
    details: "This actually didn't take any conceptualization" 
}, 

{ 
    task: "sketch", 
    type: "development", 
    startTime: "2013-2-1", 
    endTime: "2013-2-6", 
    details: "No sketching either, really" 
}, 

{ 
    task: "color profiles", 
    type: "development", 
    startTime: "2013-2-6", 
    endTime: "2013-2-9" 
}, 

{ 
    task: "HTML", 
    type: "coding", 
    startTime: "2013-2-2", 
    endTime: "2013-2-6", 
    details: "all three lines of it" 
}, 

{ 
    task: "write the JS", 
    type: "coding", 
    startTime: "2013-2-6", 
    endTime: "2013-2-9" 
}, 

{ 
    task: "advertise", 
    type: "promotion", 
    startTime: "2013-2-9", 
    endTime: "2013-2-12", 
    details: "This counts, right?" 
}, 

{ 
    task: "spam links", 
    type: "promotion", 
    startTime: "2013-2-12", 
    endTime: "2013-2-14" 
}, 
{ 
    task: "eat", 
    type: "celebration", 
    startTime: "2013-2-8", 
    endTime: "2013-2-13", 
    details: "All the things" 
}, 

{ 
    task: "crying", 
    type: "celebration", 
    startTime: "2013-2-13", 
    endTime: "2013-2-16" 
}, 

]; 

var dateFormat = d3.time.format("%Y-%m-%d"); 

var timeScale = d3.time.scale() 
     .domain([d3.min(taskArray, function(d) {return dateFormat.parse(d.startTime);}), 
       d3.max(taskArray, function(d) {return dateFormat.parse(d.endTime);})]) 
     .range([0,w-150]); 

var categories = new Array(); 

for (var i = 0; i < taskArray.length; i++){ 
    categories.push(taskArray[i].type); 
} 

var catsUnfiltered = categories; //for vert labels 

categories = checkUnique(categories); 


makeGant(taskArray, w, h); 

var title = svg.append("text") 
       .text("Gantt Chart Process") 
       .attr("x", w/2) 
       .attr("y", 25) 
       .attr("text-anchor", "middle") 
       .attr("font-size", 18) 
       .attr("fill", "#009FFC"); 



function makeGant(tasks, pageWidth, pageHeight){ 

var barHeight = 20; 
var gap = barHeight + 4; 
var topPadding = 75; 
var sidePadding = 75; 

var colorScale = d3.scale.linear() 
    .domain([0, categories.length]) 
    .range(["#00B9FA", "#F95002"]) 
    .interpolate(d3.interpolateHcl); 

makeGrid(sidePadding, topPadding, pageWidth, pageHeight); 
drawRects(tasks, gap, topPadding, sidePadding, barHeight, colorScale, pageWidth, pageHeight); 
vertLabels(gap, topPadding, sidePadding, barHeight, colorScale); 

} 


function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h){ 

var bigRects = svg.append("g") 
    .selectAll("rect") 
    .data(theArray) 
    .enter() 
    .append("rect") 
    .attr("x", 0) 
    .attr("y", function(d, i){ 
     return i*theGap + theTopPad - 2; 
    }) 
    .attr("width", function(d){ 
     return w-theSidePad/2; 
    }) 
    .attr("height", theGap) 
    .attr("stroke", "none") 
    .attr("fill", function(d){ 
    for (var i = 0; i < categories.length; i++){ 
     if (d.type == categories[i]){ 
      return d3.rgb(theColorScale(i)); 
     } 
    } 
    }) 
    .attr("opacity", 0.2); 


    var rectangles = svg.append('g') 
    .selectAll("rect") 
    .data(theArray) 
    .enter(); 


    var innerRects = rectangles.append("rect") 
      .attr("rx", 3) 
      .attr("ry", 3) 
      .attr("x", function(d){ 
       return timeScale(dateFormat.parse(d.startTime)) + theSidePad; 
       }) 
      .attr("y", function(d, i){ 
       return i*theGap + theTopPad; 
      }) 
      .attr("width", function(d){ 
       return (timeScale(dateFormat.parse(d.endTime))-timeScale(dateFormat.parse(d.startTime))); 
      }) 
      .attr("height", theBarHeight) 
      .attr("stroke", "none") 
      .attr("fill", function(d){ 
       for (var i = 0; i < categories.length; i++){ 
        if (d.type == categories[i]){ 
        return d3.rgb(theColorScale(i)); 
        } 
       } 
      }) 


     var rectText = rectangles.append("text") 
       .text(function(d){ 
       return d.task; 
       }) 
       .attr("x", function(d){ 
       return (timeScale(dateFormat.parse(d.endTime))-timeScale(dateFormat.parse(d.startTime)))/2 + timeScale(dateFormat.parse(d.startTime)) + theSidePad; 
       }) 
       .attr("y", function(d, i){ 
        return i*theGap + 14+ theTopPad; 
       }) 
       .attr("font-size", 11) 
       .attr("text-anchor", "middle") 
       .attr("text-height", theBarHeight) 
       .attr("fill", "#fff"); 


rectText.on('mouseover', function(e) { 
// console.log(this.x.animVal.getItem(this)); 
       var tag = ""; 

     if (d3.select(this).data()[0].details != undefined){ 
      tag = "Task: " + d3.select(this).data()[0].task + "<br/>" + 
       "Type: " + d3.select(this).data()[0].type + "<br/>" + 
       "Starts: " + d3.select(this).data()[0].startTime + "<br/>" + 
       "Ends: " + d3.select(this).data()[0].endTime + "<br/>" + 
       "Details: " + d3.select(this).data()[0].details; 
     } else { 
      tag = "Task: " + d3.select(this).data()[0].task + "<br/>" + 
       "Type: " + d3.select(this).data()[0].type + "<br/>" + 
       "Starts: " + d3.select(this).data()[0].startTime + "<br/>" + 
       "Ends: " + d3.select(this).data()[0].endTime; 
     } 
     var output = document.getElementById("tag"); 

      var x = this.x.animVal.getItem(this) + "px"; 
      var y = this.y.animVal.getItem(this) + 25 + "px"; 

     output.innerHTML = tag; 
     output.style.top = y; 
     output.style.left = x; 
     output.style.display = "block"; 
     }).on('mouseout', function() { 
     var output = document.getElementById("tag"); 
     output.style.display = "none"; 
      }); 


innerRects.on('mouseover', function(e) { 
//console.log(this); 
     var tag = ""; 

     if (d3.select(this).data()[0].details != undefined){ 
      tag = "Task: " + d3.select(this).data()[0].task + "<br/>" + 
       "Type: " + d3.select(this).data()[0].type + "<br/>" + 
       "Starts: " + d3.select(this).data()[0].startTime + "<br/>" + 
       "Ends: " + d3.select(this).data()[0].endTime + "<br/>" + 
       "Details: " + d3.select(this).data()[0].details; 
     } else { 
      tag = "Task: " + d3.select(this).data()[0].task + "<br/>" + 
       "Type: " + d3.select(this).data()[0].type + "<br/>" + 
       "Starts: " + d3.select(this).data()[0].startTime + "<br/>" + 
       "Ends: " + d3.select(this).data()[0].endTime; 
     } 
     var output = document.getElementById("tag"); 

     var x = (this.x.animVal.value + this.width.animVal.value/2) + "px"; 
     var y = this.y.animVal.value + 25 + "px"; 

     output.innerHTML = tag; 
     output.style.top = y; 
     output.style.left = x; 
     output.style.display = "block"; 
     }).on('mouseout', function() { 
     var output = document.getElementById("tag"); 
     output.style.display = "none"; 

}); 



} 


function makeGrid(theSidePad, theTopPad, w, h){ 

var xAxis = d3.svg.axis() 
    .scale(timeScale) 
    .orient('bottom') 
    .ticks(d3.time.days, 1) 
    .tickSize(-h+theTopPad+20, 0, 0) 
    .tickFormat(d3.time.format('%d %b')); 

var grid = svg.append('g') 
    .attr('class', 'grid') 
    .attr('transform', 'translate(' +theSidePad + ', ' + (h - 50) + ')') 
    .call(xAxis) 
    .selectAll("text") 
      .style("text-anchor", "middle") 
      .attr("fill", "#000") 
      .attr("stroke", "none") 
      .attr("font-size", 10) 
      .attr("dy", "1em"); 
} 

function vertLabels(theGap, theTopPad, theSidePad, theBarHeight, theColorScale){ 
    var numOccurances = new Array(); 
    var prevGap = 0; 

    for (var i = 0; i < categories.length; i++){ 
    numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)]; 
    } 

    var axisText = svg.append("g") //without doing this, impossible to put grid lines behind text 
    .selectAll("text") 
    .data(numOccurances) 
    .enter() 
    .append("text") 
    .text(function(d){ 
    return d[0]; 
    }) 
    .attr("x", 10) 
    .attr("y", function(d, i){ 
    if (i > 0){ 
     for (var j = 0; j < i; j++){ 
      prevGap += numOccurances[i-1][1]; 
     // console.log(prevGap); 
      return d[1]*theGap/2 + prevGap*theGap + theTopPad; 
     } 
    } else{ 
    return d[1]*theGap/2 + theTopPad; 
    } 
    }) 
    .attr("font-size", 11) 
    .attr("text-anchor", "start") 
    .attr("text-height", 14) 
    .attr("fill", function(d){ 
    for (var i = 0; i < categories.length; i++){ 
     if (d[0] == categories[i]){ 
     // console.log("true!"); 
      return d3.rgb(theColorScale(i)).darker(); 
     } 
    } 
    }); 

} 

//from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript 
function checkUnique(arr) { 
    var hash = {}, result = []; 
    for (var i = 0, l = arr.length; i < l; ++i) { 
     if (!hash.hasOwnProperty(arr[i])) { //it works with objects! in FF, at least 
      hash[ arr[i] ] = true; 
      result.push(arr[i]); 
     } 
    } 
    return result; 
} 

//from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array 
function getCounts(arr) { 
    var i = arr.length, // var to loop over 
     obj = {}; // obj to store results 
    while (i) obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences 
    return obj; 
} 

// get specific from everything 
function getCount(word, arr) { 
    return getCounts(arr)[word] || 0; 
}