I was using d3 version 4.5 earlier in my project for d3 pack circles. Now I have used latest version and got difference in pack layout symmetry. Before Image and After Image
Here is my code in both cases. Want to have same symmetry as it was in earlier version. Is there any new way to get this symmetry in latest version of d3.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var bubble = d3.pack(data)
.size([diameter, 185])
.padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes)
.descendants())
.enter()
.filter(function(d) {
return !d.children;
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle")
.attr("r", function(d) {
return d.r;
//return d.data.Radius;
})
.style("fill", function(d, i) {
return d.data.fillColor;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
node.append("text")
.each(function(d) {
var arr = d.data.Name.split(" ");
for(i = 0; i < arr.length; i++) {
if(arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this)
.append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i)
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
}
d3.select(this)
.append("title")
.text(d.data.Name);
});
d3.select(self.frameElement)
.style("height", 185 + "px");
d3.selectAll(".node")
.on("mouseover", function(d) {
var circle = d3.select(this)
.select("circle");
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r;
});
})
.on("mousemove", function(d) {
})
.on("mouseleave", function(d) {
var circle = d3.select(this)
.select("circle");
circle.transition()
.duration(500)
.attr("r", function(d) {
return d.r;
});
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r / 3;
});
});
You were right, it's this commit in d3-hierarchy between 1.1.1 and 1.1.2, which in turn was introduced between d3 4.5.0 and 4.5.1. It addresses this issue, about packing circles more condensely.
I recommend just accepting the changes, but if you really don't want to change the layout, import d3-hierarchy 1.1.1 *after* d3` to override the hierarchy module. This returns the same layout as the older version of d3:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
The downside is that by making the package versions out of sync, you might break something now or in the future, so it's not a long term sustainable approach.
To test use the following snippet and comment/uncomment the script imports.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var data = {
children: [{
Name: 'Economy',
fillColor: 'grey',
Count: 12
},
{
Name: 'Politics',
fillColor: 'grey',
Count: 10
},
{
Name: 'ESG',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Tech',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Leisure',
fillColor: 'pink',
Count: 4
},
{
Name: 'Coronavirus',
fillColor: 'pink',
Count: 4
},
{
Name: 'Blockchain',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Sports',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Coding',
fillColor: 'purple',
Count: 1,
},
{
Name: 'India',
fillColor: 'purple',
Count: 1,
}
],
};
var bubble = d3.pack(data).size([diameter, 185]).padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d) {
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle").attr("r", function(d) {
return d.r;
//return d.data.Radius;
}).style("fill", function(d, i) {
return d.data.fillColor;
});
node.append("text").each(function(d) {
var arr = d.data.Name.split(" ");
for (i = 0; i < arr.length; i++) {
if (arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i).attr("fill", "white").attr("font-size", function(d) {
return d.r / 3;
});
}
d3.select(this).append("title").text(d.data.Name);
});
d3.select(self.frameElement).style("height", 185 + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
<div id="trending-topic"></div>
I have this code, which draw a bar chart. I'm trying to put the corresponding value inside the bars but in the very end of each bar. I haven't been able to accomplish it. Can you please help me with this? Any suggestion will be highly appreciated.
I'll leave a running snippet so that you can see it and tell where to start to place the values at the end of the bars but inside it.
$(window).on('resize', function (event) {
$("#chart").width(window.innerWidth * 0.9);
$("#chart").height(window.innerHeight);
});
function horizontalGroupBarChart(config) {
function setReSizeEvent(data) {
var resizeTimer;
var interval = 500;
window.removeEventListener('resize', function () {
});
window.addEventListener('resize', function (event) {
if (resizeTimer !== false) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(function () {
$(data.mainDiv).empty();
drawHorizontalGroupBarChartChart(data);
clearTimeout(resizeTimer);
}, interval);
});
}
drawHorizontalGroupBarChartChart(config);
setReSizeEvent(config);
}
function createhorizontalGroupBarChartLegend(mainDiv, columnsInfo, colorRange) {
var z = d3.scaleOrdinal()
.range(colorRange);
var mainDivName = mainDiv.substr(1, mainDiv.length);
$(mainDiv).before("<div id='Legend_" + mainDivName + "' class='pmd-card-body' style='margin-top:0; margin-bottom:0;text-align:center'></div>");
var keys = Object.keys(columnsInfo);
keys.forEach(function (d) {
var cloloCode = z(d);
$("#Legend_" + mainDivName).append("<span class='team-graph team1' style='display: inline-block; margin-right:10px;'>\
<span style='background:" + cloloCode + ";width: 10px;height: 10px;display: inline-block;vertical-align: middle;'> </span>\
<span style='padding-top: 0;font-family:Source Sans Pro, sans-serif;font-size: 13px;display: inline;'>" + columnsInfo[d] + " </span>\
</span>");
});
}
function drawHorizontalGroupBarChartChart(config) {
var data = config.data;
var columnsInfo = config.columnsInfo;
var xAxis = config.xAxis;
var yAxis = config.yAxis;
var colorRange = config.colorRange;
var mainDiv = config.mainDiv;
var mainDivName = mainDiv.substr(1, mainDiv.length);
var label = config.label;
var requireLegend = config.requireLegend;
d3.select(mainDiv).append("svg").attr("width", $(mainDiv).width()).attr("height", $(mainDiv).height() * 0.80).attr("class","mainSVG")
var svg = d3.select(mainDiv + " svg"),
margin = { top: 20, right: 20, bottom: 40, left: 40 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.append("g").attr("transform", "translate(" +( margin.left*2.3) + "," + margin.top + ")");
if (requireLegend != null && requireLegend != undefined && requireLegend != false) {
$("#Legend_" + mainDivName).remove();
createhorizontalGroupBarChartLegend(mainDiv, columnsInfo, colorRange);
}
$(".mainSVG").attr("transform","translate(5,10)")
var y0 = d3.scaleBand()
.rangeRound([height, 0])
.paddingInner(0.1);
var y1 = d3.scaleBand()
.padding(0.05);
var x = d3.scaleLinear()
.rangeRound([0, width - margin.left ]);
var z = d3.scaleOrdinal()
.range(colorRange);
var keys = Object.keys(columnsInfo);
y0.domain(data.map(function (d) {
return d[yAxis];
}));
y1.domain(keys).rangeRound([0, y0.bandwidth()]);
x.domain([0, d3.max(data, function (d) {
return d3.max(keys, function (key) {
return d[key];
});
})]).nice();
var maxTicks = d3.max(data, function (d) {
return d3.max(keys, function (key) {
return d[key];
});
});
var element = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function (d) {
return "translate(0," + y0(d[yAxis]) + ")";
});
var rect = element.selectAll("rect")
.data(function (d, i) {
return keys.map(function (key) {
return { key: key, value: d[key], index: key + "_" + i + "_" + d[yAxis] };
});
})
.enter().append("rect")
.attr("y", function (d) {
return y1(d.key);
})
.attr("width", function (d) {
return x(d.value);
})
.attr("data-index", function (d, i) {
return d.index;
})
.attr("height", y1.bandwidth())
.attr("fill", function (d) {
return z(d.key);
})
var datax = [0,1,2,3,4,5,6,7,8,9,10,11,12];
var tScale= d3.scaleLinear()
.rangeRound([0, width - margin.left ]);
tScale.domain(d3.extent(datax)).nice();
//CBT:add tooltips
var self = {};
self.svg = svg;
self.cssPrefix = "horgroupBar0_";
self.data = data;
self.keys = keys;
self.height = height;
self.width = width;
self.label = label;
self.yAxis = yAxis;
self.xAxis = xAxis;
horBarTooltip.addTooltips(self);
rect.on("mouseover", function () {
var currentEl = d3.select(this);
var index = currentEl.attr("data-index");
horBarTooltip.showTooltip(self, index);
});
rect.on("mouseout", function () {
var currentEl = d3.select(this);
var index = currentEl.attr("data-index");
horBarTooltip.hideTooltip(self, index);
});
rect.on("mousemove", function () {
horBarTooltip.moveTooltip(self);
});
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(maxTicks))
.append("text")
.attr("x", width / 2)
.attr("y", margin.bottom * 0.7)
.attr("dx", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y0).ticks(null, "s"))
.append("text")
.attr("x", height * 0.4 * -1)
.attr("y", margin.left * 0.8 * -1)//y(y.ticks().pop()) + 0.5)
.attr("dy", "0.71em")
.attr("fill", "#00338D")
.attr("font-weight", "bold")
// .attr("text-anchor", "start")
}
var helpers = {
getDimensions: function (id) {
var el = document.getElementById(id);
var w = 0, h = 0;
if (el) {
var dimensions = el.getBBox();
w = dimensions.width;
h = dimensions.height;
} else {
console.log("error: getDimensions() " + id + " not found.");
}
return { w: w, h: h };
}
}
var horBarTooltip = {
addTooltips: function (pie) {
var keys = pie.keys;
// group the label groups (label, percentage, value) into a single element for simpler positioning
var element = pie.svg.append("g")
.selectAll("g")
.data(pie.data)
.enter().append("g")
.attr("class", function (d, i) {
return pie.cssPrefix + "tooltips" + "_" + i
});
tooltips = element.selectAll("g")
.data(function (d, i) {
return keys.map(function (key) {
return { key: key, value: d[key], index: key + "_" + i + "_" + d[pie.yAxis] };
});
})
.enter()
.append("g")
.attr("class", pie.cssPrefix + "tooltip")
.attr("id", function (d, i) {
return pie.cssPrefix + "tooltip" + d.index;
})
.style("opacity", 0)
.append("rect")
.attr("rx", 2)
.attr("ry", 2)
.attr("x", -2)
.attr("opacity", 0.71)
.style("fill", "#000000");
element.selectAll("g")
.data(function (d, i) {
return keys.map(function (key) {
return { key: key, value: d[key], index: key + "_" + i + "_" + d[pie.yAxis] };
});
})
.append("text")
.attr("fill", function (d) {
return "#efefef"
})
.style("font-size", function (d) {
return 10;
})
.style("font-family", function (d) {
return "arial";
})
.text(function (d, i) {
var caption = "" + pie.label.xAxis + ":{value}";
return horBarTooltip.replacePlaceholders(pie, caption, i, {
value: d.value,
});
});
element.selectAll("g rect")
.attr("width", function (d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return dims.w + (2 * 4);
})
.attr("height", function (d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return dims.h + (2 * 4);
})
.attr("y", function (d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return -(dims.h / 2) + 1;
});
},
showTooltip: function (pie, index) {
var fadeInSpeed = 250;
if (horBarTooltip.currentTooltip === index) {
fadeInSpeed = 1;
}
horBarTooltip.currentTooltip = index;
d3.select("#" + pie.cssPrefix + "tooltip" + index)
.transition()
.duration(fadeInSpeed)
.style("opacity", function () {
return 1;
});
horBarTooltip.moveTooltip(pie);
},
moveTooltip: function (pie) {
d3.selectAll("#" + pie.cssPrefix + "tooltip" + horBarTooltip.currentTooltip)
.attr("transform", function (d) {
var mouseCoords = d3.mouse(this.parentNode);
var x = mouseCoords[0] + 4 + 2;
var y = mouseCoords[1] - (2 * 4) - 2;
return "translate(" + x + "," + y + ")";
});
},
hideTooltip: function (pie, index) {
d3.select("#" + pie.cssPrefix + "tooltip" + index)
.style("opacity", function () {
return 0;
});
// move the tooltip offscreen. This ensures that when the user next mouseovers the segment the hidden
// element won't interfere
d3.select("#" + pie.cssPrefix + "tooltip" + horBarTooltip.currentTooltip)
.attr("transform", function (d, i) {
// klutzy, but it accounts for tooltip padding which could push it onscreen
var x = pie.width + 1000;
var y = pie.height + 1000;
return "translate(" + x + "," + y + ")";
});
},
replacePlaceholders: function (pie, str, index, replacements) {
var replacer = function () {
return function (match) {
var placeholder = arguments[1];
if (replacements.hasOwnProperty(placeholder)) {
return replacements[arguments[1]];
} else {
return arguments[0];
}
};
};
return str.replace(/\{(\w+)\}/g, replacer(replacements));
}
};
var groupChartData = [{ "num": 1, "over": "Singapore" }, { "num": 1.3, "over": "The Netherlands" }, { "num": 2, "over": "United Kingdom" }, { "num": 2.4, "over": "United States"}, { "num": 2.6, "over": "New Zealand" }, { "num": 2.8, "over": "Sweden" }, { "num": 3, "over": "Canada"}, { "num": 3, "over": "UAE" }, { "num": 4, "over": "Australia" }, { "num": 4.4, "over": "France" },{ "num": 5, "over": "South Korea" },{ "num": 5.2, "over": "Germany" },{ "num": 5.5, "over": "Austria" },{ "num": 6, "over": "Austria" },{ "num": 7, "over": "Brazil" },{ "num": 7, "over": "China" },{ "num": 8, "over": "Japan" },{ "num": 10, "over": "Russia" },{ "num": 11, "over": "Mexico" },{ "num": 12, "over": "India" },];
var columnsInfo = { "num": "<span class='mainTitle KPMGWeb-ExtraLight'>Technology & innovation pillar: score by country</span>" };
$("#chart").empty();
var barChartConfig = {
mainDiv: "#chart",
colorRange: ["#0091DA", "#6D2077"],
data: groupChartData,
columnsInfo: columnsInfo,
xAxis: "runs",
yAxis: "over",
label: {
xAxis: "",
yAxis: ""
},
requireLegend: true
};
var groupChart = new horizontalGroupBarChart(barChartConfig);
.mainTitle{
font-size: 3em;
}
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart" style="width: 800;height: 600">
Thank you very much!
It sounds like you were almost there with adding the labels. You want to add them to the g element that holds the rect bars, and add them after the rects so they will appear on top of the bars, so:
var element = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) {
return "translate(0," + y0(d[yAxis]) + ")";
});
var rect = element.selectAll("rect")
.data(function(d, i) {
return keys.map(function(key) {
return {
key: key,
value: d[key],
index: key + "_" + i + "_" + d[yAxis]
};
});
})
.enter().append("rect")
.attr("width", function (d) {
return x(d.value);
})
[ etc. ]
// add the text elements
element.append('text')
The text element needs to be at the end of the bar, and the bar width is x(d.value) using the data bound to the rect elements. d.value translates to d.num in terms of the data you already have attached to the g elements, so we can set the x attribute as x(d.num). If the text-anchor is set to end, that will align the end of the text with the end of the bar; we want a little space between text and end of bar, so add in a small offset:
element.append('text')
.attr('text-anchor', 'end')
.attr('x', d => x(d.num) - 5)
The value to be shown in the bar is also going to be d.num, so we can add that:
element.append('text')
.attr('text-anchor', 'end')
.attr('x', d => x(d.num) - 5)
.text(d => d.num )
If you run the code now, you'll find that the numbers are partially obscured by the bar above, so let's sort out the y offset. The bar width is y1.bandwidth(); to align the baseline of the text with the bottom of the bar, add
element.append('text')
.attr('text-anchor', 'end')
.attr('x', d => x(d.num) - 5)
.attr('y', y1.bandwidth())
.text(d => d.num )
Depending on what size you anticipate your users viewing the chart at, you might want to try to centre the text over the bar -- e.g. try
element.append('text')
.attr('text-anchor', 'end')
.attr('x', d => x(d.num) - 5)
.attr('y', y1.bandwidth()/2)
.attr('dy', '0.25em')
.text(d => d.num )
Here's a working example:
$(window).on('resize', function(event) {
$("#chart").width(window.innerWidth * 0.9);
$("#chart").height(window.innerHeight);
});
function horizontalGroupBarChart(config) {
function setReSizeEvent(data) {
var resizeTimer;
var interval = 500;
window.removeEventListener('resize', function() {});
window.addEventListener('resize', function(event) {
if (resizeTimer !== false) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(function() {
$(data.mainDiv).empty();
drawHorizontalGroupBarChartChart(data);
clearTimeout(resizeTimer);
}, interval);
});
}
drawHorizontalGroupBarChartChart(config);
setReSizeEvent(config);
}
function createhorizontalGroupBarChartLegend(mainDiv, columnsInfo, colorRange) {
var z = d3.scaleOrdinal()
.range(colorRange);
var mainDivName = mainDiv.substr(1, mainDiv.length);
$(mainDiv).before("<div id='Legend_" + mainDivName + "' class='pmd-card-body' style='margin-top:0; margin-bottom:0;text-align:center'></div>");
var keys = Object.keys(columnsInfo);
keys.forEach(function(d) {
var cloloCode = z(d);
$("#Legend_" + mainDivName).append("<span class='team-graph team1' style='display: inline-block; margin-right:10px;'>\
<span style='background:" + cloloCode + ";width: 10px;height: 10px;display: inline-block;vertical-align: middle;'> </span>\
<span style='padding-top: 0;font-family:Source Sans Pro, sans-serif;font-size: 13px;display: inline;'>" + columnsInfo[d] + " </span>\
</span>");
});
}
function drawHorizontalGroupBarChartChart(config) {
var data = config.data;
var columnsInfo = config.columnsInfo;
var xAxis = config.xAxis;
var yAxis = config.yAxis;
var colorRange = config.colorRange;
var mainDiv = config.mainDiv;
var mainDivName = mainDiv.substr(1, mainDiv.length);
var label = config.label;
var requireLegend = config.requireLegend;
d3.select(mainDiv).append("svg").attr("width", $(mainDiv).width()).attr("height", $(mainDiv).height() * 0.80).attr("class", "mainSVG")
var svg = d3.select(mainDiv + " svg"),
margin = {
top: 20,
right: 20,
bottom: 40,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.append("g").attr("transform", "translate(" + (margin.left * 2.3) + "," + margin.top + ")");
if (requireLegend != null && requireLegend != undefined && requireLegend != false) {
$("#Legend_" + mainDivName).remove();
createhorizontalGroupBarChartLegend(mainDiv, columnsInfo, colorRange);
}
$(".mainSVG").attr("transform", "translate(5,10)")
var y0 = d3.scaleBand()
.rangeRound([height, 0])
.paddingInner(0.1);
var y1 = d3.scaleBand()
.padding(0.05);
var x = d3.scaleLinear()
.rangeRound([0, width - margin.left]);
var z = d3.scaleOrdinal()
.range(colorRange);
var keys = Object.keys(columnsInfo);
y0.domain(data.map(function(d) {
return d[yAxis];
}));
y1.domain(keys).rangeRound([0, y0.bandwidth()]);
x.domain([0, d3.max(data, function(d) {
return d3.max(keys, function(key) {
return d[key];
});
})]).nice();
var maxTicks = d3.max(data, function(d) {
return d3.max(keys, function(key) {
return d[key];
});
});
var element = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) {
return "translate(0," + y0(d[yAxis]) + ")";
});
var rect = element.selectAll("rect")
.data(function(d, i) {
return keys.map(function(key) {
return {
key: key,
value: d[key],
index: key + "_" + i + "_" + d[yAxis]
};
});
})
.enter().append("rect")
.attr("y", function(d) {
return y1(d.key);
})
.attr("width", function(d) {
return x(d.value);
})
.attr("data-index", function(d, i) {
return d.index;
})
.attr("height", y1.bandwidth())
.attr("fill", function(d) {
return z(d.key);
})
element.append('text')
.attr('x', d => x(d.num) - 5)
.attr('y', y1.bandwidth()/2)
.attr('dy', '0.35em')
.attr('text-anchor', 'end')
.text(d => d.num )
var datax = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var tScale = d3.scaleLinear()
.rangeRound([0, width - margin.left]);
tScale.domain(d3.extent(datax)).nice();
//CBT:add tooltips
var self = {};
self.svg = svg;
self.cssPrefix = "horgroupBar0_";
self.data = data;
self.keys = keys;
self.height = height;
self.width = width;
self.label = label;
self.yAxis = yAxis;
self.xAxis = xAxis;
horBarTooltip.addTooltips(self);
rect.on("mouseover", function() {
var currentEl = d3.select(this);
var index = currentEl.attr("data-index");
horBarTooltip.showTooltip(self, index);
});
rect.on("mouseout", function() {
var currentEl = d3.select(this);
var index = currentEl.attr("data-index");
horBarTooltip.hideTooltip(self, index);
});
rect.on("mousemove", function() {
horBarTooltip.moveTooltip(self);
});
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(maxTicks))
.append("text")
.attr("x", width / 2)
.attr("y", margin.bottom * 0.7)
.attr("dx", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y0).ticks(null, "s"))
.append("text")
.attr("x", height * 0.4 * -1)
.attr("y", margin.left * 0.8 * -1) //y(y.ticks().pop()) + 0.5)
.attr("dy", "0.71em")
.attr("fill", "#00338D")
.attr("font-weight", "bold")
// .attr("text-anchor", "start")
}
var helpers = {
getDimensions: function(id) {
var el = document.getElementById(id);
var w = 0,
h = 0;
if (el) {
var dimensions = el.getBBox();
w = dimensions.width;
h = dimensions.height;
} else {
console.log("error: getDimensions() " + id + " not found.");
}
return {
w: w,
h: h
};
}
}
var horBarTooltip = {
addTooltips: function(pie) {
var keys = pie.keys;
// group the label groups (label, percentage, value) into a single element for simpler positioning
var element = pie.svg.append("g")
.selectAll("g")
.data(pie.data)
.enter().append("g")
.attr("class", function(d, i) {
return pie.cssPrefix + "tooltips" + "_" + i
});
tooltips = element.selectAll("g")
.data(function(d, i) {
return keys.map(function(key) {
return {
key: key,
value: d[key],
index: key + "_" + i + "_" + d[pie.yAxis]
};
});
})
.enter()
.append("g")
.attr("class", pie.cssPrefix + "tooltip")
.attr("id", function(d, i) {
return pie.cssPrefix + "tooltip" + d.index;
})
.style("opacity", 0)
.append("rect")
.attr("rx", 2)
.attr("ry", 2)
.attr("x", -2)
.attr("opacity", 0.71)
.style("fill", "#000000");
element.selectAll("g")
.data(function(d, i) {
return keys.map(function(key) {
return {
key: key,
value: d[key],
index: key + "_" + i + "_" + d[pie.yAxis]
};
});
})
.append("text")
.attr("fill", function(d) {
return "#efefef"
})
.style("font-size", function(d) {
return 10;
})
.style("font-family", function(d) {
return "arial";
})
.text(function(d, i) {
var caption = "" + pie.label.xAxis + ":{value}";
return horBarTooltip.replacePlaceholders(pie, caption, i, {
value: d.value,
});
});
element.selectAll("g rect")
.attr("width", function(d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return dims.w + (2 * 4);
})
.attr("height", function(d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return dims.h + (2 * 4);
})
.attr("y", function(d, i) {
var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + d.index);
return -(dims.h / 2) + 1;
});
},
showTooltip: function(pie, index) {
var fadeInSpeed = 250;
if (horBarTooltip.currentTooltip === index) {
fadeInSpeed = 1;
}
horBarTooltip.currentTooltip = index;
d3.select("#" + pie.cssPrefix + "tooltip" + index)
.transition()
.duration(fadeInSpeed)
.style("opacity", function() {
return 1;
});
horBarTooltip.moveTooltip(pie);
},
moveTooltip: function(pie) {
d3.selectAll("#" + pie.cssPrefix + "tooltip" + horBarTooltip.currentTooltip)
.attr("transform", function(d) {
var mouseCoords = d3.mouse(this.parentNode);
var x = mouseCoords[0] + 4 + 2;
var y = mouseCoords[1] - (2 * 4) - 2;
return "translate(" + x + "," + y + ")";
});
},
hideTooltip: function(pie, index) {
d3.select("#" + pie.cssPrefix + "tooltip" + index)
.style("opacity", function() {
return 0;
});
// move the tooltip offscreen. This ensures that when the user next mouseovers the segment the hidden
// element won't interfere
d3.select("#" + pie.cssPrefix + "tooltip" + horBarTooltip.currentTooltip)
.attr("transform", function(d, i) {
// klutzy, but it accounts for tooltip padding which could push it onscreen
var x = pie.width + 1000;
var y = pie.height + 1000;
return "translate(" + x + "," + y + ")";
});
},
replacePlaceholders: function(pie, str, index, replacements) {
var replacer = function() {
return function(match) {
var placeholder = arguments[1];
if (replacements.hasOwnProperty(placeholder)) {
return replacements[arguments[1]];
} else {
return arguments[0];
}
};
};
return str.replace(/\{(\w+)\}/g, replacer(replacements));
}
};
var groupChartData = [{
"num": 1,
"over": "Singapore"
}, {
"num": 1.3,
"over": "The Netherlands"
}, {
"num": 2,
"over": "United Kingdom"
}, {
"num": 2.4,
"over": "United States"
}, {
"num": 2.6,
"over": "New Zealand"
}, {
"num": 2.8,
"over": "Sweden"
}, {
"num": 3,
"over": "Canada"
}, {
"num": 3,
"over": "UAE"
}, {
"num": 4,
"over": "Australia"
}, {
"num": 4.4,
"over": "France"
}, {
"num": 5,
"over": "South Korea"
}, {
"num": 5.2,
"over": "Germany"
}, {
"num": 5.5,
"over": "Austria"
}, {
"num": 6,
"over": "Austria"
}, {
"num": 7,
"over": "Brazil"
}, {
"num": 7,
"over": "China"
}, {
"num": 8,
"over": "Japan"
}, {
"num": 10,
"over": "Russia"
}, {
"num": 11,
"over": "Mexico"
}, {
"num": 12,
"over": "India"
}, ];
var columnsInfo = {
"num": "<span class='mainTitle KPMGWeb-ExtraLight'>Technology & innovation pillar: score by country</span>"
};
$("#chart").empty();
var barChartConfig = {
mainDiv: "#chart",
colorRange: ["#0091DA", "#6D2077"],
data: groupChartData,
columnsInfo: columnsInfo,
xAxis: "runs",
yAxis: "over",
label: {
xAxis: "",
yAxis: ""
},
requireLegend: true
};
var groupChart = new horizontalGroupBarChart(barChartConfig);
.mainTitle {
font-size: 3em;
}
svg text {
font-size: 10px;
font-family: sans-serif;
}
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart" style="width: 800;height: 600">
By the way, I noticed that there seem to be two bars on the same line for Austria - not sure if that is deliberate or not.
I have the following code I got from the net. I am trying to build a graph that has self linking nodes.
After much struggle I was able to do it for edges linking to other nodes. Can someone plz help me to create a self linking edge?
I tried integrating the example D3 Force Layout Graph - Self linking node
But could not suceed. Please help.
And thanks in advance
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Force Layout with labels on edges</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css">
</style>
</head>
<body>
<script type="text/javascript">
var w = 1400;
var h = 600;
//distance between nodes
var linkDistance=200;
var colors = d3.scale.category10();
var dataset = {
nodes: [
{name: "Car"},
{name: "Jeep"}
],
edges: [
{source: 0, target: 0}
],
methlbl: [
{name:"add(int, int)"}
]
};
var svg = d3.select("body").append("svg").attr({"width":w,"height":h});
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w,h])
.linkDistance([linkDistance])
.charge([-500])
.theta(0.1)
.gravity(0.05)
.start();
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.attr("id",function(d,i) {return 'edge'+i})
.attr('marker-end','url(#arrowhead)')
.style("stroke","#ccc")
.style("pointer-events", "none");
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr({"r":15})
.style("fill",function(d,i){return colors(4);})
.call(force.drag)
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({"x":function(d){return d.x;},
"y":function(d){return d.y;},
"class":"nodelabel",
"stroke":"black"})
.text(function(d){return d.name;});
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':40,
'dy':-10,
'font-size':20,
'fill':'#aaa'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d,i){return dataset.methlbl[i].name});
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':25,
'refY':0,
//'markerUnits':'strokeWidth',
'orient':'auto',
'markerWidth':10,
'markerHeight':10,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke','#ccc');
force.on("tick", function(){
edges.attr({"x1": function(d){return d.source.x;},
"y1": function(d){return d.source.y;},
"x2": function(d){return d.target.x;},
"y2": function(d){return d.target.y;}
});
nodes.attr({"cx":function(d){return d.x;},
"cy":function(d){return d.y;}
});
nodelabels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
edgepaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
//console.log(d)
return path});
edgelabels.attr('transform',function(d,i){
if (d.target.x<d.source.x){
bbox = this.getBBox();
rx = bbox.x+bbox.width/2;
ry = bbox.y+bbox.height/2;
return 'rotate(180 '+rx+' '+ry+')';
}
else {
return 'rotate(0)';
}
});
});
</script>
</body>
</html>
Removed unnecessary code and made some improvements. Hope following code snippet helps.
var w = 800;
var h = 400;
//distance between nodes
var linkDistance = 200;
var colors = d3.scale.category10();
var dataset = {
nodes: [{
name: "Car"
},
{
name: "Jeep"
}
],
edges: [{
source: 0,
target: 0
}],
methlbl: [{
name: "add(int, int)"
}]
};
var svg = d3.select("body").append("svg").attr({
"width": w,
"height": h
});
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([linkDistance])
.charge([-500])
.theta(0.1)
.gravity(0.05)
.start();
var edges = svg.selectAll("path")
.data(dataset.edges)
.enter()
.append("path")
.style("fill","none")
.attr("id", function(d, i) {
return 'edge' + i
})
.attr('marker-end', function(d) {
return d.source == d.target ? '' : 'url(#arrowhead)'
})
.style("stroke", "#ccc")
.style("pointer-events", "none");
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr({
"r": 15
})
.style("fill", function(d, i) {
return colors(4);
})
.call(force.drag)
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({
"x": function(d) {
return d.x;
},
"y": function(d) {
return d.y;
},
"class": "nodelabel",
"stroke": "black"
})
.text(function(d) {
return d.name;
});
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'font-size': 20,
'fill': '#aaa'
})
edgelabels.append("textPath")
.attr("xlink:href", function(d, i) {
return '#edge' + i;
})
.style("pointer-events", "none")
.text(function(d, i) {
return dataset.methlbl[i].name
});
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke', '#ccc');
force.on("tick", function() {
edges.attr("d", function(d) {
var x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
dx = x2 - x1,
dy = y2 - y1,
dr = Math.sqrt(dx * dx + dy * dy),
// Defaults for normal edge.
drx = dr,
dry = dr,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// Self edge.
if (x1 === x2 && y1 === y2) {
// Fiddle with this angle to get loop oriented.
xRotation = -45;
// Needs to be 1.
largeArc = 1;
// Change sweep to change orientation of loop.
//sweep = 0;
// Make drx and dry different to get an ellipse
// instead of a circle.
drx = 30;
dry = 20;
// For whatever reason the arc collapses to a point if the beginning
// and ending points of the arc are the same, so kludge it.
x2 = x2 + 1;
y2 = y2 + 1;
}
return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2;
});
nodes.attr({
"cx": function(d) {
return d.x;
},
"cy": function(d) {
return d.y;
}
});
edgelabels.selectAll("textPath")
.attr("xlink:href", function(d, i) {
return '#edge' + i;
})
.attr("startOffset", function(d, i) {
var arcLength = d3.select("#edge" + i).node().getTotalLength();
var textLength = d3.select("#edgelabel" + i).node().getComputedTextLength();
var offset = (arcLength - textLength) / 2;
return offset;
});
nodelabels.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v3.min.js"></script>
Thanks in advance for any help.
I'm new to D3 and javascript as a whole. I've been pretty stuck on this for a while now even searching through other similar posts.
I'd like to flip my stacked bar chart appropriately so that it aligns to the bottom of the SVG.
When I do try it the way I think it should be done, I get a "invalid negative value for '' message.
var dataset = [
[
{ x: 0, y: 5 },
{ x: 1, y: 4 },
{ x: 2, y: 2 },
{ x: 3, y: 7 },
{ x: 4, y: 23 }
],
[
{ x: 0, y: 10 },
{ x: 1, y: 12 },
{ x: 2, y: 19 },
{ x: 3, y: 23 },
{ x: 4, y: 17 }
],
[
{ x: 0, y: 22 },
{ x: 1, y: 28 },
{ x: 2, y: 32 },
{ x: 3, y: 35 },
{ x: 4, y: 43 }
]
];
//Width and Height
var w = 500;
h = 300;
//Create SVG canvas
var svg = d3.select('body').append('svg')
.attr('width', w)
.attr('height', h);
//Set up Stack
var stack = d3.layout.stack();
//Stack dataset
stack(dataset);
//Create scales
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0,w], 0.05);
var yScale = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([0, h]);
//Create colors for scale
var colors = d3.scale.category10();
//Create a Group for each row of data
var groups = svg.selectAll('g')
.data(dataset)
.enter() //only creates placeholder
.append('g') //creates group
.style('fill', function(d, i) {
return colors(i);
});
//Add a rectangle for each datavalue
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) { return yScale(d.y0 + d.y); })
.attr("height", function(d) { return yScale(d.y0) - yScale(d.y0 + d.y); })
.attr("width", xScale.rangeBand());
I think you need to change the way height is getting calculated for each bar. See this plnkr. Is this something you were looking for. May be you can use height (h) variable for your calculations. I changed y parameters like below.
//Add a rectangle for each datavalue
var rects = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
/* .attr("y", function(d) { return yScale(d.y0 + d.y); })
.attr("height", function(d) { return yScale(d.y0) - yScale(d.y0 + d.y); })
.attr("y", function(d) { return yScale(d.y0 ); })
.attr("height", function(d) { return yScale(d.y0 + d.y);})
*/
.attr("y", function(d) {
return yScale(d.y0 + d.y);
})
.attr("height", function(d) {
return h - yScale(d.y0 + d.y);
})
.attr("width", xScale.rangeBand());