block by timelyportfolio 8c9a77da3e9ca57827a8801fb8a16ba9

vue d3 tree responding to d2b sunburst

Full Screen

As I progress to event handling and communication in vuejs, I discover that things get difficult very quickly, or the complexity moves beyond my limited capabilities. I was able to tie the d2b sunburst chart mouseover to my naive vue d3 treemap component. Thanks so much to the author of d2b Kevin Warne for his help in issue.

Code in R

library(treemap)
library(d3r)
library(htmltools)

# set up dependency for d2bjs chart library
d2b_dep <- htmltools::htmlDependency(
  name = "d2b",
  version = "0.5.1",
  src = c(href = "https://unpkg.com/d2b@0.5.1/build/"),
  script = "d2b.min.js"
)

d2b_vue_dep <- htmltools::htmlDependency(
  name = "d2b-vue",
  version = "1.0.11",
  src = c(href = "https://unpkg.com/vue-d2b@1.0.11/dist/"),
  script = "vue-d2b.min.js"
)

# our simple Vue d3 treemap component
template <- tag(
  "template",
  list(
    id = "d3treemap",
    tag(
      "svg",
      list(
        "v-bind:style"="styleObject",
        tag(
          "g",
          list(
            tag(
              "rect",
              list(
                "v-for" = "(node, index) in nodes",
                "v-if" = "node.depth === 2",
                "v-bind:x" = "node.x0",
                "v-bind:width" = "node.x1 - node.x0",
                "v-bind:y" =  "node.y0",
                "v-bind:height" = "node.y1 - node.y0",
                "v-bind:style" = "{fill: node.data.color ? node.data.color : color(node.parent.data.name)}"
              )
            )
          )
        )
      )
    )
  )
)

component <- tags$script(
"
Vue.component('treemap-component', {
  template: '#d3treemap',
  props: {
    tree: Object,
    sizefield: {
      type: String,
      default: 'size'
    },
    treewidth: {
      type: Number,
      default: 400
    },
    treeheight: {
      type: Number,
      default: 400
    },
    tile: {
      type: Function,
      default: d3.treemapSquarify
    },
    color: {
      type: Function,
        default: d3.scaleOrdinal(d3.schemeCategory10)
    }
  },
  computed: {
    styleObject: function() {
      return {width: this.treewidth, height: this.treeheight}
    },
    treemap: function() { return this.calculate_tree() },
    nodes: function() {
      var color = this.color;
      var nodes = [];
      this.treemap.each(function(d) {
        nodes.push(d);
      });
      return nodes;
    }
  },
  methods: {
    calculate_tree: function() {
      var sizefield = this.sizefield;
      var d3t = d3.hierarchy(this.tree)
        .sum(function(d) {
          return d[sizefield]
        });
      return d3.treemap()
        .size([this.treewidth, this.treeheight])
        .tile(this.tile)
        .round(true)
        .padding(1)(d3t)
    }
  }
});
"
)



app <- tags$script(HTML(
sprintf(
"
// try to keep color consistent across charts
//  so use global color function
var color = d3.scaleOrdinal(d3.schemeCategory20);

// careful here in a real application
//  set up a global/window store object to hold state
//  will be a simple object
var tree = %s;
var store = {
  tree: tree,
  filtered_tree: tree,
  size: 'x',
  width: 800,
  height: 600,
  tile: d3.treemapBinary,
  color: color,
  sunburstChartConfig: function(chart) {
    chart.label(function(d){return d.name});
    chart.color(function(d){return color(d.name);})
    chart.sunburst().size(function(d){return d.x});
  }
};

var app = new Vue({
  el: '#app',
  components: {
    'sunburst-chart': vued2b.ChartSunburst
  },
  data: store,
  methods: {
    sunburstChartRendered: function (el, chart) {
      var that = this;
      d3.select(el).selectAll('.d2b-sunburst-chart')
        .on('mouseover', function (d) {
          if(d3.event.target.classList[0] === 'd2b-sunburst-arc'){
            that.filtered_tree = d3.select(d3.event.target).datum().data;
          }
        });
    }
  }
})
",
d3r::d3_nest(
  treemap::random.hierarchical.data(depth=4),
  value_cols = "x"
)
  )
))


ui <- tagList(
  template,
  component,
  tags$div(
    id = "app",
    tags$div(
      style = "height:400px; width:400px; float:left;",
      tag(
        "sunburst-chart",
        list(
          ":data" = "tree",
          ":config" = "sunburstChartConfig",
          "@rendered" = "sunburstChartRendered"
        )
      )
    ),
    tags$div(
      style = "height:400px; width:400px; float:left;",
      tag(
        "treemap-component",
        list(":tree" = "filtered_tree",":sizefield"="'x'",":color" = "color") #use defaults
      )
    )
  ),
  app,
  html_dependency_vue(offline=FALSE,minified=FALSE),
  d3_dep_v4(offline=FALSE),
  d2b_dep,
  d2b_vue_dep
)

browsable(ui)

index.html


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/d3"></script>
<script src="https://unpkg.com/d2b@0.5.1/build/d2b.min.js"></script>
<script src="https://unpkg.com/vue-d2b@1.0.11/dist/vue-d2b.min.js"></script>

</head>
<body style="background-color:white;">
<template id="d3treemap">
  <svg v-bind:style="styleObject">
    <g>
      <rect v-for="(node, index) in nodes" v-if="node.depth === 2" v-bind:x="node.x0" v-bind:width="node.x1 - node.x0" v-bind:y="node.y0" v-bind:height="node.y1 - node.y0" v-bind:style="{fill: node.data.color ? node.data.color : color(node.parent.data.name)}"></rect>
    </g>
  </svg>
</template>
<script>
Vue.component('treemap-component', {
  template: '#d3treemap',
  props: {
    tree: Object,
    sizefield: {
      type: String,
      default: 'size'
    },
    treewidth: {
      type: Number,
      default: 400
    },
    treeheight: {
      type: Number,
      default: 400
    },
    tile: {
      type: Function,
      default: d3.treemapSquarify
    },
    color: {
      type: Function,
        default: d3.scaleOrdinal(d3.schemeCategory10)
    }
  },
  computed: {
    styleObject: function() {
      return {width: this.treewidth, height: this.treeheight}
    },
    treemap: function() { return this.calculate_tree() },
    nodes: function() {
      var color = this.color;
      var nodes = [];
      this.treemap.each(function(d) {
        nodes.push(d);
      });
      return nodes;
    }
  },
  methods: {
    calculate_tree: function() {
      var sizefield = this.sizefield;
      var d3t = d3.hierarchy(this.tree)
        .sum(function(d) {
          return d[sizefield]
        });
      return d3.treemap()
        .size([this.treewidth, this.treeheight])
        .tile(this.tile)
        .round(true)
        .padding(1)(d3t)
    }
  }
});
</script>
<div id="app">
  <div style="height:400px; width:400px; float:left;">
    <sunburst-chart :data="tree" :config="sunburstChartConfig" @rendered="sunburstChartRendered"></sunburst-chart>
  </div>
  <div style="height:400px; width:400px; float:left;">
    <treemap-component :tree="filtered_tree" :sizefield="&#39;x&#39;" :color="color"></treemap-component>
  </div>
</div>
<script>
// try to keep color consistent across charts
//  so use global color function
var color = d3.scaleOrdinal(d3.schemeCategory20);

// careful here in a real application
//  set up a global/window store object to hold state
//  will be a simple object
var tree = {"children":[{"name":"A","children":[{"name":"A.1","children":[{"name":"A.1.a","children":[{"name":"A.1.a.A","x":6.0048,"colname":"index4"},{"name":"A.1.a.B","x":5.6057,"colname":"index4"},{"name":"A.1.a.C","x":1.1462,"colname":"index4"},{"name":"A.1.a.D","x":0.0846,"colname":"index4"},{"name":"A.1.a.E","x":0.8213,"colname":"index4"}],"colname":"index3"},{"name":"A.1.b","children":[{"name":"A.1.b.A","x":0.4234,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"A.2","children":[{"name":"A.2.a","children":[{"name":"A.2.a.A","x":1.2096,"colname":"index4"},{"name":"A.2.a.B","x":0.5725,"colname":"index4"},{"name":"A.2.a.C","x":0.2986,"colname":"index4"},{"name":"A.2.a.D","x":0.7827,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"A.3","children":[{"name":"A.3.a","children":[{"name":"A.3.a.A","x":0.5993,"colname":"index4"},{"name":"A.3.a.B","x":2.6323,"colname":"index4"},{"name":"A.3.a.C","x":1.3235,"colname":"index4"},{"name":"A.3.a.D","x":2.2462,"colname":"index4"},{"name":"A.3.a.E","x":5.2847,"colname":"index4"}],"colname":"index3"},{"name":"A.3.b","children":[{"name":"A.3.b.A","x":2.3685,"colname":"index4"},{"name":"A.3.b.B","x":0.8124,"colname":"index4"},{"name":"A.3.b.C","x":1.6443,"colname":"index4"}],"colname":"index3"},{"name":"A.3.c","children":[{"name":"A.3.c.A","x":0.3409,"colname":"index4"},{"name":"A.3.c.B","x":0.2124,"colname":"index4"},{"name":"A.3.c.C","x":3.8828,"colname":"index4"}],"colname":"index3"},{"name":"A.3.d","children":[{"name":"A.3.d.A","x":0.1642,"colname":"index4"},{"name":"A.3.d.B","x":0.604,"colname":"index4"}],"colname":"index3"},{"name":"A.3.e","children":[],"x":3.967,"colname":"index3"}],"colname":"index2"},{"name":"A.4","children":[{"name":"A.4.a","children":[{"name":"A.4.a.A","x":1.0949,"colname":"index4"},{"name":"A.4.a.B","x":3.934,"colname":"index4"},{"name":"A.4.a.C","x":1.2862,"colname":"index4"},{"name":"A.4.a.D","x":4.341,"colname":"index4"},{"name":"A.4.a.E","x":0.1998,"colname":"index4"},{"name":"A.4.a.F","x":2.128,"colname":"index4"}],"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"B","children":[{"name":"B.1","children":[{"name":"B.1.a","children":[{"name":"B.1.a.A","x":1.076,"colname":"index4"},{"name":"B.1.a.B","x":2.0908,"colname":"index4"},{"name":"B.1.a.C","x":3.0615,"colname":"index4"}],"colname":"index3"},{"name":"B.1.b","children":[{"name":"B.1.b.A","x":1.8733,"colname":"index4"},{"name":"B.1.b.B","x":12.3626,"colname":"index4"}],"colname":"index3"},{"name":"B.1.c","children":[{"name":"B.1.c.A","x":0.7496,"colname":"index4"},{"name":"B.1.c.B","x":6.1983,"colname":"index4"},{"name":"B.1.c.C","x":0.1181,"colname":"index4"}],"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"C","children":[{"name":"C.1","children":[{"name":"C.1.a","children":[{"name":"C.1.a.A","x":1.2485,"colname":"index4"},{"name":"C.1.a.B","x":0.2663,"colname":"index4"}],"colname":"index3"},{"name":"C.1.b","children":[{"name":"C.1.b.A","x":1.2818,"colname":"index4"},{"name":"C.1.b.B","x":0.7558,"colname":"index4"},{"name":"C.1.b.C","x":0.23,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"C.2","children":[{"name":"C.2.a","children":[{"name":"C.2.a.A","x":0.3204,"colname":"index4"},{"name":"C.2.a.B","x":0.9136,"colname":"index4"},{"name":"C.2.a.C","x":0.2502,"colname":"index4"},{"name":"C.2.a.D","x":0.5094,"colname":"index4"},{"name":"C.2.a.E","x":10.0988,"colname":"index4"}],"colname":"index3"},{"name":"C.2.b","children":[{"name":"C.2.b.A","x":2.02,"colname":"index4"}],"colname":"index3"},{"name":"C.2.c","children":[{"name":"C.2.c.A","x":1.0563,"colname":"index4"},{"name":"C.2.c.B","x":0.7758,"colname":"index4"},{"name":"C.2.c.C","x":14.1867,"colname":"index4"}],"colname":"index3"},{"name":"C.2.d","children":[{"name":"C.2.d.A","x":1.2492,"colname":"index4"}],"colname":"index3"},{"name":"C.2.e","children":[{"name":"C.2.e.A","x":0.7442,"colname":"index4"},{"name":"C.2.e.B","x":1.5704,"colname":"index4"}],"colname":"index3"},{"name":"C.2.f","children":[{"name":"C.2.f.A","x":2.4539,"colname":"index4"},{"name":"C.2.f.B","x":2.2992,"colname":"index4"},{"name":"C.2.f.C","x":1.7886,"colname":"index4"},{"name":"C.2.f.D","x":6.7719,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"C.3","children":[{"name":"C.3.a","children":[{"name":"C.3.a.A","x":0.3635,"colname":"index4"}],"colname":"index3"},{"name":"C.3.b","children":[{"name":"C.3.b.A","x":0.5884,"colname":"index4"}],"colname":"index3"},{"name":"C.3.c","children":[{"name":"C.3.c.A","x":0.2694,"colname":"index4"},{"name":"C.3.c.B","x":5.9839,"colname":"index4"}],"colname":"index3"},{"name":"C.3.d","children":[{"name":"C.3.d.A","x":0.4943,"colname":"index4"},{"name":"C.3.d.B","x":4.3875,"colname":"index4"}],"colname":"index3"},{"name":"C.3.e","children":[{"name":"C.3.e.A","x":0.4639,"colname":"index4"},{"name":"C.3.e.B","x":3.6792,"colname":"index4"},{"name":"C.3.e.C","x":0.5293,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"C.4","children":[{"name":"C.4.a","children":[{"name":"C.4.a.A","x":2.2266,"colname":"index4"}],"colname":"index3"},{"name":"C.4.b","children":[{"name":"C.4.b.A","x":0.228,"colname":"index4"},{"name":"C.4.b.B","x":0.8451,"colname":"index4"},{"name":"C.4.b.C","x":0.7434,"colname":"index4"},{"name":"C.4.b.D","x":5.3967,"colname":"index4"},{"name":"C.4.b.E","x":0.5366,"colname":"index4"}],"colname":"index3"},{"name":"C.4.c","children":[{"name":"C.4.c.A","x":2.3889,"colname":"index4"},{"name":"C.4.c.B","x":0.2578,"colname":"index4"}],"colname":"index3"},{"name":"C.4.d","children":[{"name":"C.4.d.A","x":0.7377,"colname":"index4"},{"name":"C.4.d.B","x":5.0587,"colname":"index4"}],"colname":"index3"}],"colname":"index2"},{"name":"C.5","children":[{"name":"C.5.a","children":[{"name":"C.5.a.A","x":0.2622,"colname":"index4"},{"name":"C.5.a.B","x":1.5642,"colname":"index4"},{"name":"C.5.a.C","x":0.1044,"colname":"index4"}],"colname":"index3"},{"name":"C.5.b","children":[{"name":"C.5.b.A","x":4.3989,"colname":"index4"}],"colname":"index3"},{"name":"C.5.c","children":[{"name":"C.5.c.A","x":1.4453,"colname":"index4"},{"name":"C.5.c.B","x":0.3751,"colname":"index4"},{"name":"C.5.c.C","x":0.4504,"colname":"index4"}],"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"D","children":[{"name":"D.1","children":[{"name":"D.1.a","children":[{"name":"D.1.a.A","x":0.3634,"colname":"index4"},{"name":"D.1.a.B","x":4.3141,"colname":"index4"},{"name":"D.1.a.C","x":1.0609,"colname":"index4"},{"name":"D.1.a.D","x":0.9883,"colname":"index4"}],"colname":"index3"},{"name":"D.1.b","children":[{"name":"D.1.b.A","x":3.8657,"colname":"index4"},{"name":"D.1.b.B","x":0.2552,"colname":"index4"}],"colname":"index3"}],"colname":"index2"}],"colname":"index1"}],"name":"root"};
var store = {
  tree: tree,
  filtered_tree: tree,
  size: 'x',
  width: 800,
  height: 600,
  tile: d3.treemapBinary,
  color: color,
  sunburstChartConfig: function(chart) {
    chart.label(function(d){return d.name});
    chart.color(function(d){return color(d.name);})
    chart.sunburst().size(function(d){return d.x});
  }
};

var app = new Vue({
  el: '#app',
  components: {
    'sunburst-chart': vued2b.ChartSunburst
  },
  data: store,
  methods: {
    sunburstChartRendered: function (el, chart) {
      var that = this;
      d3.select(el).selectAll('.d2b-sunburst-chart')
        .on('mouseover', function (d) {
          if(d3.event.target.classList[0] === 'd2b-sunburst-arc'){
            that.filtered_tree = d3.select(d3.event.target).datum().data;
          }
        });
    }
  }
})
</script>
</body>
</html>