Skip to content

Instantly share code, notes, and snippets.

@Gargamil
Last active February 28, 2018 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Gargamil/17babbe505218edbc8571780e2141368 to your computer and use it in GitHub Desktop.
Save Gargamil/17babbe505218edbc8571780e2141368 to your computer and use it in GitHub Desktop.
Angular 2 zoomable Treemap Component
app-treemap *,
app-treemap *:before,
app-treemap *:after {
box-sizing: border-box;
}
app-treemap{
width: 100%;
height: 100%;
margin: 0;
position: absolute;
}
app-treemap .feature {
position: relative;
width: calc(100% - 10px);
height: calc(100% - 10px);
margin: 5px;
background: #aaa;
border: 1px solid black;
}
app-treemap .feature .child{
position: absolute;
width: 100%;
height: calc(100% - 1.5em);
}
app-treemap .feature .parent{
display:block;
height:1.5em;
line-height: 1.5em;
font-weight: bold;
}
app-treemap .feature .parent span{
cursor: pointer;
}
app-treemap .feature .parent span + span:before{
content: ">";
padding: 0 5px;
}
app-treemap .node {
position: absolute;
border: 1px white solid;
overflow: hidden;
-webkit-transition: opacity .8s;
transition: opacity .8s;
}
app-treemap .node .label {
display: inline;
font-family: sans-serif;
color: rgba(255, 255, 255, 1);
white-space: nowrap;
position: absolute;
margin: 0;
}
app-treemap .node .zoompoint{
cursor: pointer;
}
app-treemap .node .labelcheckbox{
vertical-align: middle;
}
app-treemap .node:hover {
/*opacity: 1;*/
}
app-treemap .node:hover .label {
/*-webkit-filter: blur(10px);
filter: blur(10px);*/
}
app-treemap .node.level-0 {
z-index: 4;
font-size: 2vmin;
display: none;
}
app-treemap .node.level-1 {
z-index: 3;
font-size: 1vmin;
}
app-treemap .node.level-1 .label {
top: 0;
left: 0;
}
app-treemap .node.level-2 {
z-index: 2;
font-size: 1vmin;
}
app-treemap .node.level-2 .label {
bottom: 0;
right: 0;
}
app-treemap .node.level-3 {
z-index: 1;
font-size: 1vmin;
}
<div class="feature" id="chart"></div>
import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core';
import * as d3 from 'd3';
@Component({
selector: 'app-treemap',
templateUrl: './treemap.component.html',
styleUrls: ['./treemap.component.css'],
encapsulation: ViewEncapsulation.None,
})
export class TreemapComponent implements OnInit {
/*
BASED ON: https://gist.github.com/fallenartist/f3438a9d17e481199e04c8e7d431dbd1
*/
private width: number;
private height: number;
private x;
private y;
private node;
private root;
private color;
private maxDepth;
private topDepth;
private treemap;
private chart;
private parent;
constructor() {
this.maxDepth = 3;
this.topDepth = 1;
}
@Input()
set analyzeData(data) {
this.init();
this.root = data;
this.render(data);
}
private init() {
this.width = 100;
this.height = 100;
this.x = d3.scaleLinear().domain([0, this.width]).range([0, this.width]);
this.y = d3.scaleLinear().domain([0, this.height]).range([0, this.height]);
this.color = d3.scaleOrdinal(d3.schemeCategory20c);
this.treemap = d3.treemap()
.size([this.width, this.height])
.paddingInner(0)
.round(false)
.tile(d3.treemapSquarify)
this.chart = d3.select('#chart');
this.parent = this.chart.append('g').attr('class', 'parent');
this.chart.append('g').attr('class', 'child');
}
private render(node) {
node = d3.hierarchy(node)
.sum((d) => { return d.size; })
.sort((a, b) => { return b.value - a.value; });
this.addParents(node);
this.treemap(node);
const desc = node.descendants().filter((d) => {
return d.depth < this.maxDepth;
});
this.chart.select('.child').selectAll('.node').remove();
const children = this.chart
.select('.child')
.selectAll('.node')
.data(desc)
.enter().append('div');
children
.attr('class', (d) => {
return 'node level-' + d.depth;
})
.attr('title', (d) => {
return d.data.name ? d.data.name : 'null';
})
.style('left', (d) => {
return this.x(d.x0) + '%';
})
.style('top', (d) => {
return this.y(d.y0) + '%';
})
.style('width', (d) => {
return this.x(d.x1 - d.x0) + '%';
})
.style('height', (d) => {
return this.y(d.y1 - d.y0) + '%';
})
.style('background-color', (d) => {
while (d.depth > 1) { d = d.parent; }
const color_ = d3.rgb(this.color(d.data.name));
if(d.children) {
color_.opacity = 0.5;
}
return color_;
});
const label = children.append('p')
.attr('class', 'label');
const toplabels = label.filter((d) => { return d.depth === this.topDepth; });
toplabels.append('input')
.attr('type', 'checkbox')
.attr('class', 'labelcheckbox');
label.append('span')
.attr('class', 'labeltext')
.text((d) => {
return d.data.name ? d.data.name : 'null';
}).on('mousedown touchstart', (d) => {
if (d.children) {
d.data._parent = d.parent;
this.render(d.data);
}
});
toplabels.append('span')
.attr('class', 'zoompoint')
.text((d) => {
if (d.children) {
return ' +';
}
})
.on('mousedown touchstart', (d) => {
if (d.children) {
d.data._parent = d.parent;
this.render(d.data);
}
});
}
private addParents(node) {
this.parent.selectAll('span').remove();
const parents = [];
parents.push(node.data);
while (node.data._parent) {
parents.unshift(node.data._parent.data);
node = node.data._parent;
}
for (const p of parents){
this.addParentElement(p);
}
}
private addParentElement(element) {
const newElement = this.parent.append('span');
newElement.text(element.name);
newElement.datum(element);
newElement.on('mousedown touchstart', (d) => {
this.render(d);
});
}
ngOnInit() {
}
}
@pcart-grandjean
Copy link

pcart-grandjean commented Feb 28, 2018

Hello,
Could you please provide an example of data that will be accepted by your component? It seems different from the data expected by the source you got your inspiration from (https://gist.github.com/fallenartist/f3438a9d17e481199e04c8e7d431dbd1). For example, you have a size.
Thanks,
Pierre

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment