了解d3js中的函数

时间:2015-07-21 04:57:19

标签: d3.js

有人可以解释一下这个功能:

var transitions = function () 
                {
                    return states.reduce(function (initial, state) {
                        return initial.concat(
                                state.transitions.map(function (transition) {
                                    return {source: state, transition: transition};
                                })
                                );
                    }, []);
                };

和这一行:var gTransitions = svg.append('g').selectAll("path.transition"); - 如何选择path.transition?

我是新的d3和javascript,我在项目的这一点上真的很困难。

上面的代码片段取自以下代码。我已经发表评论说" QUESTION1"和"问题2"找到它。

window.onload = function ()
            {
                var radius = 40;

                window.states = [
                    {x: 43, y: 67, label: "first", transitions: []},
                    {x: 340, y: 150, label: "second", transitions: []},
                    {x: 200, y: 250, label: "third", transitions: []}
                ];

                window.svg = d3.select('body')
                        .append("svg")
                        .attr("width", "960px")
                        .attr("height", "500px");

                // define arrow markers for graph links
                svg.append('svg:defs').append('svg:marker')
                        .attr('id', 'end-arrow')
                        .attr('viewBox', '0 -5 10 10')
                        .attr('refX', 4)
                        .attr('markerWidth', 8)
                        .attr('markerHeight', 8)
                        .attr('orient', 'auto')
                        .append('svg:path')
                        .attr('d', 'M0,-5L10,0L0,5')
                        .attr('class', 'end-arrow')
                        ;


                // line displayed when dragging new nodes
                var drag_line = svg.append('svg:path')
                        .attr({
                            'class': 'dragline hidden',
                            'd': 'M0,0L0,0'
                        })
                        ;
                //QUESTION1
                var gTransitions = svg.append('g').selectAll("path.transition");
                var gStates = svg.append("g").selectAll("g.state");


                //QUESTION2
                var transitions = function () 
                {
                    return states.reduce(function (initial, state) {
                        return initial.concat(
                                state.transitions.map(function (transition) {
                                    return {source: state, transition: transition};
                                })
                                );
                    }, []);
                };

                var transformTransitionEndpoints = function (d, i) {
                    var endPoints = d.endPoints();

                    var point = [
                        d.type == 'start' ? endPoints[0].x : endPoints[1].x,
                        d.type == 'start' ? endPoints[0].y : endPoints[1].y
                    ];

                    return "translate(" + point + ")";
                }

                var transformTransitionPoints = function (d, i) {
                    return "translate(" + [d.x, d.y] + ")";
                }

                var computeTransitionPath = (function () {
                    var line = d3.svg.line()
                            .x(function (d, i) {
                                return d.x;
                            })
                            .y(function (d, i) {
                                return d.y;
                            })
                            .interpolate("cardinal");

                    return function (d) {

                        var source = d.source,
                        target = d.transition.points.length && d.transition.points[0] || d.transition.target,
                        deltaX = target.x - source.x,
                        deltaY = target.y - source.y,
                        dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
                        normX = deltaX / dist,
                        normY = deltaY / dist,
                        sourcePadding = radius + 4, //d.left ? 17 : 12,
                        sourceX = source.x + (sourcePadding * normX),
                        sourceY = source.y + (sourcePadding * normY);

                        source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
                        target = d.transition.target;
                        deltaX = target.x - source.x;
                        deltaY = target.y - source.y;
                        dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                        normX = deltaX / dist;
                        normY = deltaY / dist;
                        targetPadding = radius + 8;//d.right ? 17 : 12,
                        targetX = target.x - (targetPadding * normX);
                        targetY = target.y - (targetPadding * normY);

                        var points =
                                [{x: sourceX, y: sourceY}].concat(
                                d.transition.points,
                                [{x: targetX, y: targetY}]
                                )
                                ;

                        var l = line(points);

                        return l;
                    };
                })();

                var dragPoint = d3.behavior.drag()
                        .on("drag", function (d, i) {
                            console.log("transitionmidpoint drag");
                            var gTransitionPoint = d3.select(this);

                            gTransitionPoint.attr("transform", function (d, i) {
                                d.x += d3.event.dx;
                                d.y += d3.event.dy;
                                return "translate(" + [d.x, d.y] + ")"
                            });

                            // refresh transition path
                            gTransitions.selectAll("path").attr('d', computeTransitionPath);
                            // refresh transition endpoints
                            gTransitions.selectAll("circle.endpoint").attr({
                                transform: transformTransitionEndpoints
                            });

                            // refresh transition points
                            gTransitions.selectAll("circle.point").attr({
                                transform: transformTransitionPoints
                            });

                            d3.event.sourceEvent.stopPropagation();
                        });

                var renderTransitionMidPoints = function (gTransition) {
                    gTransition.each(function (transition) {
                        var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
                            return transition.transition.points.indexOf(d);
                        });

                        transitionPoints.enter().append("circle")
                                .attr({
                                    'class': 'point',
                                    r: 4,
                                    transform: transformTransitionPoints
                                })
                                .call(dragPoint);
                        transitionPoints.exit().remove();
                    });
                };

                var renderTransitionPoints = function (gTransition) {
                    gTransition.each(function (d) {
                        var endPoints = function () {
                            var source = d.source,
                                    target = d.transition.points.length && d.transition.points[0] || d.transition.target,
                                    deltaX = target.x - source.x,
                                    deltaY = target.y - source.y,
                                    dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
                                    normX = deltaX / dist,
                                    normY = deltaY / dist,
                                    sourceX = source.x + (radius * normX),
                                    sourceY = source.y + (radius * normY);

                                    source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
                                    target = d.transition.target;
                                    deltaX = target.x - source.x;
                                    deltaY = target.y - source.y;
                                    dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                                    normX = deltaX / dist;
                                    normY = deltaY / dist;
                                    targetPadding = radius + 8;//d.right ? 17 : 12,
                                    targetX = target.x - (radius * normX);
                                    targetY = target.y - (radius * normY);

                            return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
                        };

                        var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
                            {endPoints: endPoints, type: 'start'},
                            {endPoints: endPoints, type: 'end'}
                        ]);

                        transitionEndpoints.enter().append("circle")
                                .attr({
                                    'class': function (d) {
                                        return 'endpoint ' + d.type;
                                    },
                                    r: 4,
                                    transform: transformTransitionEndpoints
                                })
                                ;
                        transitionEndpoints.exit().remove();
                    });
                };

                var renderTransitions = function () {
                    gTransition = gTransitions.enter().append('g')
                            .attr({
                                'class': 'transition'
                            })

                    gTransition.append('path')
                            .attr({
                                d: computeTransitionPath,
                                class: 'background'
                            })
                            .on({
                                dblclick: function (d, i) {
                                    gTransition = d3.select(d3.event.target.parentElement);
                                    if (d3.event.ctrlKey) {
                                        var p = d3.mouse(this);

                                        gTransition.classed('selected', true);
                                        d.transition.points.push({x: p[0], y: p[1]});

                                        renderTransitionMidPoints(gTransition, d);
                                        gTransition.selectAll('path').attr({
                                            d: computeTransitionPath
                                        });
                                    } else {
                                        var gTransition = d3.select(d3.event.target.parentElement),
                                                transition = gTransition.datum(),
                                                index = transition.source.transitions.indexOf(transition.transition);

                                        transition.source.transitions.splice(index, 1)
                                        gTransition.remove();

                                        d3.event.stopPropagation();
                                    }
                                }
                            });

                    gTransition.append('path')
                            .attr({
                                d: computeTransitionPath,
                                class: 'foreground'
                            });

                    renderTransitionPoints(gTransition);
                    renderTransitionMidPoints(gTransition);

                    gTransitions.exit().remove();
                };

                var renderStates = function () {
                    var gState = gStates.enter()
                            .append("g")
                            .attr({
                                "transform": function (d) {
                                    return "translate(" + [d.x, d.y] + ")";
                                },
                                'class': 'state'
                            })
                            .call(drag);

                    gState.append("circle")
                            .attr({
                                r: radius + 4,
                                class: 'outer'
                            })
                            .on({
                                mousedown: function (d) {
                                    console.log("state circle outer mousedown");
                                    startState = d, endState = undefined;

                                    // reposition drag line
                                    drag_line
                                            .style('marker-end', 'url(#end-arrow)')
                                            .classed('hidden', false)
                                            .attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);

                                    // force element to be an top
                                    this.parentNode.parentNode.appendChild(this.parentNode);
                                    //d3.event.stopPropagation();
                                },
                                mouseover: function () {
                                    svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
                                },
                                mouseout: function () {
                                    svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
                                    //$( this).popover( "hide");
                                }
                            });

                    gState.append("circle")
                            .attr({
                                r: radius,
                                class: 'inner'
                            })
                            .on({
                                mouseover: function () {
                                    svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
                                },
                                mouseout: function () {
                                    svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
                                },
                            });
                };

                var startState, endState;
                var drag = d3.behavior.drag()
                        .on("drag", function (d, i) {
                            console.log("drag");
                            if (startState) {
                                return;
                            }

                            var selection = d3.selectAll('.selected');

                            // if dragged state is not in current selection
                            // mark it selected and deselect all others
                            if (selection[0].indexOf(this) == -1) {
                                selection.classed("selected", false);
                                selection = d3.select(this);
                                selection.classed("selected", true);
                            }

                            // move states
                            selection.attr("transform", function (d, i) {
                                d.x += d3.event.dx;
                                d.y += d3.event.dy;
                                return "translate(" + [d.x, d.y] + ")"
                            });

                            // move transistion points of each transition 
                            // where transition target is also in selection
                            var selectedStates = d3.selectAll('g.state.selected').data();
                            var affectedTransitions = selectedStates.reduce(function (array, state) {
                                return array.concat(state.transitions);
                            }, [])
                                    .filter(function (transition) {
                                        return selectedStates.indexOf(transition.target) != -1;
                                    });
                            affectedTransitions.forEach(function (transition) {
                                for (var i = transition.points.length - 1; i >= 0; i--) {
                                    var point = transition.points[i];
                                    point.x += d3.event.dx;
                                    point.y += d3.event.dy;
                                }
                            });

                            // reappend dragged element as last 
                            // so that its stays on top 
                            selection.each(function () {
                                this.parentNode.appendChild(this);
                            });

                            // refresh transition path
                            gTransitions.selectAll("path").attr('d', computeTransitionPath);

                            // refresh transition endpoints
                            gTransitions.selectAll("circle.endpoint").attr({
                                transform: transformTransitionEndpoints
                            });
                            // refresh transition points
                            gTransitions.selectAll("circle.point").attr({
                                transform: transformTransitionPoints
                            });

                            d3.event.sourceEvent.stopPropagation();
                        })
                        .on("dragend", function (d) {
                            console.log("dragend");
                            // needed by FF
                            drag_line.classed('hidden', true)
                                    .style('marker-end', '');

                            if (startState && endState) {
                                startState.transitions.push({label: "transition label 1", points: [], target: endState});
                                update();
                            }
                            startState = undefined;
                            d3.event.sourceEvent.stopPropagation();
                        });

                svg.on({
                    mousedown: function () {
                        console.log("mousedown", d3.event.target);
                        if (d3.event.target.tagName == 'svg') {
                            if (!d3.event.ctrlKey) {
                                d3.selectAll('g.selected').classed("selected", false);
                            }
                            var p = d3.mouse(this);
                        }
                    },
                    mousemove: function () {
                        var p = d3.mouse(this);
                            // update drag line
                            drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
                            var state = d3.select('g.state .inner.hover');
                            endState = (!state.empty() && state.data()[0]) || undefined;
                    },
                    mouseup: function () {
                        console.log("mouseup");
                        // remove temporary selection marker class
                        d3.selectAll('g.state.selection').classed("selection", false);
                    },
                    mouseout: function () 
                    {
                        if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
                            // remove temporary selection marker class
                            d3.selectAll('g.state.selection').classed("selection", false);
                        }
                    }
                });

                update();

                function update() {
                    gStates = gStates.data(states, function (d) {
                        return states.indexOf(d);
                    });
                    renderStates();

                    var _transitions = transitions();
                    gTransitions = gTransitions.data(_transitions, function (d) {
                        return _transitions.indexOf(d);
                    });
                    renderTransitions();
                }
                ;
            };

1 个答案:

答案 0 :(得分:2)

我认为这是来自http://bl.ocks.org/lgersman/5370827

背景

states (= window.states)是一个状态对象数组(在您的情况下为3)。每个状态对象都有一个属性 transitions (表示对此状态下其他状态的可能更改),这是一个数组。

问题1

这使用Array原型的reduce,concat和map方法构建一个函数,该函数使用状态数组中的转换数组返回{ source: state, transition: transition }形式的对象数组。

第一层非常简单 - 只是一个功能定义。您最终使用var _transitions = transition();

调用它
var transitions = function () {
    return ...
};

请注意,每个调用都会根据调用函数时存在的状态/转换返回列表。

第二层通过连接第三层的数组片段来构建一个数组。从文档(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)中,reduce有效地从数组中获取单个值。

在我们的例子中,单值是一个通过连接数组片段构建的更大的数组。 reduce函数的第二个参数是初始值(在本例中为空数组)

return states.reduce(function (initial, state) {
    return initial.concat(
        ...
    );
}, []);

所以我们先传入一个空数组。使用状态的第一个元素(即状态[0])的第三层(在上面的部分中)的输出被连接到它以构建新的数组。然后将这个新数组与第三层的第二个输出连接起来(即使用状态[1]),依此类推

第3层是一张简单的地图(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)。对于状态中的每个转换数组条目,它返回一个{ source: state, transition: transition }形式的对象,使用它来构建一个数组(我们在上面看到的第二层使用它)

state.transitions.map(function (transition) {
    return { source: state, transition: transition };
})

所以,如果我们要为“第一”状态追踪它并假设你有2个转换条目(你的代码有一个空数组,但原始例子插入了几个转换),你会得到像< / p>

[
    {
        source: <<"first" state object>>
        transition: <<transition1a of "first" state - from it's transition array, 1st element>>
    },
    {
        source: <<"first" state object>>
        transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
    },
]

将这个提升到第二层,你会得到类似的东西(假设状态“第二个”有3个过渡发出)

[
    {
        source: <<"first" state object>>
        transition: <<transition1a of "first" state - from it's transition array, 1st element>>
    },
    {
        source: <<"first" state object>>
        transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
    },
    {
        source: <<"second" state object>>
        transition: <<transition2a of "second" state - from it's transition array, 1st element>>
    },
    {
        source: <<"second" state object>>
        transition: <<transition2b of "second" state - from it's transition array, 2nd element>>
    },
    {
        source: <<"second" state object>>
        transition: <<transition2c of "second" state - from it's transition array, 3rd element>>
    },
    ...
    ... and so on for all the states
]

第一层实际上是一个在调用时完成上述所有步骤的功能。

问题2

这有效地构建了d3选择(参见https://github.com/mbostock/d3/wiki/Selections) - 选择的d3 data来自第一个问题的输出。代码的最后有这个链接

gTransitions = gTransitions.data(_transitions, function (d) {
    return _transitions.indexOf(d);
});

通过调用transitions()设置_transitions;就在那条线上方。

然后使用此d3选择作为d3选择(通常使用enter()/ exit())来更新svg元素DOM。如果您搜索gTransitions.enter()gTransitions.exit(),您可以找到保持svg DOM更新的相关代码位。请注意,enter()涉及许多步骤(附加g,设置它的类,附加行为,附加g的路径......)

第一次调用update()函数负责将DOM同步到初始数据(在您的情况下,因为您的转换属性是空数组,所以没有创建任何内容)。

随后,DOM事件处理程序更新各个状态的转换数组,并在处理程序结束时调用update()函数以重新附加更新的数据(即transition()调用的输出),从而驱动创建/删除转换的DOM元素(通过调用renderTransitions()) - 这些实际上是(state)svg circle之间的svg路径