有人可以解释一下这个功能:
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();
}
;
};
答案 0 :(得分:2)
我认为这是来自http://bl.ocks.org/lgersman/5370827。
states (= window.states)是一个状态对象数组(在您的情况下为3)。每个状态对象都有一个属性 transitions (表示对此状态下其他状态的可能更改),这是一个数组。
这使用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
]
第一层实际上是一个在调用时完成上述所有步骤的功能。
这有效地构建了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路径