d3.js - understanding functions in d3js -


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