如何根据日期动态定位线元素的一端?

时间:2017-06-26 16:44:38

标签: javascript d3.js

我不确定我的标题是否准确,所以让我描述一下我要解决的问题。首先,这是一个有效的jsFiddle

如果用户选项/ alt-拖动蓝色圆圈,他们就可以移动“事件框” - 这是由grouprect {{circle组成的text 1}}元素和line。在下图所示的示例中,该行的x2/y2终点应固定为其时间轴日期。例如,即使重新定位“事件框”,“2003年1月1日”的红线也应始终指向该日期。

我知道如何将线点直接指向日期 - 但是当移动“事件框”时,它将不再指向正确的日期。我使用两个日期属性:

  1. actualDate - 用于(错误地)确定行终点
  2. positionDate - 在用户重新定位“事件框”时更新
  3. 这是定位该行的代码。

          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;
                });
    

    任何帮助或提示非常感谢 - 我一直在努力解决这个问题。

    enter image description here

    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");
            }
        }
    }
    

1 个答案:

答案 0 :(得分:0)

所以问题是如何在“全局”非组坐标中相对于动态变量定位组元素。

必须有一种更好,更少'hacky'的方法来做到这一点 - 但我不知道它是什么。方法如下:

  1. 假设“引导线”不属于“事件框”组
  2. 将所需变量(组位置)存储在临时数组中
  3. 将这些值应用于“引导线:selectAll循环。
  4. 不漂亮但是有效

    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]
            });
    
    }