Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active May 1, 2017 17:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timelyportfolio/f715fd63bf341654d71fe83d95e771f6 to your computer and use it in GitHub Desktop.
Save timelyportfolio/f715fd63bf341654d71fe83d95e771f6 to your computer and use it in GitHub Desktop.
my first Vue component v2 - d3 treemap
license: mit
scrolling: yes

My First Vue Component v2 without createElement

As I said in the v1 version of this example, I wanted to try two ways to build this.

  • createElement in code
  • vue directives

I had thought I would like the vuejs directive approach, but in terms of debugging and clarity, I actually think I like the createElement in code approach more. I would love thoughts. Below is a code comparison.

vue directive vs. createElement

vue directive

  <svg v-bind:style="styleObject">
    <g>
      <rect v-for="(node, index) in nodes" v-if="node.depth === 2"
      	v-bind="{'x':node.x0, 'width': node.x1 - node.x0,'y': node.y0, 'height': node.y1 - node.y0, 'fill': color(node.parent.data.name)}">
      </rect>
    </g>
  </svg>

createElement

create_tree: function() {
  var createElement = this.$createElement;
  var color = d3.scaleOrdinal(d3.schemeCategory10);
  var nodes_g = this.nodes.map(function(d) {
    if(d.depth === 2) {
      return createElement(
        'g',
        null,
        [createElement(
          'rect',
          {
            attrs: {
              x: d.x0,
              width: d.x1 - d.x0,
              y: d.y0,
              height: d.y1 - d.y0
            },
            style: {
              fill: color(d.parent.data.name)
            }
          }
        )]
      )
    }
  })
  return createElement(
    'svg',
    {style: {height: this.treeheight, width: this.treewidth}},
    nodes_g
  );
}

Code in R

I put everything together in R, but I think even non-R readers can understand the code. If not, view page source. Without R, I would not have the awesome treemap package to generate random hierarchical tree data.

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

rhd <- random.hierarchical.data()

rhd_json <- d3_nest(rhd, value_cols="x")

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 === depth",
                "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)}",
                "v-on:click" = "onNodeClick(node)"
              )
            )
          )
        )
      )
    )
  )
)

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
    },
    depth: {
      type: Number,
      default: 2
    },
    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)
    },
    onNodeClick: function(node) {
      this.$emit('node_click', node, this)
    }
  }
});
"
)

app <- tags$script(HTML(
sprintf("
var tree = %s;

var app = new Vue({
  el: '#app',
  data: {
    tree: tree,
    size: 'x',
    width: 800,
    height: 600,
    tile: d3.treemapSliceDice,
    depth: 1,
    color: d3.scaleOrdinal(d3.schemeCategory20c)
  },
  methods: {
    node_clicked: function(node, component) {
      console.log(node, component);
    }
  }
})
",
rhd_json
)
))

# one treemap example
browsable(
  tagList(
    template,
    component,
    tags$div(
      id="app",
      tag(
        "treemap-component",
        list(":tree" = "tree",":sizefield"="'x'","v-on:node_click"="node_clicked") #use defaults
      )
    ),
    app,
    html_dependency_vue(offline=FALSE,minified=FALSE),
    d3_dep_v4(offline=FALSE)
  )
)


browsable(
  tagList(
    template,
    component,
    tags$div(
      id="app",
      tag(
        "treemap-component",
        list(":tree" = "tree",":sizefield"="'x'") #use defaults
      ),
      tag(
        "treemap-component",
        list(":tree" = "tree",":sizefield"="size",":treeheight"=100,":treewidth"=200) #use explicit height,width
      ),
      tag(
        "treemap-component",
        list(
          ":tree" = "tree",":sizefield"="size",
          ":treeheight"="height",":treewidth"="width",
          ":depth"="depth", ":color"="color"
        )
      ),
      tag(
        "treemap-component",
        list(":tree" = "tree",":sizefield"="size",":tile"="tile","v-on:node_click"="node_clicked")
      )
    ),
    tags$script("
      setInterval(
        function(){app.$data.depth = app.$data.depth === 3 ? 1 : app.$data.depth + 1},
        2000
      )
    "),
    app,
    html_dependency_vue(offline=FALSE,minified=FALSE),
    d3_dep_v4(offline=FALSE)
  )
)
<!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>
</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 === depth" 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)}" v-on:click="onNodeClick(node)"></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
},
depth: {
type: Number,
default: 2
},
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)
},
onNodeClick: function(node) {
this.$emit('node_click', node, this)
}
}
});
</script>
<div id="app">
<treemap-component :tree="tree" :sizefield="&#39;x&#39;"></treemap-component>
<treemap-component :tree="tree" :sizefield="size" :treeheight="100" :treewidth="200"></treemap-component>
<treemap-component :tree="tree" :sizefield="size" :treeheight="height" :treewidth="width" :depth="depth" :color="color"></treemap-component>
<treemap-component :tree="tree" :sizefield="size" :tile="tile" v-on:node_click="node_clicked"></treemap-component>
</div>
<script>
setInterval(
function(){app.$data.depth = app.$data.depth === 3 ? 1 : app.$data.depth + 1},
2000
)
</script>
<script>
var tree = {"children":[{"name":"A","children":[{"name":"A.1","children":[{"name":"A.1.a","x":0.3902,"colname":"index3"},{"name":"A.1.b","x":1.7404,"colname":"index3"}],"colname":"index2"},{"name":"A.2","children":[{"name":"A.2.a","x":1.0128,"colname":"index3"},{"name":"A.2.b","x":3.5715,"colname":"index3"}],"colname":"index2"},{"name":"A.3","children":[{"name":"A.3.a","x":0.8313,"colname":"index3"},{"name":"A.3.b","x":0.0759,"colname":"index3"},{"name":"A.3.c","x":1.8782,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"B","children":[{"name":"B.1","children":[{"name":"B.1.a","x":0.4866,"colname":"index3"},{"name":"B.1.b","x":1.8149,"colname":"index3"},{"name":"B.1.c","x":2.7673,"colname":"index3"},{"name":"B.1.d","x":0.2664,"colname":"index3"},{"name":"B.1.e","x":0.5731,"colname":"index3"}],"colname":"index2"},{"name":"B.2","children":[{"name":"B.2.a","x":0.5775,"colname":"index3"},{"name":"B.2.b","x":0.4183,"colname":"index3"},{"name":"B.2.c","x":0.192,"colname":"index3"},{"name":"B.2.d","x":0.5227,"colname":"index3"},{"name":"B.2.e","x":0.3131,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"C","children":[{"name":"C.1","children":[{"name":"C.1.a","x":3.0069,"colname":"index3"},{"name":"C.1.b","x":0.2474,"colname":"index3"}],"colname":"index2"},{"name":"C.2","children":[{"name":"C.2.a","x":0.7694,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"D","children":[{"name":"D.1","children":[{"name":"D.1.a","x":1.2516,"colname":"index3"},{"name":"D.1.b","x":5.7938,"colname":"index3"},{"name":"D.1.c","x":1.9006,"colname":"index3"}],"colname":"index2"},{"name":"D.2","children":[{"name":"D.2.a","x":1.1821,"colname":"index3"}],"colname":"index2"},{"name":"D.3","children":[{"name":"D.3.a","x":1.3802,"colname":"index3"},{"name":"D.3.b","x":0.2289,"colname":"index3"},{"name":"D.3.c","x":1.3503,"colname":"index3"},{"name":"D.3.d","x":0.7134,"colname":"index3"},{"name":"D.3.e","x":5.967,"colname":"index3"},{"name":"D.3.f","x":0.6689,"colname":"index3"}],"colname":"index2"}],"colname":"index1"}],"name":"root"};
var app = new Vue({
el: '#app',
data: {
tree: tree,
size: 'x',
width: 800,
height: 600,
tile: d3.treemapSliceDice,
depth: 1,
color: d3.scaleOrdinal(d3.schemeCategory20c)
},
methods: {
node_clicked: function(node, component) {
console.log(node, component);
}
}
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment