can 1 explain me function:
var transitions = function () { return states.reduce(function (initial, state) { return initial.concat( state.transitions.map(function (transition) { return {source: state, transition: transition}; }) ); }, []); }; and line: var gtransitions = svg.append('g').selectall("path.transition"); - how path.transition getting selected?
i new d3 , javascript , stuck @ point in project.
the above snippet taken out of below code. have put comments saying "question1" , "question2" find it.
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 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 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 not in current selection // mark selected , deselect 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 // transition target 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) { (var = transition.points.length - 1; >= 0; i--) { var point = transition.points[i]; point.x += d3.event.dx; point.y += d3.event.dy; } }); // reappend dragged element last // 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 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(); } ; };
i assume http://bl.ocks.org/lgersman/5370827.
background
states (=window.states) array of state objects (3 in case). each state object has property transitions (which represents possible changes other states state), array.
question 1
this uses reduce, concat , map method of array prototype build function returns array of objects of form { source: state, transition: transition } using transition arrays inside state array.
the 1st layer pretty simple - function definition. call using var _transitions = transition();
var transitions = function () { return ... }; note each call returns list based on states / transitions exist @ time function called.
the 2nd layer builds array concatenating array fragments 3rd layer. documentation (https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/array/reduce), reduce gets single value array.
in our case, single value larger array built concatenating array fragments. 2nd parameter reduce function intial value (in case empty array)
return states.reduce(function (initial, state) { return initial.concat( ... ); }, []); so first pass in empty array. output of 3rd layer (... in section above) using 1st element of states (i.e. states[0]) concatenated build new array. new array concatenated 2nd output of 3rd layer (i.e. using states[1]) , on
the 3rd layer simple map (https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/array/map). each transition array entry in state, returns object of form { source: state, transition: transition }, using build array (which used 2nd layer saw above)
state.transitions.map(function (transition) { return { source: state, transition: transition }; }) so, if trace "first" state , assuming had 2 transition entries (your code has empty array, original example inserts couple of transitions), you'd
[ { source: <<"first" state object>> transition: <<transition1a of "first" state - it's transition array, 1st element>> }, { source: <<"first" state object>> transition: <<transition1b of "first" state - it's transition array, 2nd element>> }, ] carrying 2nd layer, you'd (assuming state "second" had 3 transitions emanating it)
[ { source: <<"first" state object>> transition: <<transition1a of "first" state - it's transition array, 1st element>> }, { source: <<"first" state object>> transition: <<transition1b of "first" state - it's transition array, 2nd element>> }, { source: <<"second" state object>> transition: <<transition2a of "second" state - it's transition array, 1st element>> }, { source: <<"second" state object>> transition: <<transition2b of "second" state - it's transition array, 2nd element>> }, { source: <<"second" state object>> transition: <<transition2c of "second" state - it's transition array, 3rd element>> }, ... ... , on states ] and 1st layer function steps above when called.
question 2
this builds d3 selection (see https://github.com/mbostock/d3/wiki/selections) - selection's d3 data comes output of 1st question. end of code has link
gtransitions = gtransitions.data(_transitions, function (d) { return _transitions.indexof(d); }); _transitions being set call transitions(); in line above that.
this d3 selection used d3 selections (with enter() / exit()), update svg element dom. if search gtransitions.enter() , gtransitions.exit() can find related bits of code keep svg dom updated. note enter() involves number of steps (append g, set it's class, attach behaviour, append path g...)
the first time, update() function called takes care of syncing dom initial data (in case since transition properties empty arrays, nothing created).
subsequently, dom event handlers update respective states's transition arrays , update() function called @ end of handler reattach updated data (i.e. output of transition() call) , hence drive creation / removal of dom elements transitions (via call rendertransitions()) - these svg paths between (state) svg circles
Comments
Post a Comment