【发布时间】:2021-03-08 04:40:58
【问题描述】:
我真的需要一些帮助。我正在尝试制作自己的时间轴图表(与谷歌时间轴相同)。它已经可以工作了,但我不知道如何创建一个按类型对标签进行分组的函数。我对这个函数进行了硬编码,它适用于 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>
【问题讨论】:
-
请将您的代码缩减为可运行的minimal reproducible example。如果您的问题只是其中的一小部分,那么 400 行 javascript 就需要我们解析很多!另外,请给我们一些关于“按类型分组标签”的意思的指示
-
您似乎对何时使用 vanilla JS 以及何时使用 d3JS 感到困惑。不再需要使用
style和innerHtml。您还需要确保您阅读了d3 v3 documentation 而不是后面的。例如d3中.on("mouseover"的参数为d,你尝试使用d3.select(this).data()[0].name获取的数据,你可以直接替换为d.name -
鲁本,你是对的。我刚开始使用 D3,我有点迷茫。感谢您的建议。源代码很长,因为我想让你看到整个应用程序。我想你会更容易理解我的意思。
这里是我需要帮助的地方var nRows = d3.max(taskArray, (d) => d.type === "celebration" ? d.yIndex + 1 : null );
标签: javascript jquery arrays sorting d3.js