我不确定我的标题是否准确,所以让我描述一下我要解决的问题。首先,这是一个有效的jsFiddle
如果用户选项/ alt-拖动蓝色圆圈,他们就可以移动“事件框” - 这是由group
,rect
{{circle
组成的text
1}}元素和line
。在下图所示的示例中,该行的x2/y2
终点应固定为其时间轴日期。例如,即使重新定位“事件框”,“2003年1月1日”的红线也应始终指向该日期。
我知道如何将线点直接指向日期 - 但是当移动“事件框”时,它将不再指向正确的日期。我使用两个日期属性:
这是定位该行的代码。
d3.select(this).select("line.leaderLine")
.attr("x1", horizontal_margin)
.attr("y1", 0)
.attr("x2", function (d, i) {
return x(new Date(d.actualDate));
})
.attr("y2", function (d, i) {
return cV - d.yPos;
});
任何帮助或提示非常感谢 - 我一直在努力解决这个问题。
var horizontal_margin = 10;
var vertical_margin = 10;
var spacer = 10;
var svgWidth = 700 - (horizontal_margin * 2)
var svgHeight = 500 - (vertical_margin * 2);
var margin_content = {
top: vertical_margin,
right: svgWidth - horizontal_margin,
bottom: svgHeight - 20,
left: horizontal_margin
};
var margin_scrubber = {
top: margin_content.bottom + spacer,
right: horizontal_margin,
bottom: svgHeight - spacer,
left: horizontal_margin
};
var tlWidth = margin_content.right - margin_content.left;
var tlHeight = margin_content.bottom - margin_content.top;
var cV = (margin_content.bottom - margin_content.top) / 2;
var svg;
var x;
var x2;
var xAxis_focus;
var xAxis_scrubber;
var zoom;
var content;
var scrubber;
var brush;
// transform used for dragging & zooming
// var zoomTrans = d3.zoomIdentity;
var drag_this = d3.drag().subject(this)
.on('start', function (d) {
if (d3.event.sourceEvent.altKey == true) {
if (d.x1) {
d.x1 = d3.event.x - d.xt;
d.y1 = d3.event.y - d.yt;
} else {
d.x1 = d3.event.x;
d.y1 = d3.event.y;
}
}
})
.on('drag', function (d) {
if (d3.event.sourceEvent.altKey == true) {
d3.select(this)
.attr("transform", "translate(" + (d3.event.x) + "," + (d3.event.y) + ")");
d.xt = d3.event.x - d.x1;
d.yt = d3.event.y - d.y1;
d.yPos = d3.event.y;
d.positionDate = x.invert(d3.event.x);
d3.select(this).select("line.leaderLine")
.attr("x1", horizontal_margin)
.attr("y1", 0)
.attr("x2", function (d, i) {
return x(new Date(d.actualDate));
})
.attr("y2", function (d, i) {
return cV - d.yPos;
});
}
});
var eventData = {
"events": [
{
"actualDate": "January 1 2002",
"positionDate": 0,
"eventText": "This is the first event",
"xOffset": 0,
"yPos": 80,
"state": true
},
{
"actualDate": "January 1 2003",
"positionDate": 0,
"eventText": "Another event",
"xOffset": 0,
"yPos": 300,
"state": true
}
]
};
loadTimeline(eventData);
function loadTimeline(data) {
for (let index = 0; index < data.events.length; ++index) {
let e = data.events[index];
if (e.positionDate == 0) {
e.positionDate = new Date(e.actualDate)
} else {
e.positionDate = new Date(e.positionDate)
}
}
svg = d3.select("body").append("svg")
.attr('width', svgWidth)
.attr('height', svgHeight)
.attr("transform", "translate(" + margin_content.left + "," + margin_content.top + ")");
var parseDate = d3.timeParse("%B %d %Y");
x = d3.scaleTime().range([0, tlWidth - (horizontal_margin * 2)]);
x2 = d3.scaleTime().range([0, tlWidth - (horizontal_margin * 2)]);
xAxis_focus = d3.axisBottom(x);
xAxis_scrubber = d3.axisBottom(x2);
zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[tlWidth - (horizontal_margin * 4), tlHeight]
])
.extent([
[0, 0],
[tlWidth - (horizontal_margin * 4), tlHeight]
])
.on("zoom", zoomed);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", margin_content.right - margin_content.left)
.attr("height", margin_content.bottom - margin_content.top);
svg.append("rect")
.attr("class", "zoom")
.attr("width", margin_content.right - margin_content.left)
.attr("height", margin_content.bottom - margin_content.top)
.style("stroke", "red")
.attr("transform", "translate(" + margin_content.left + "," + margin_content.top + ")")
.call(zoom)
.on("dblclick.zoom", null);
content = svg.append("g")
.attr("class", "content")
.attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + margin_content.left + "," + margin_content.top + ")");
scrubber = svg.append("g")
.attr("class", "scrubber")
.attr("transform", "translate(" + margin_scrubber.left + "," + margin_scrubber.top + ")");
brush = d3.brushX()
.extent([
[0, 0],
[tlWidth, 40]
])
.on("brush end", brushed);
var maxDate = d3.max(data.events, function (d) {
return d.positionDate;
})
var newDate = new Date(maxDate.getTime());
newDate.setYear(maxDate.getFullYear() + 2);
var pad = {
positionDate: newDate,
eventText: "pad"
}
data.events.push(pad)
x.domain(d3.extent(data.events, function (d, i) {
return d.positionDate;
}));
x2.domain(x.domain());
// **** event
var events = content.selectAll("g").data(data.events.filter(function (d, i) {
if (d.state == undefined) {
d.state = true;
}
return i < data.events.length - 1
}));
var newEvent = events.enter()
.append("g")
.attr("class", "event")
.call(drag_this);
newEvent.append("line")
.attr("class", "leaderLine")
.attr("x1", horizontal_margin)
.attr("y1", 0)
.attr("x2", horizontal_margin)
.attr("y2", function (d, i) {
return cV - d.yPos;
});
newEvent.append("rect")
.attr("class", "event-content")
.attr("x", horizontal_margin)
.attr("y", 15)
.attr("width", 160)
.attr("height", 120);
newEvent.append("circle")
.attr("class", "event-circle")
.attr("r", 10)
.attr("cx", horizontal_margin)
.attr("cy", 0)
.on("click", eventClick);
newEvent.append("text")
.attr('y', 5)
.attr("x", horizontal_margin + 15)
.attr("class", "event-text")
.text(function (d) {
return d.actualDate;
});
//*** end event
content.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(" + margin_content.left + "," + cV + ")")
.call(xAxis_focus);
// scrubber
scrubber.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(" + margin_scrubber.left + "," + (margin_scrubber.bottom - margin_scrubber.top) / 2 + ")")
.call(xAxis_scrubber);
scrubber.append("g")
.attr("class", "brush")
.attr("transform", "translate(" + margin_scrubber.left + "," + 0 + ")")
.call(brush)
.call(brush.move, x.range());
scrubber.append("g")
.selectAll("rect.event-marker").data(data.events.filter(function (d, i) {
return i < data.events.length - 1;
}))
.enter()
.append("rect")
.attr("class", "event-marker")
.attr("x", function (d, i) {
return x2(d.positionDate) + (margin_scrubber.left);
})
.attr("y", function (d, i) {
if (i % 2 == 0) {
return 5
} else {
return 20
}
})
.attr("width", 5)
.attr("height", 5);
}
var staggerLayout = function (i) {
if (i % 2 == 0) {
return 20
} else {
return 100
}
};
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
console.log('brushed');
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
content.select(".axis--x").call(xAxis_focus);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(tlWidth / (s[1] - s[0]))
.translate(-s[0], 0));
var events = content.selectAll("g.event")
.attr("transform", function (d, i) {
if (d.yPos == 0) {
d.yPos = staggerLayout(i);
}
return "translate(" + x(d.positionDate) + margin_content.left + "," + d.yPos + ")"
});
var leaderLines = content.selectAll("line.leaderLine")
.attr("x1", horizontal_margin)
.attr("y1", 0)
.attr("x2", function (d, i) {
return x(new Date(d.actualDate));
})
.attr("y2", function (d, i) {
return cV - d.yPos;
// return (margin_content.bottom - margin_content.top) / 2;
});
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
content.select(".axis--x").call(xAxis_focus);
scrubber.select(".brush").call(brush.move, x.range().map(t.invertX, t));
var events = content.selectAll("g.event")
.attr("transform", function (d, i) {
if (d.yPos == 0) {
d.yPos = staggerLayout(i);
}
return "translate(" + x(d.positionDate) + margin_content.left + "," + d.yPos + ")"
});
var leaderLines = content.selectAll("line.leaderLine")
.attr("x1", horizontal_margin)
.attr("y1", 0)
.attr("x2", function (d, i) {
return x(new Date(d.actualDate));
})
.attr("y2", function (d, i) {
return cV - d.yPos;
});
}
function eventClick(d, i) {
if (d3.event.altKey == false) {
console.log('d', i);
if (d.state == false) {
d3.select(this)
.classed("event-box", false)
.classed("event-box-hilite", true);
d.state = true;
d3.select(this.parentNode)
.selectAll('.event-content')
.style("visibility", "visible");
} else {
d3.select(this)
.classed("event-box", true)
.classed("event-box-hilite", false);
d.state = false;
d3.select(this.parentNode)
.selectAll('.event-content')
.style("visibility", "hidden");
}
}
}
答案 0 :(得分:0)
所以问题是如何在“全局”非组坐标中相对于动态变量定位组元素。
必须有一种更好,更少'hacky'的方法来做到这一点 - 但我不知道它是什么。方法如下:
selectAll
循环。不漂亮但是有效
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
content.select(".axis--x").call(xAxis_focus);
scrubber.select(".brush").call(brush.move, x.range().map(t.invertX, t));
var tmp = [];
var events = content.selectAll("g.event")
.attr("transform", function (d, i) {
if (d.yPos == 0) {
d.yPos = staggerLayout(i);
}
tmp.push([x(d.positionDate) + margin_content.left, d.yPos])
return "translate(" + x(d.positionDate) + margin_content.left + "," + d.yPos + ")"
});
var lines = content.selectAll("line.leader")
.attr("x1", function (d, i) {
return horizontal_margin + x(new Date(d.actualDate));
})
.attr("y1", cV)
.attr("x2", function (d, i) {
return tmp[i][0]
})
.attr("y2", function (d, i) {
return tmp[i][1]
});
}