我真的需要一些帮助。我正在尝试制作我自己的时间线图(和谷歌时间线一样)。它已经工作了,但我不知道如何使一个函数按类型对标签进行分组。我硬编码了这个函数,它适用于3组/类型。我需要的是一个通用的功能,即使有100个标签,也可以对所有标签进行分组。
第二个问题是,在第二组和第三组中,如果是a.endTime >= b.startTime,则不在同一行。我会非常感谢你的帮助
这是我的代码:
var w = 800;
var h = 400;
var svg = d3
.select(".svg")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "svg");
//main array
var items = [{
task: "conceptualize",
type: "development",
startTime: "2013-1-28", //year/month/day
endTime: "2013-2-1",
number: 2,
},
{
task: "sketch",
type: "development",
startTime: "2013-2-6",
endTime: "2013-2-9",
number: 2,
},
{
task: "color profiles",
type: "development",
startTime: "2013-2-6",
endTime: "2013-2-9",
number: 2,
},
{
task: "HTML",
type: "coding",
startTime: "2013-2-2",
endTime: "2013-2-6",
number: 1,
},
{
task: "write the JS",
type: "coding",
startTime: "2013-2-1",
endTime: "2013-2-6",
number: 1,
},
{
task: "eat",
type: "celebration",
startTime: "2013-2-8",
endTime: "2013-2-13",
number: 0,
},
{
task: "crying",
type: "celebration",
startTime: "2013-2-13",
endTime: "2013-2-16",
number: 0,
},
];
//array sorting
items.sort((a, b) => {
return (
a.number - b.number || Date.parse(b.startTime) - Date.parse(a.startTime)
);
});
//here create new array and add index for every label
var taskArray = [];
var stack = [];
items.map((e) => {
var lane = stack.findIndex(
(s) => s.endTime <= e.startTime && s.startTime < e.startTime
);
var yIndex = lane === -1 ? stack.length : lane;
taskArray.push({
...e,
yIndex,
});
stack[yIndex] = e;
});
//here hardcoded grouping based on index and type
var nRows = d3.max(taskArray, (d) =>
d.type === "celebration" ? d.yIndex + 1 : null
);
//here hardcoded grouping based on index and type
var nRows2 = d3.max(taskArray, (d) =>
d.type === "development" ? (d.yIndex = d.yIndex + 1 + nRows) : null
);
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);
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
) {
svg
.append("g")
.selectAll("rect")
.data(theArray)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d, i) {
return d.yIndex * 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();
rectangles
.append("rect")
.attr("rx", 3)
.attr("ry", 3)
.attr("x", function(d) {
return timeScale(dateFormat.parse(d.startTime)) + theSidePad;
})
//here draw milestones depend on index
.attr("y", function(d, i) {
return d.yIndex * 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));
}
}
});
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 d.yIndex * theGap + 14 + theTopPad;
})
.attr("font-size", 11)
.attr("text-anchor", "middle")
.attr("text-height", theBarHeight)
.attr("fill", "#fff");
}
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;
}body {
background: #fff;
font-family: 'Open-Sans', sans-serif;
}
#container {
margin: 0 auto;
position: relative;
width: 800px;
overflow: visible;
}
.svg {
width: 800px;
height: 400px;
overflow: visible;
position: absolute;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}<div id="container">
<div class="svg"></div>
</div>
<script src="https://d3js.org/d3.v3.min.js"></script>
发布于 2020-11-25 13:28:47
您可以极大地简化代码,同时使其具有动态。除了我留下的注释之外,还有一些提示
categories;
d3.nest对类别进行分组,以获得用于着色的索引,只需使用第二个函数参数--每个类别一个g元素,g中的一个rect.background,以及符合该类别的任务。这样,您可以非常容易地删除类别,这样可以更容易地计算位置,并且只需要设置填充颜色一次。如果你有什么问题请告诉我
var w = 800;
var h = 400;
var svg = d3
.select(".svg")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "svg");
//main array
var items = [{
task: "conceptualize",
type: "development",
startTime: "2013-1-28", //year/month/day
endTime: "2013-2-1",
number: 2,
},
{
task: "sketch",
type: "development",
startTime: "2013-2-6",
endTime: "2013-2-9",
number: 2,
},
{
task: "color profiles",
type: "development",
startTime: "2013-2-6",
endTime: "2013-2-9",
number: 2,
},
{
task: "HTML",
type: "coding",
startTime: "2013-2-2",
endTime: "2013-2-6",
number: 1,
},
{
task: "write the JS",
type: "coding",
startTime: "2013-2-1",
endTime: "2013-2-6",
number: 1,
},
{
task: "eat",
type: "celebration",
startTime: "2013-2-8",
endTime: "2013-2-13",
number: 0,
},
{
task: "crying",
type: "celebration",
startTime: "2013-2-13",
endTime: "2013-2-16",
number: 0,
},
];
var dateFormat = d3.time.format("%Y-%m-%d");
items.forEach((e) => {
e.startTime = dateFormat.parse(e.startTime);
e.endTime = dateFormat.parse(e.endTime);
});
// Use d3.nest to group the items based on the category
// https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_nest
// You can use .sortKeys to sort by key, for example
var categories = d3.nest()
.key(d => d.type)
.sortValues((a, b) => a.startTime - b.startTime)
.rollup(function(events) {
// Here, we see if we can apply some useful logic
// In this case, we search for overlapping events so they can be placed
// in different lanes
events.forEach(function(e, i) {
// Look only at the preceding events
// Remember that the events have been sorted already
const overlappingEvents = events.slice(0, i)
.filter(other => other.endTime > e.startTime);
if(overlappingEvents.length > 0) {
e.level = d3.max(overlappingEvents, e => e.level) + 1;
} else {
e.level = 0;
}
});
return events;
})
.entries(items);
// Set for each category the required number of lanes
let offset = 0;
categories.forEach(c => {
c.lanes = d3.max(c.values, d => d.level) + 1;
c.offset = offset;
offset += c.lanes;
});
var timeScale = d3.time
.scale()
.domain([
d3.min(items, d => d.startTime),
d3.max(items, d => d.endTime)
])
.range([0, w - 150]);
makeGant(categories, w, h);
function makeGant(categories, 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(
categories,
gap,
topPadding,
sidePadding,
barHeight,
colorScale,
pageWidth,
pageHeight
);
vertLabels(gap, topPadding, sidePadding, barHeight, colorScale);
}
function drawRects(
categories,
theGap,
theTopPad,
theSidePad,
theBarHeight,
theColorScale,
w,
h
) {
const categoryGroups = svg
.append("g")
.selectAll("g")
.data(categories)
.enter()
.append("g")
.attr("transform", function(d, i) {
// Take the preceding categories and sum their required number of levels
return `translate(0, ${d.offset * theGap + theTopPad - 2})`;
})
// All children inherit this attribute
.attr("fill", function(d, i) {
return d3.rgb(theColorScale(i));
})
// Just draw one rectangle for all lanes of this category
categoryGroups
.append("rect")
.attr("class", "background")
.attr("width", function(d) {
return w - theSidePad / 2;
})
.attr("height", function(d) {
return theGap * d.lanes;
})
.attr("stroke", "none")
.attr("opacity", 0.2);
//
var rectangles = categoryGroups
.selectAll(".task")
.data(function(d) { return d.values; })
.enter();
rectangles
.append("rect")
.attr("class", "task")
.attr("rx", 3)
.attr("ry", 3)
.attr("x", function(d) {
return timeScale(d.startTime) + theSidePad;
})
//here draw milestones depend on index
.attr("y", function(d, i) {
return d.level * theGap;
})
.attr("width", function(d) {
return timeScale(d.endTime) - timeScale(d.startTime);
})
.attr("height", theBarHeight)
.attr("stroke", "none");
rectangles
.append("text")
.text(function(d) {
return d.task;
})
.attr("x", function(d) {
return (timeScale(d.endTime) - timeScale(d.startTime)) / 2 +
timeScale(d.startTime) + theSidePad;
})
.attr("y", function(d, i) {
return d.level * theGap + 14;
})
.attr("font-size", 11)
.attr("text-anchor", "middle")
.attr("text-height", theBarHeight)
.attr("fill", "#fff");
}
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 axisText = svg
.append("g") //without doing this, impossible to put grid lines behind text
.selectAll("text")
.data(categories) // one label per category
.enter()
.append("text")
.text(function(d) {
return d.key;
})
.attr("x", 10)
.attr("y", function(d, i) {
return (d.offset + d.lanes / 2) * theGap + theTopPad;
})
.attr("font-size", 11)
.attr("text-anchor", "start")
.attr("text-height", 14)
.attr("fill", function(d, i) {
return d3.rgb(theColorScale(i)).darker();
});
}body {
background: #fff;
font-family: 'Open-Sans', sans-serif;
}
#container {
margin: 0 auto;
position: relative;
width: 800px;
overflow: visible;
}
.svg {
width: 800px;
height: 400px;
overflow: visible;
position: absolute;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}<div id="container">
<div class="svg"></div>
</div>
<script src="https://d3js.org/d3.v3.js"></script>
https://stackoverflow.com/questions/65002755
复制相似问题