Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active February 13, 2018 01:46
Show Gist options
  • Save timelyportfolio/8c9a77da3e9ca57827a8801fb8a16ba9 to your computer and use it in GitHub Desktop.
Save timelyportfolio/8c9a77da3e9ca57827a8801fb8a16ba9 to your computer and use it in GitHub Desktop.
vue d3 tree responding to d2b sunburst
license: mit

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)
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment