block by timelyportfolio 48be457b8cdcea8fecb8d5d8fb53aee1

dc.js and networkD3 with crosstalk

Full Screen

Built with blockbuilder.org


for the 2-way version, see this block


dc.js is perfect for experimenting with crosstalk. In this experiment, we use a dc.js heatmap to filter links from a networkD3 force network htmlwidget.

R Code to Replicate

library(htmltools)
library(crosstalk)
library(igraph)
library(networkD3)

# Use igraph to make the graph and find membership
karate <- make_graph("Zachary")

# example from ?networkD3::igraph_to_networkD3
karate <- make_graph("Zachary")
wc <- cluster_walktrap(karate)
members <- membership(wc)
karate_d3 <- igraph_to_networkD3(karate, group = members)
fn <- forceNetwork(
  Links = karate_d3$links, Nodes = karate_d3$nodes,
  Source = 'source', Target = 'target', NodeID = 'name',
  Group = 'group',
  height = 400, width = 400
)

browsable(
  attachDependencies(
    tagList(
      tags$head(
        tags$script(src="https://d3js.org/d3.v3.min.js"),
        tags$script(src="http://dc-js.github.io/dc.js/js/crossfilter.js"),
        tags$script(src="http://dc-js.github.io/dc.js/js/dc.js"),
        tags$link(
          href="http://dc-js.github.io/dc.js/css/dc.css",
          rel="stylesheet"
        )
      ),
      tags$div(id="chart-heat", style="display:inline-block;"),
      tags$div(as.tags(fn), style="display:inline-block"),
      tags$script(
        HTML(
          sprintf(
  "
  var data = %s;

  var ndx = crossfilter(data);
  var netDim = ndx.dimension(function(d){return [+d.from,+d.to]});
  var netGrp = netDim.group().reduceSum(function(d){return 1});

  var heatChart = dc.heatMap('#chart-heat')
  heatChart
    .width(400)
    .height(400)
    .dimension(netDim)
    .group(netGrp)
    .keyAccessor(function(d) { return +d.key[0]; })
    .valueAccessor(function(d) { return +d.key[1]; })
    .title(function(d) {
      return 'Source:   ' + d.key[0] + '\\n' +
      'Target:  ' + d.key[1] + '\\n'});// +
      //'Weight: ' + d.value;})

  heatChart.render();

  // we do not need crosstalk for this
  //   but use it anyways to demonstate and experiment
  //   with crosstalk as our holder for filter state
  var ct_grp = crosstalk.group('grp1');
  var ct_filter = crosstalk.filter.createHandle(ct_grp);

  // add a filter listener on our dc.js heatmap
  heatChart.on('filtered',function(chart){
    ct_filter.set(chart.filters());
  })

  // now make a function to highlight selected nodes and links
  function highlightForce(filters){
    var force_svg = d3.select('.forceNetwork svg');
    var force_nodes = force_svg.selectAll('.node');
    var force_links = force_svg.selectAll('.link');

    var sources = d3.zip.apply([],ct_filter._filterSet.value)[0];
    var targets = d3.zip.apply([],ct_filter._filterSet.value)[1];

    sources = sources ? sources : [];
    targets = targets ? targets : [];

    var matched = function(link){
      var found = false;
      for(i=0; i< sources.length; i++){
        if(sources[i] == link.source.name && targets[i] == link.target.name){
          found = true;
          break;
        }
      }
      return found;
    };

    force_links.each(function(link){
      if(matched(link)){
        d3.select(this).style('stroke-width',5);
      } else {
        d3.select(this).style('stroke-width',1);
      }
    });
  }

  // when crosstalk filter changes highlight
  ct_filter.on('change',function(val){
    highlightForce(val.value);
  })
",
            jsonlite::toJSON(
              get.data.frame(karate),
              dataframe="rows"
            )
          )
        )
      )
    ),
    crosstalk::crosstalkLibs()
  )
)