Skip to content

Instantly share code, notes, and snippets.

@dahis39
Last active October 21, 2019 07:40
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dahis39/f28369f0b17b456ac2f1fa9b937c5002 to your computer and use it in GitHub Desktop.
Save dahis39/f28369f0b17b456ac2f1fa9b937c5002 to your computer and use it in GitHub Desktop.
Dendrogram + Grouped Horizontal Bar Chart
license: gpl-3.0
height: 1100

Dendrogram + Grouped Horizontal Bar Chart, based on and inspirited by Mike Bostock's brilliant Dendrogram.

At first, I was hunting for a skills bar chart for my portfolio site, then I saw Mike's Dendrogram. I was thinking if there was a traditional bar chart attached to the pointers of Dendrogram, the new combination gonna have the best of both charts with very limited separation. Then this happens, with a little bit animation and custom colors. I also commented on both Mike's and mine codes.

Original Dendrogram: http://bl.ocks.org/mbostock/4063570

For better visualization, visit my portfolio at: http://www.saturnringstation.com/portfolio/

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1px;
}
text {
font-family: "Arial Black", Gadget, sans-serif;
fill: black;
font-weight: bold;
font-size: 14px
}
.xAxis .tick text{
fill: black;
}
.grid .tick line{
stroke: grey;
stroke-dasharray: 5, 10;
opacity: 0.7;
}
.grid path{
stroke-width: 0;
}
.node circle {
fill: #999;
}
.node--internal circle {
fill: #555;
}
.node--internal text {
font-size: 16px;
text-shadow: 0 2px 0 #fff, 0 -2px 0 #fff, 2px 0 0 #fff, -2px 0 0 #fff;
}
.node--leaf text {
fill: white;
}
.ballG text {
fill: white;
}
.shadow {
-webkit-filter: drop-shadow( -1.5px -1.5px 1.5px #000 );
filter: drop-shadow( -1.5px -1.5px 1.5px #000 );
}
</style>
<body>
<svg width="960" height="1100"></svg>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// main svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(20,0)"); // move right 20px.
// x-scale and x-axis
var experienceName = ["", "Basic 1.0","Alright 2.0","Handy 3.0","Expert 4.0","Guru 5.0"];
var formatSkillPoints = function (d) {
return experienceName[d % 6];
}
var xScale = d3.scaleLinear()
.domain([0,5])
.range([0, 400]);
var xAxis = d3.axisTop()
.scale(xScale)
.ticks(5)
.tickFormat(formatSkillPoints);
// Setting up a way to handle the data
var tree = d3.cluster() // This D3 API method setup the Dendrogram datum position.
.size([height, width - 460]) // Total width - bar chart width = Dendrogram chart width
.separation(function separate(a, b) {
return a.parent == b.parent // 2 levels tree grouping for category
|| a.parent.parent == b.parent
|| a.parent == b.parent.parent ? 0.4 : 0.8;
});
var stratify = d3.stratify() // This D3 API method gives cvs file flat data array dimensions.
.parentId(function(d) { return d.id.substring(0, d.id.lastIndexOf(".")); });
d3.csv("skillsdata.csv", row, function(error, data) {
if (error) throw error;
var root = stratify(data);
tree(root);
// Draw every datum a line connecting to its parent.
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x
+ "C" + (d.parent.y + 100) + "," + d.x
+ " " + (d.parent.y + 100) + "," + d.parent.x
+ " " + d.parent.y + "," + d.parent.x;
});
// Setup position for every datum; Applying different css classes to parents and leafs.
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
// Draw every datum a small circle.
node.append("circle")
.attr("r", 4);
// Setup G for every leaf datum.
var leafNodeG = g.selectAll(".node--leaf")
.append("g")
.attr("class", "node--leaf-g")
.attr("transform", "translate(" + 8 + "," + -13 + ")");
leafNodeG.append("rect")
.attr("class","shadow")
.style("fill", function (d) {return d.data.color;})
.attr("width", 2)
.attr("height", 30)
.attr("rx", 2)
.attr("ry", 2)
.transition()
.duration(800)
.attr("width", function (d) {return xScale(d.data.value);});
leafNodeG.append("text")
.attr("dy", 19.5)
.attr("x", 8)
.style("text-anchor", "start")
.text(function (d) {
return d.data.id.substring(d.data.id.lastIndexOf(".") + 1);
});
// Write down text for every parent datum
var internalNode = g.selectAll(".node--internal");
internalNode.append("text")
.attr("y", -10)
.style("text-anchor", "middle")
.text(function (d) {
return d.data.id.substring(d.data.id.lastIndexOf(".") + 1);
});
// Attach axis on top of the first leaf datum.
var firstEndNode = g.select(".node--leaf");
firstEndNode.insert("g")
.attr("class","xAxis")
.attr("transform", "translate(" + 7 + "," + -14 + ")")
.call(xAxis);
// tick mark for x-axis
firstEndNode.insert("g")
.attr("class", "grid")
.attr("transform", "translate(7," + (height - 15) + ")")
.call(d3.axisBottom()
.scale(xScale)
.ticks(5)
.tickSize(-height, 0, 0)
.tickFormat("")
);
// Emphasize the y-axis baseline.
svg.selectAll(".grid").select("line")
.style("stroke-dasharray","20,1")
.style("stroke","black");
// The moving ball
var ballG = svg.insert("g")
.attr("class","ballG")
.attr("transform", "translate(" + 1100 + "," + height/2 + ")");
ballG.insert("circle")
.attr("class","shadow")
.style("fill","steelblue")
.attr("r", 5);
ballG.insert("text")
.style("text-anchor", "middle")
.attr("dy",5)
.text("0.0");
// Animation functions for mouse on and off events.
d3.selectAll(".node--leaf-g")
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
function handleMouseOver(d) {
var leafG = d3.select(this);
leafG.select("rect")
.attr("stroke","#4D4D4D")
.attr("stroke-width","2");
var ballGMovement = ballG.transition()
.duration(400)
.attr("transform", "translate(" + (d.y
+ xScale(d.data.value) + 90) + ","
+ (d.x + 1.5) + ")");
ballGMovement.select("circle")
.style("fill", d.data.color)
.attr("r", 18);
ballGMovement.select("text")
.delay(300)
.text(Number(d.data.value).toFixed(1));
}
function handleMouseOut() {
var leafG = d3.select(this);
leafG.select("rect")
.attr("stroke-width","0");
}
});
function row(d) {
return {
id: d.id,
value: +d.value,
color: d.color
};
}
</script>
We can make this file beautiful and searchable if this error is corrected: It looks like row 2 should actually have 3 columns, instead of 2. in line 1.
id,value,color
Tom,
Tom.Front-End,
Tom.Front-End.Design,1,#808080
Tom.Front-End.Ps,0.5,#2E2575
Tom.Front-End.HTML & CSS,3,#E54F24
Tom.Front-End.SVG,1.5,#FF9900
Tom.Front-End.Bootstrap,3,#563B7E
Tom.Front-End.JavaScript World,
Tom.Front-End.JavaScript World.JavaScript,3,#D6BA33
Tom.Front-End.JavaScript World.JQuery,3,#1169AE
Tom.Front-End.JavaScript World.D3 js,2.5,#F6854D
Tom.Back-End,
Tom.Back-End.PHP,1.5,#617CBE
Tom.Back-End.C++,1,#0073AD
Tom.Back-End.SQL,1.5,#E58E00
Tom.Back-End.Java World,
Tom.Back-End.Java World.Java SE,3,#EB2C2F
Tom.Back-End.Java World.JSP,2,#EB2C2F
Tom.Back-End.Java World.Hibernate,2.5,#59666C
Tom.Back-End.Java World.Spring MVC / Boot,3,#6DB33F
Tom.Back-End.Java World.Spring Data,2.5,#6DB33F
Tom.Back-End.Java World.Spring Security,2.5,#5FA134
Tom.Back-End.Java World.Thymeleaf,3,#005F0F
Tom.Databases,
Tom.Databases.MySQL,2.5,#00618A
Tom.SysAdmin,
Tom.SysAdmin.VPS & SharedHost,2,#808080
Tom.SysAdmin.Linux,2.5,#040404
Tom.SysAdmin.Apache & Tomcat,2,#CA212F
Tom.CMS,
Tom.CMS.Wordpress,1.5,#00A0D2
Tom.CMS.Drupal,1.5,#0678BE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment