Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active April 29, 2017 11:34
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/12af134a23616a6dc95d0f736347f978 to your computer and use it in GitHub Desktop.
Save timelyportfolio/12af134a23616a6dc95d0f736347f978 to your computer and use it in GitHub Desktop.
my first Vue component (ugly) - d3 treemap
license: mit
scrolling: yes

I had intentionally done this in the "non-vue" way by creating elements with code. I thought I would like the "vue" way better as shown here, but I'm not so sure. I actually think the createElement code way might be clearer and easier for a traditional programmer. Would love thoughts.


My First Vue Component

I have had so much fun using other Vue components that I just had to try to build one myself. I chose to do one based on d3.js to increase the complexity. This one works, but I already know there are a couple better ways to accomplish this.

for fun, open up the console and play with app.$data and watch realtime updates

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.

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()))
)

component <- tags$script(
"
//create greetings component based on the greetings template
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
    }
  },
  computed: {
    treemap: function() { return this.calculate_tree() },
    nodes: function() {
      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)
    },
    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
      );
    }
  },
  render: function() {return this.create_tree()},
  updated: function() {return this.create_tree()}
});
"
)

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
  }
})
",
rhd_json
)
))

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")
      ),
      tag(
        "treemap-component",
        list(":tree" = "tree",":sizefield"="size",":tile"="tile")
      )
    ),
    app,
    html_dependency_vue(offline=FALSE,minified=FALSE),
    d3_dep_v4(offline=FALSE)
  )
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="//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></svg>
</template>
<script>
//create greetings component based on the greetings template
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
}
},
computed: {
treemap: function() { return this.calculate_tree() },
nodes: function() {
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)
},
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
);
}
},
render: function() {return this.create_tree()},
updated: function() {return this.create_tree()}
});
</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"></treemap-component>
<treemap-component :tree="tree" :sizefield="size" :tile="tile"></treemap-component>
</div>
<script>
var tree = {"children":[{"name":"A","children":[{"name":"A.1","children":[{"name":"A.1.a","x":0.7234,"colname":"index3"},{"name":"A.1.b","x":1.3044,"colname":"index3"},{"name":"A.1.c","x":2.3885,"colname":"index3"},{"name":"A.1.d","x":1.0981,"colname":"index3"}],"colname":"index2"},{"name":"A.2","children":[{"name":"A.2.a","x":0.094,"colname":"index3"},{"name":"A.2.b","x":0.1838,"colname":"index3"},{"name":"A.2.c","x":1.5752,"colname":"index3"}],"colname":"index2"},{"name":"A.3","children":[{"name":"A.3.a","x":0.4794,"colname":"index3"}],"colname":"index2"},{"name":"A.4","children":[{"name":"A.4.a","x":4.1686,"colname":"index3"},{"name":"A.4.b","x":0.8951,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"B","children":[{"name":"B.1","children":[{"name":"B.1.a","x":0.3944,"colname":"index3"},{"name":"B.1.b","x":0.5038,"colname":"index3"},{"name":"B.1.c","x":0.7252,"colname":"index3"},{"name":"B.1.d","x":0.9184,"colname":"index3"}],"colname":"index2"},{"name":"B.2","children":[{"name":"B.2.a","x":0.4523,"colname":"index3"},{"name":"B.2.b","x":4.4584,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"C","children":[{"name":"C.1","children":[{"name":"C.1.a","x":3.4448,"colname":"index3"}],"colname":"index2"},{"name":"C.2","children":[{"name":"C.2.a","x":0.5852,"colname":"index3"},{"name":"C.2.b","x":0.2688,"colname":"index3"},{"name":"C.2.c","x":3.235,"colname":"index3"},{"name":"C.2.d","x":0.8756,"colname":"index3"}],"colname":"index2"},{"name":"C.3","children":[{"name":"C.3.a","x":0.3787,"colname":"index3"}],"colname":"index2"}],"colname":"index1"},{"name":"D","children":[{"name":"D.1","children":[{"name":"D.1.a","x":6.4576,"colname":"index3"}],"colname":"index2"},{"name":"D.2","children":[{"name":"D.2.a","x":1.9254,"colname":"index3"},{"name":"D.2.b","x":2.8249,"colname":"index3"},{"name":"D.2.c","x":0.6848,"colname":"index3"},{"name":"D.2.d","x":0.5441,"colname":"index3"},{"name":"D.2.e","x":1.1926,"colname":"index3"},{"name":"D.2.f","x":0.8097,"colname":"index3"},{"name":"D.2.g","x":0.201,"colname":"index3"}],"colname":"index2"},{"name":"D.3","children":[{"name":"D.3.a","x":0.9273,"colname":"index3"}],"colname":"index2"},{"name":"D.4","children":[{"name":"D.4.a","x":5.3492,"colname":"index3"},{"name":"D.4.b","x":2.1529,"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
}
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment