Animate a path using Leaflet and D3.js - animation

I am trying to implement the following example to animate a path using Leaflet and D3.js: http://zevross.com/blog/2014/09/30/use-the-amazing-d3-library-to-animate-a-path-on-a-leaflet-map/.
I receive an error in line 110 saying that d is undefined and I can't find a solution. Any idea on how to solve it?
<!DOCTYPE html5>
<html>
<head>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css"
integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet.js"
integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw=="
crossorigin=""></script>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
}
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
// set up the map
map = new L.Map('map');
// create the tile layer with correct attribution
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='Map data © OpenStreetMap contributors';
var osm = new L.TileLayer(osmUrl, {minZoom: 8, maxZoom: 15, attribution: osmAttrib});
// start the map in Donosti
map.setView(new L.LatLng(40.72332345541449, -73.99),14);
map.addLayer(osm);
var svg = d3.select(map.getPanes().overlayPane).append("svg");
var g = svg.append("g").attr("class", "leaflet-zoom-hide");
d3.json("points.geojson", function(collection) {
var featuresdata = collection.features.filter(function(d) {
return d.properties.id == "route1"
});
var transform = d3.geo.transform({
point: projectPoint
});
var d3path = d3.geo.path().projection(transform);
var toLine = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return applyLatLngToLayer(d).x
})
.y(function(d) {
return applyLatLngToLayer(d).y
});
var ptFeatures = g.selectAll("circle")
.data(featuresdata)
.enter()
.append("circle")
.attr("r", 3)
.attr("class", "waypoints");
var linePath = g.selectAll(".lineConnect")
.data([featuresdata])
.enter()
.append("path")
.attr("class", "lineConnect");
// This will be our traveling circle it will
// travel along our path
var marker = g.append("circle")
.attr("r", 10)
.attr("id", "marker")
.attr("class", "travelMarker");
var originANDdestination = [featuresdata[0], featuresdata[17]];
var begend = g.selectAll(".drinks")
.data(originANDdestination)
.enter()
.append("circle", ".drinks")
.attr("r", 5)
.style("fill", "red")
.style("opacity", "1");
// I want names for my coffee and beer
var text = g.selectAll("text")
.data(originANDdestination)
.enter()
.append("text")
.text(function(d) {
return d.properties.name
})
.attr("class", "locnames")
.attr("y", function(d) {
return -10
});
// when the user zooms in or out you need to reset
// the view
map.on("viewreset", reset);
// this puts stuff on the map!
reset();
transition();
// Reposition the SVG to cover the features.
function reset() {
var bounds = d3path.bounds(collection),
topLeft = bounds[0],
bottomRight = bounds[1];
text.attr("transform",
function(d) {
return "translate(" +
applyLatLngToLayer(d).x + "," +
applyLatLngToLayer(d).y + ")";
});
// for the points we need to convert from latlong
// to map units
begend.attr("transform",
function(d) {
return "translate(" +
applyLatLngToLayer(d).x + "," +
applyLatLngToLayer(d).y + ")";
});
ptFeatures.attr("transform",
function(d) {
return "translate(" +
applyLatLngToLayer(d).x + "," +
applyLatLngToLayer(d).y + ")";
});
// again, not best practice, but I'm harding coding
// the starting point
marker.attr("transform",
function() {
var y = featuresdata[0].geometry.coordinates[1]
var x = featuresdata[0].geometry.coordinates[0]
return "translate(" +
map.latLngToLayerPoint(new L.LatLng(y, x)).x + "," +
map.latLngToLayerPoint(new L.LatLng(y, x)).y + ")";
});
// Setting the size and location of the overall SVG container
svg.attr("width", bottomRight[0] - topLeft[0] + 120)
.attr("height", bottomRight[1] - topLeft[1] + 120)
.style("left", topLeft[0] - 50 + "px")
.style("top", topLeft[1] - 50 + "px");
// linePath.attr("d", d3path);
linePath.attr("d", toLine)
// ptPath.attr("d", d3path);
g.attr("transform", "translate(" + (-topLeft[0] + 50) + "," + (-topLeft[1] + 50) + ")");
}; // end reset
function transition() {
linePath.transition()
.duration(7500)
.attrTween("stroke-dasharray", tweenDash)
.each("end", function() {
d3.select(this).call(transition);// infinite loop
});
}; //end transition
// this function feeds the attrTween operator above with the
// stroke and dash lengths
function tweenDash() {
return function(t) {
//total length of path (single value)
var l = linePath.node().getTotalLength();
interpolate = d3.interpolateString("0," + l, l + "," + l);
//t is fraction of time 0-1 since transition began
var marker = d3.select("#marker");
// p is the point on the line (coordinates) at a given length
// along the line. In this case if l=50 and we're midway through
// the time then this would 25.
var p = linePath.node().getPointAtLength(t * l);
//Move the marker to that point
marker.attr("transform", "translate(" + p.x + "," + p.y + ")"); //move marker
console.log(interpolate(t))
return interpolate(t);
}
}; //end tweenDash
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}; //end projectPoint
});
function applyLatLngToLayer(d) {
var y = d.geometry.coordinates[1]
var x = d.geometry.coordinates[0]
return map.latLngToLayerPoint(new L.LatLng(y, x))
};
</script>
</body>
</html>
And the external file needed to run the code is:
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature",
"properties": { "latitude": 40.722390, "longitude": -73.995170, "time": 1, "id": "route1", "name":"Gimme" },
"geometry": { "type": "Point", "coordinates": [ -73.99517, 40.72239 ] } },
{ "type": "Feature",
"properties": { "latitude": 40.721580, "longitude": -73.995480, "time": 2, "id": "route1", "name":"Along route" },
"geometry": { "type": "Point", "coordinates": [ -73.99548, 40.72158 ] } }]}

Related

D3js horizontal bar chart: how to add numbers from data at the end of every bar?

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.

Legend that updates at the click of the mouse

I created this sample heatmap: Plunker
Initially I use a certain linear scale colorScale1 to color the heatmap.
When the user clicks on the legend, the color scale is updated and the threshold scale (colorScale2) is used.
This switch works well.
Now I don't know how to change the legend for the colorScale2.
The ticks and the gradient for colorScale2 are wrong. I looked for a linearGradient equivalent for scaleThreshold but I didn't find anything.
This is the code:
var itemSize = 20;
var cellBorderSize = 1;
var cellSize = itemSize - 1 + cellBorderSize;
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var width = 80 - margin.right - margin.left;
var height = 80 - margin.top - margin.bottom;
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var domain1 = [0, 80, 90, 95, 100];
var range1 = ['#EC93AB', '#CEB1DE', '#95D3F0', '#77EDD9', '#A9FCAA'];
var colorScale1 = d3.scaleLinear()
.domain(domain1)
.range(range1);
var domain2 = [0, 95, 100];
var range2 = ['white', 'lightgrey', 'grey'];
var colorScale2 = d3.scaleThreshold()
.domain(domain2)
.range(range2);
svg.append('defs')
.append('pattern')
.attr('id', 'pattern-stripes')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(45)')
.attr('width', 3)
.attr('height', 3)
.append('rect')
.attr('width', 1)
.attr('height', 3)
.attr('transform', 'translate(0, 0)')
.attr('fill', 'black');
///////////////////////////////////////////////////////////
// Load data files.
///////////////////////////////////////////////////////////
var files = ['./data.csv'];
var promises = [];
promises.push(d3.csv(files[0]));
Promise.all(promises)
.then(makeHeatmap)
.catch(function(err) {
console.log('Error loading files');
throw err;
});
///////////////////////////////////////////////////////////
// Data heatmap
///////////////////////////////////////////////////////////
function makeHeatmap(myData) {
var data = myData[0];
// get each element of data file and creates an object
var data = data.map(function(item) {
var newItem = {};
newItem.name = item.NAME;
newItem.year = item.YEAR;
newItem.val = item.VAL;
return newItem;
});
var names = data.map(function(d) {
return d.name;
});
regionsName = d3.set(names).values();
numRegions = regionsName.length;
var years = data.map(function(d) {
return d.year;
});
yearsName = d3.set(years).values();
numYears = yearsName.length;
///////////////////////////////////////////////////////////
// Draw heatmap
///////////////////////////////////////////////////////////
var cells = svg.selectAll('.cell')
.data(data)
.enter()
.append('g')
.append('rect')
.attr('data-value', function(d) {
return d.val;
})
.attr('data-r', function(d) {
var idr = regionsName.indexOf(d.name);
return idr;
})
.attr('data-c', function(d, i) {
if(regionsName.includes(d.name) & d.year == '1990') var idc = 0;
else if(regionsName.includes(d.name) && d.year == '1991') var idc = 1;
else if(regionsName.includes(d.name) && d.year == '1992') var idc = 2;
return idc;
})
.attr('class', function() {
var idr = d3.select(this).attr('data-r'); // row
var idc = d3.select(this).attr('data-c'); // column
return 'cell cr' + idr + ' cc' + idc;
})
.attr('width', cellSize)
.attr('height', cellSize)
.attr('x', function(d) {
var c = d3.select(this).attr('data-c');
return c * cellSize;
})
.attr('y', function() {
var r = d3.select(this).attr('data-r');
return r * cellSize;
})
.attr('fill', function(d) {
var col;
if(d.name == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
} // end makeHeatmap
///////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////
// create tick marks
var xLegend = d3.scaleLinear()
.domain([0, 100])
.range([10, 409]); // larghezza dei tick
var axisLegend = d3.axisBottom(xLegend)
.tickSize(19) // height of ticks
.tickFormat(function(v, i) { // i is index of domain colorScale, v is the corrisponding value (v = domain[i])
if(v == 0) {
return v + '%';
}
else {
return v;
}
})
.tickValues(colorScale1.domain());
var svgLegend = d3.select('#legend').append('svg').attr('width', 600);
// append title
svgLegend.append('text')
.attr('class', 'legendTitle')
.attr('x', 10)
.attr('y', 20)
.style('text-anchor', 'start')
.text('Legend title');
// draw the rectangle and fill with gradient
svgLegend.append('rect')
.attr('class', 'legendRect')
.attr('x', 10) // position
.attr('y', 30)
.attr('width', 400) // larghezza fascia colorata
.attr('height', 15) // altezza fascia colorata
.style('fill', 'url(#linear-gradient1)')
.on('click', function() {
if(currentFill === '1') {
updateColor2();
currentFill = '2';
}
else {
updateColor1();
currentFill = '1';
}
});
svgLegend
.attr('class', 'legendLinAxis')
.append('g')
.attr('class', 'legendLinG')
.attr('transform', 'translate(0, 30)') // 47 è la posizione verticale dei tick (se l'aumenti, scendono) (47 per farli partire sotto, 30 per farli partire da sopra)
.call(axisLegend);
var defs = svgLegend.append('defs');
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient1 = defs.append('linearGradient')
.attr('id', 'linear-gradient1')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale1.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale1(d);
});
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale2.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale2(d);
});
// update the colors to a different color scale (colorScale1)
function updateColor1() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient1)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
}
// update the colors to a different color scale (colorScale2)
function updateColor2() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient2)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale2(d.val);
}
return col;
});
}
// start set-up
updateColor1();
var currentFill = '1';
Here is a slightly modified version of your code for which the legend's ticks and color scale are updated when clicking on the legend:
var itemSize = 20;
var cellBorderSize = 1;
var cellSize = itemSize - 1 + cellBorderSize;
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var width = 80 - margin.right - margin.left;
var height = 80 - margin.top - margin.bottom;
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var domain1 = [0, 80, 90, 95, 100];
var range1 = ['#EC93AB', '#CEB1DE', '#95D3F0', '#77EDD9', '#A9FCAA'];
var colorScale1 = d3.scaleLinear()
.domain(domain1)
.range(range1);
var domain2 = [0, 95, 100];
var range2 = ['white', 'lightgrey', 'grey'];
var colorScale2 = d3.scaleThreshold()
.domain(domain2)
.range(range2);
svg.append('defs')
.append('pattern')
.attr('id', 'pattern-stripes')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(45)')
.attr('width', 3)
.attr('height', 3)
.append('rect')
.attr('width', 1)
.attr('height', 3)
.attr('transform', 'translate(0, 0)')
.attr('fill', 'black');
var data = [
{ "NAME": "ronnie", "YEAR": 1990, "VAL": 90 },
{ "NAME": "ronnie", "YEAR": 1991, "VAL": 95 },
{ "NAME": "ronnie", "YEAR": 1992, "VAL": 98 },
{ "NAME": "bob", "YEAR": 1990, "VAL": 92 },
{ "NAME": "bob", "YEAR": 1991, "VAL": 90 },
{ "NAME": "bob", "YEAR": 1992, "VAL": 99 },
{ "NAME": "carl", "YEAR": 1990, "VAL": 98 },
{ "NAME": "carl", "YEAR": 1991, "VAL": 99 },
{ "NAME": "carl", "YEAR": 1992, "VAL": 995 }
];
makeHeatmap(data);
///////////////////////////////////////////////////////////
// Data heatmap
///////////////////////////////////////////////////////////
function makeHeatmap(data) {
//var data = myData[0];
// get each element of data file and creates an object
var data = data.map(function(item) {
var newItem = {};
newItem.name = item.NAME;
newItem.year = item.YEAR;
newItem.val = item.VAL;
return newItem;
});
var names = data.map(function(d) {
return d.name;
});
regionsName = d3.set(names).values();
numRegions = regionsName.length;
var years = data.map(function(d) {
return d.year;
});
yearsName = d3.set(years).values();
numYears = yearsName.length;
///////////////////////////////////////////////////////////
// Draw heatmap
///////////////////////////////////////////////////////////
var cells = svg.selectAll('.cell')
.data(data)
.enter()
.append('g')
.append('rect')
.attr('data-value', function(d) {
return d.val;
})
.attr('data-r', function(d) {
var idr = regionsName.indexOf(d.name);
return idr;
})
.attr('data-c', function(d, i) {
if(regionsName.includes(d.name) & d.year == '1990') var idc = 0;
else if(regionsName.includes(d.name) && d.year == '1991') var idc = 1;
else if(regionsName.includes(d.name) && d.year == '1992') var idc = 2;
return idc;
})
.attr('class', function() {
var idr = d3.select(this).attr('data-r'); // row
var idc = d3.select(this).attr('data-c'); // column
return 'cell cr' + idr + ' cc' + idc;
})
.attr('width', cellSize)
.attr('height', cellSize)
.attr('x', function(d) {
var c = d3.select(this).attr('data-c');
return c * cellSize;
})
.attr('y', function() {
var r = d3.select(this).attr('data-r');
return r * cellSize;
})
.attr('fill', function(d) {
var col;
if(d.name == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
} // end makeHeatmap
///////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////
// create tick marks
var xLegend = d3.scaleLinear()
.domain([0, 100])
.range([10, 409]); // larghezza dei tick
var axisLegend = d3.axisBottom(xLegend)
.tickSize(19) // height of ticks
.tickFormat(function(v, i) { // i is index of domain colorScale, v is the corrisponding value (v = domain[i])
if(v == 0) {
return v + '%';
}
else {
return v;
}
});
var svgLegend = d3.select('#legend').append('svg').attr('width', 600);
// append title
svgLegend.append('text')
.attr('class', 'legendTitle')
.attr('x', 10)
.attr('y', 20)
.style('text-anchor', 'start')
.text('Legend title');
// draw the rectangle and fill with gradient
svgLegend.append('rect')
.attr('class', 'legendRect')
.attr('x', 10) // position
.attr('y', 30)
.attr('width', 400) // larghezza fascia colorata
.attr('height', 15) // altezza fascia colorata
.style('fill', 'url(#linear-gradient1)')
.on('click', function() {
if(currentFill === '1') {
updateColor2();
currentFill = '2';
}
else {
updateColor1();
currentFill = '1';
}
});
var legend = svgLegend
.attr('class', 'legendLinAxis')
.append('g')
.attr('class', 'legendLinG')
.attr('transform', 'translate(0, 30)'); // 47 è la posizione verticale dei tick (se l'aumenti, scendono) (47 per farli partire sotto, 30 per farli partire da sopra)
var defs = svgLegend.append('defs');
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient1 = defs.append('linearGradient')
.attr('id', 'linear-gradient1')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale1.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale1(d);
});
// horizontal gradient and append multiple color stops by using D3's data/enter step
function getGradient2data() {
// Duplicates elements of domain2:
var duplicatedDomain = domain2.reduce(function (res, current, index, array) { return res.concat([current, current]); }, []).slice(1, -1);
// Duplicates elements of range2:
var duplicatedRange = range2.slice(1).reduce(function (res, current, index, array) { return res.concat([current, current]); }, []);
// Zips both domain and range:
return duplicatedDomain.map( function(e, i) { return { "offset": e + "%", "color": duplicatedRange[i] }; [e, duplicatedRange[i]]; });
}
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(getGradient2data())
//.data([
// { offset: "0%", color: "lightgrey" },
// { offset: "95%", color: "lightgrey" },
// { offset: "95%", color: "grey" },
// { offset: "100%", color: "grey" }
//])
.enter().append('stop')
.attr('offset', function(d) {
return d.offset;
})
.attr('stop-color', function(d) {
return d.color;
});
// update the colors to a different color scale (colorScale1)
function updateColor1() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient1)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
axisLegend.tickValues(colorScale1.domain());
legend.call(axisLegend);
}
// update the colors to a different color scale (colorScale2)
function updateColor2() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient2)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale2(d.val);
}
return col;
});
axisLegend.tickValues(colorScale2.domain());
legend.call(axisLegend);
}
// start set-up
updateColor1();
var currentFill = '1';
#heatmap {
float: left;
background-color: whitesmoke;
}
.cell {
stroke: #E6E6E6;
stroke-width: 1px;
}
/**
* Legend linear.
*/
.legendTitle {
font-size: 15px;
fill: black;
font-weight: 12;
font-family: Consolas, courier;
}
#legendLin {
background-color: yellow;
}
.legendLinAxis path, .legendLinAxis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.legendLinAxis text {
font-family: Consolas, courier;
font-size: 8pt;
fill: black;
}
.legendLinG .tick line {
stroke: black;
stroke-width: 1px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="./style.css" media="screen"/>
</head>
<body>
<div id='heatmap'></div>
<div id='legend'></div>
<script src="./script.js"></script>
</body>
</html>
Ticks update:
Within the update functions (updateColor1, updateColor2), in addition to the update of the color scale gradient, we can also include the update of legend ticks (similar to how it was first initialized):
axisLegend.tickValues(colorScale1.domain());
legend.call(axisLegend);
Gradient update:
The creation of "abrupt gradients" is slightly different from the one of "linear gradients". Here is a slightly modified version of your linear-gradient2 threshold gradient:
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data([
{ offset: "0%", color: "lightgrey" },
{ offset: "95%", color: "lightgrey" },
{ offset: "95%", color: "grey" },
{ offset: "100%", color: "grey" }
])
.enter().append('stop')
.attr('offset', function(d) { return d.offset; })
.attr('stop-color', function(d) { return d.color; });
Or if the threshold gradient is to change, instead of hardcoding it, we can also get it from the defined domain and range:
function getGradient2data() {
// Duplicates elements of domain2:
var duplicatedDomain = domain2.reduce(function (res, current, index, array) { return res.concat([current, current]); }, []).slice(1, -1);
// Duplicates elements of range2:
var duplicatedRange = range2.slice(1).reduce(function (res, current, index, array) { return res.concat([current, current]); }, []);
// Zips both domain and range:
return duplicatedDomain.map( function(e, i) { return { "offset": e + "%", "color": duplicatedRange[i] }; [e, duplicatedRange[i]]; });
}
which produces:
[
{ offset: "0%", color: "lightgrey" },
{ offset: "95%", color: "lightgrey" },
{ offset: "95%", color: "grey" },
{ offset: "100%", color: "grey" }
]

D3 - flare not showing all data

Here is the code I'm working with. I'm generating data in php and sending that to d3 via json:
php file:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: #fff;
}
</style>
<body>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"> </script>
<script type="text/javascript" src="flare.js"></script>
<?php
// Move php data to JSON to be used in d3 apps
$flare_child_1 = array("name"=> "subchild1", "size"=> 90);
$flare_child_2 = array("name"=> "subchild2", "size"=> 10);
$flare_child_3 = array("name"=> "subchild3", "size"=> 55);
$flare_child_4 = array("name"=> "subchild4", "size"=> 72);
$flare_child_5 = array("name"=> "subchild5", "size"=> 60);
$flare_children_1[] = $flare_child_1;
$flare_children_1[] = $flare_child_2;
$flare_children_1[] = $flare_child_3;
$flare_children_1[] = $flare_child_4;
$flare_children_1[] = $flare_child_5;
$flare_children[] = array('name'=> "first", 'children'=>$flare_children_1);
$flare = array('name'=> "flare", 'children'=>$flare_children);
echo "<script> var root = "; echo json_encode($flare); echo ";";
echo "input_data(root);</script>";
?>
</body>
js file
var width = 960,
height = 700,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI,x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI,x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
function getRootmostAncestorByRecursion(node) {
return node.depth > 1 ? getRootmostAncestorByRecursion(node.parent) : node;
}
function input_data(root) {
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(getRootmostAncestorByRecursion(d).name);
})
.on("click", click)
.append("title")
.text(function(d) {
return d.name + "\n" + formatNumber(d.value);
});
}
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); };
})
.selectAll("path")
.attrTween("d", function(d) { return function() { return arc(d);}; });
}
d3.select(self.frameElement).style("height", height + "px");
I'm expecting something similar to this https://bl.ocks.org/mbostock/raw/4348373/
but all I'm seeing a blue spot. The children don't feature. I'm not sure what I'm doing wrong.
Thanks for looking at this.
Couple things wrong here.
First, while you are correct path creation is wrapped in a function and will execute after the php code but your svg creation will execute before the body tag, so you'll never get an svg tag.
Second, your JSON is malformed. I executed the php and it produces:
<script>
var root = {
"name": "flare",
"children": {
"name": "first",
"children": [{
"name": "subchild1",
"size": 90
}, {
"name": "subchild2",
"size": 10
}, {
"name": "subchild3",
"size": 55
}, {
"name": "subchild4",
"size": 72
}, {
"name": "subchild5",
"size": 60
}]
}
};
input_data(root);
Notice, that the first children is an object and not an array of objects.
Putting these two things together here.

d3js - how to add the text each of the arc's centre, after animation end?

I am drawing 8 arcs with animation. my requirement is after adding the arc ( each of them ) i need to add a line with animation from the arc centre and end of the line need to add the text as well ( please see the picture )
But i don't know how to handle this scenario. any one can help to sort this out?
this what i requried :
And here is my try:
var array = [
[500, 600],
[600, 700],
[800, 900],
[900,1000]
]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var val = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function getTweenPie(arc) {
return function (finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var update = function (i){
var x = i*5;
arc = d3.svg.arc()
.innerRadius(radius - (5*x))
.outerRadius(radius - (5.1*x));
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array[i]).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', getTweenPie(arc))
.each('end', function(){
svg1.append('g').append('text')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.text(i)
})
}
for( var i = 0; i < array.length; i++) {
update(i);
}
}
Live Demo
I have updated my code like this :
It's works fine to me.
window.onload = function () {
var objects = {
"payStTotalGraph": [
{
"label": "Total Contract Amount",
"value": 40900000,
"color": "#fff"
},
{
"label": "",
"value": 20
}
],
"payStInvRisedGraph": [
{
"label": "Invoice Raised",
"value": 170000
},
{
"label": "",
"value": 170000
}
],
"payStatusGraph": [
{
"label": "Net Certified",
"value": 8748188.89
},
{
"label": "Remaining",
"value": 33089260.45
}
]
}
var myColor = [
["#e9d600", "#ff0"],
["#27b6a9", "transparent"],
["#bd565b", "#68bc4b"]
]
var w = 200,
h = 200,
r = Math.min(w, h) / 2,
labelr = r + 30, // radius for label anchor
color = d3.scale.category20(),
donut = d3.layout.pie(),
arc = d3.svg.arc().innerRadius(r * 0.97).outerRadius(r);
function getTweenPie(arc) {
return function (finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
}
var vis = d3.select("body").append("svg:svg").attr("width", w + 150).attr("height", h);
var nv = r * 0.97;
var n = 0;
var cr = 2;
var lineW = 80;
var textPoint = lineW + 10;
var tape;
var graphBuilder = function ( d, x ) {
var outer = nv -= 10;
var inner = nv *= 0.97;
arc = d3.svg.arc().innerRadius( inner ).outerRadius( outer );
labelr = outer;
vis.data([d])
var arcs = vis.selectAll("g.arc")
.data(donut.value(function(d, i) { return d.value }))
.enter().append("svg:g")
.attr("class", "arc"+x)
.attr("transform", "translate(" + (r + 30) + "," + r + ")");
//phrse 2
arcs.append("svg:path")
.attr("fill", function(d, i) { return myColor[x][i]; })
.transition()
.duration(5000)
.attrTween('d', getTweenPie(arc))
var g = arcs.append('g')
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x*x + y*y);
return "translate(" + ((x/h * labelr)-3) + ',' + (y/h * labelr) + ")";
});
g.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', cr)
.style('fill', '#f00');
g.append('line')
.attr("transform", "translate(" + cr + ',' + cr + ")" )
.style("stroke", function(c,i){ if(!d[i].label) return; console.log(d[i].label); return "#000"})
.attr("x1", 0)
.attr("y1", -cr)
.attr("x2", 0)
.attr("y2", -cr)
.transition()
.delay(4000)
.duration(1500)
.attr("x2", lineW)
g.append('text')
.attr("transform", "translate(" + textPoint + ',' + cr + ")" )
.transition()
.delay(5000)
.text(function (s, i ) {
return d[i].label;
});
}
for( label in objects ) {
graphBuilder( objects[label], n++ );
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>

Zoomable Sunburst with % Share labeled

I want show percentage share of each block in labelled zoomable sunburst chart. I am referring http://bl.ocks.org/metmajer/5480307 this example.
here I want add (%x) share of each block. Please help.
Below is my index.html
<!DOCTYPE html>
<meta charset="utf-8"><style>
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
</style> <body>
<script src="http://d3js.org/d3.v3.min.js">
</script> <script>
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
d3.json("atmLeads.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc).attr("class",function(d){return "ring_"+ d.depth;})
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
function click(d) {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
</script>
Try this code.
var path = g.append("path")
.attr("d", arc).attr("class", function(d) {
return "ring_" + d.depth;
})
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click);
var totalSize = path.node().__data__.value;
var text = g.append("text")
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
return d.name +" "+percentageString;
});
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) {
return d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
var root = {
"name": "ATM Leads Converted",
"size": 34752,
"children": [
{
"name": "Converted",
"size": 417
}, {
"name": "Failure",
"size": 1366
}, {
"name": "Interested",
"size": 916
}, {
"name": "No Value",
"size": 48932
}, {
"name": "Not Interested",
"size": 14479
}, {
"name": "Not contactable",
"size": 2961
},
{
"name": "Success",
"size": 1142
}, {
"name": "Will Get Back",
"size": 1564
}, {
"name": "Wrong Number",
"size": 358
}
]
};
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc).attr("class", function(d) {
return "ring_" + d.depth;
})
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click);
var totalSize = path.node().__data__.value;
var text = g.append("text")
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
return d.name +" "+percentageString;
});
function click(d) {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function(d) {
return y(d.y);
});
}
});
}
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i ? function(t) {
return arc(d);
} : function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Resources