Skip to content

Instantly share code, notes, and snippets.

@lorenzopub
Created May 8, 2017 06:16
Show Gist options
  • Save lorenzopub/055ebebc66dbd2ee2a05da249212929e to your computer and use it in GitHub Desktop.
Save lorenzopub/055ebebc66dbd2ee2a05da249212929e 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.0.28",
  src = c(href = "https://unpkg.com/[email protected]/build/"),
  script = "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': d2b.vueChartSunburst
  },
  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
)

browsable(ui)

forked from timelyportfolio's block: vue d3 tree responding to d2b sunburst

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script>
<script src="https://unpkg.com/[email protected]/build/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': d2b.vueChartSunburst
},
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