D3.js Gauge Chart Indicators And Labels Positioning - javascript

I am new to D3.js | Data-Driven Documents charts and I am working with a Gauge Chart style using this. For this purpose, I searched over the internet and found the below simple code with some tweaks from my side and it is working well as shown in the below screenshot.
And the code for this is shared below.
D3.js Gauge Chart Basic Code:
/*---------------------
// D3.js Gauge Chart //
---------------------*/
// Data which need to be fetched
var name = "Server Response";
var value = 75; // My Desired Value To Show
var gaugeMaxValue = 100;
// Data of calculation
var percentValue = value / gaugeMaxValue;
var needleClient;
(function () {
var barWidth, chart, chartInset, degToRad, repaintGauge, height, margin, numSections, padRad, percToDeg, percToRad, percent, radius, sectionIndx, svg, totalPercent, width, recalcPointerPos;
percent = percentValue;
numSections = 1;
sectionPerc = 1 / numSections / 2;
padRad = 0.025;
chartInset = 10;
// Orientation of Gauge:
totalPercent = .75;
el = d3.select('.chart-gauge');
margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
};
width = el[0][0].offsetWidth - margin.left - margin.right;
height = width;
radius = Math.min(width, height) / 2;
barWidth = 40 * width / 300;
// Utility methods
percToDeg = function (perc) {
return perc * 360;
};
percToRad = function (perc) {
return degToRad(percToDeg(perc));
};
degToRad = function (deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height / 1.5 + margin.top + margin.bottom); // height/1.5 To Remove Extra Bottom Space
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-first");
chart.append('path').attr('class', "arc chart-second");
chart.append('path').attr('class', "arc chart-third");
arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
repaintGauge = function () {
perc = 0.5;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc1.startAngle(arcStartRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
chart.select(".chart-third").attr('d', arc3);
}
var Needle = (function () {
//Helper function that returns the `d` value for moving the needle
var recalcPointerPos = function (perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = 0;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
function Needle(el) {
this.el = el;
this.len = width / 2.5;
this.radius = this.len / 8;
}
Needle.prototype.render = function () {
this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));
};
Needle.prototype.moveTo = function (perc) {
var self,
oldValue = this.perc || 0;
this.perc = perc;
self = this;
// Reset pointer position
this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function () {
return function (percentOfPercent) {
var progress = (1 - percentOfPercent) * oldValue;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function () {
return function (percentOfPercent) {
var progress = percentOfPercent * perc;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
};
return Needle;
})();
var dataset = [{
metric: name,
value: value
}]
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function () {
return dataset[0].metric;
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 6) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size", 25)
.style("fill", "#000000");
var trX = 180 - 210 * Math.cos(percToRad(percent / 2));
var trY = 195 - 210 * Math.sin(percToRad(percent / 2));
// (180, 195) are the coordinates of the center of the gauge.
displayValue = function () {
texts.append("text")
.text(function () {
return dataset[0].value;
})
.attr('id', "Value")
.attr('transform', "translate(" + trX + ", " + trY + ")")
.attr("font-size", 18)
.style("fill", '#000000');
}
texts.append("text")
.text(function () {
return 0;
})
.attr('id', 'scale0')
.attr('transform', "translate(" + ((width + margin.left) / 100) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function () {
return gaugeMaxValue / 2;
})
.attr('id', 'scale10')
.attr('transform', "translate(" + ((width + margin.left) / 2.15) + ", " + ((height + margin.top) / 30) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function () {
return gaugeMaxValue;
})
.attr('id', 'scale20')
.attr('transform', "translate(" + ((width + margin.left) / 1.03) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
needle = new Needle(chart);
needle.render();
needle.moveTo(percent);
setTimeout(displayValue, 1350);
})();
.chart-gauge {}
.chart-first {fill:#458B00}
.chart-second {fill:#e6e600}
.chart-third {fill:#e92213}
.needle,.needle-center {fill:#464A4F}
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
<div class="chart-gauge"></div>
Alternate: JS Fiddle (https://jsfiddle.net/t019mo73/)
Now I have some problems here that I want to fix. First of all I want to fix the positioning of labels and then Second I want to add more indicators and Third one is that I want to split Green, Yellow, Red section as per my desires like 15%, 60%, 25% as an example. What I want is also demonstrated in the below screenshot.
So is this anything possible to fix it in my upper mentioned code?

Related

d3: To add inner grey dotted line in Gauge Chart

I'm new in d3.js graph. I have developed a d3 Gauge Chart with version 5.16.0. But I need to add inner grey dotted line as shown in the attached image. I have attached the image. Can you help me to achieve that inner grey dotted line?
My .cshtml file:
Script version 5.16.0
<div id="OverviewChart"></div>
My css file:
.chart-gauge {
}
.chart-first {
fill: #1A9AD4
}
.chart-second {
fill: #10B981
}
.chart-third {
fill: #F59E0B
}
.chart-fourth {
fill: #EF4444
}
.needle, .needle-center {
fill: #464A4F
}
My Js file:
var width = 288;
var height = 288;
var radius = Math.min(width, height) / 2;
var chartInset = 10;
var barWidth = 20;
var innerRadius = radius - chartInset - barWidth;
var outerRadius = radius - chartInset;
var color = "#66AB8C";
var totalPercent = .75;
var padRad = 0.025;
//****Utility methods****
var percToRad = function (perc) {
return degToRad(percToDeg(perc));
};
var degToRad = function (deg) {
return deg * Math.PI / 180;
};
var percToDeg = function (perc) {
return perc * 360;
};
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
};
//****Set up the SVG container****
const svg = d3.select("#OverviewChart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height / 1.5 + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-first");
chart.append('path').attr('class', "arc chart-second");
chart.append('path').attr('class', "arc chart-third");
chart.append('path').attr('class', "arc chart-fourth");
//****Set up the scales****
const scale = d3.scaleLinear()
.domain([0, 100]) // The range of possible values
.range([0, 2 * Math.PI]); // The range of angles in radians
//****Set up the arc generator*****
perc = 0.5;
perc1 = 0.25;
perc2 = 0.25;
perc3 = 0.5;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
const arc1 = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(arcStartRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc1 / 3);
next_start += perc1 / 3;
const arc2 = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc2 / 3);
next_start += perc2 / 3;
const arc3 = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc3 / 3);
const arc4 = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(arcStartRad + padRad).endAngle(arcEndRad);
////****Add the gauge to the SVG****
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
chart.select(".chart-third").attr('d', arc3);
chart.select(".chart-fourth").attr('d', arc4);
//svg.append("path")
// .attr('class', 'arc chart-first')
// .attr("d", arc1)
// .style("fill", color);
//****Add the value text to the center of the gauge****
//svg.append("text")
// .attr("x", width / 2)
// .attr("y", height / 2)
// .text("Hi")
// .style("text-anchor", "middle")
// .style("font-size", "24px");
var dataset = [{
metric: "Overall Task",//name,
//value: value
}]
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function () {
return "388";
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 2.3) + ", " + ((height + margin.top) / 2.5) + ")")
.attr("font-family", "Open Sans")
.attr("font-size", 28)
.attr("font-weight", 600)
.attr("line-height", 34)
.attr("letter-spacing", -0.02)
.attr("text-align", "center")
.style("fill", "#000000");
texts.append("text")
.text(function () {
return dataset[0].metric;
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 3) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-family", "Open Sans")
.attr("font-size", 18)
.attr("font-weight", 600)
.attr("line-height", 22)
.attr("letter-spacing", 0)
.attr("text-align", "left")
.style("fill", "#000000");
texts.append("text")
.text(function () {
return "Updated on 07/11/2022";
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 3.5) + ", " + ((height + margin.top) / 1.37) + ")")
.attr("font-family", "Open Sans")
.attr("font-size", 12)
.attr("font-weight", 400)
.attr("line-height", 18)
.attr("letter-spacing", 0)
.attr("text-align", "left")
.style("fill", "#000000");

How to wrap a heatmap on a spiral (seasonal spiral) - d3?

I'm trying to wrap a heatmap with daily data on a spiral. Following this example I want to place days beneath eachother and move to the right when the week changes. If using the above example I get days laid next to eachother.
I tried to adapt the code from the example although I made good progress any help would be appreciated. The segments don't align nicely and there are some paths that shouldn't be there. Surely I made some mistakes while adapting the calculations.
Wanted result
Current progress
Code
const radians = 0.0174532925;
//CHART CONSTANTS
const chartRadius = 100;
const chartWidth = chartRadius * 3;
const chartHeight = chartRadius * 3;
const labelRadius = chartRadius + 5;
const margin = { "top": 180, "bottom": 40, "left": 180, "right": 40 };
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
//CHART OPTIONS
const holeRadiusProportion = 0.75; //fraction of chartRadius. 0 gives you some pointy arcs in the centre.
const holeRadius = holeRadiusProportion * chartRadius;
const segmentsPerCoil = 52; //number of coils. for this example, I have 12 months per year. But you change to whatever suits your data.
const segmentAngle = 360 / segmentsPerCoil;
let coils; //number of coils, based on data.length / segmentsPerCoil
let coilWidth; //remaining chartRadius (after holeRadius removed), divided by coils + 1. I add 1 as the end of the coil moves out by 1 each time
//SCALES
const colour = d3.scaleSequential(d3.interpolateViridis);
//CREATE SVG AND A G PLACED IN THE CENTRE OF THE SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", chartWidth + margin.left + margin.right)
.attr("height", chartHeight + margin.top + margin.bottom);
const g = svg.append("g")
.attr("transform", "translate("
+ (margin.left + chartRadius)
+ ","
+ (margin.top + chartRadius) + ")");
// count the weeks
let week = 0
//LOAD THE DATA
d3.csv("restrictions_daily.csv", convertTextToNumbers, function (error, data) {
if (error) { throw error; };
// get the week number
data.forEach(function (d) {
const dateParse = d3.timeParse("%d/%m/%Y")(d.date)
d.week = week
if (+d3.timeFormat("%d")(dateParse) % 7 === 0)
week = week + 1
})
//CALCULATE AND STORE THE REMAING
let dataLength = 52
coils = Math.ceil(dataLength / segmentsPerCoil);
coilWidth = (chartRadius * (1 - holeRadiusProportion)) / (coils + 1);
//console.log("coilWidth: " + coilWidth);
var dataExtent = d3.extent(data, function (d) { return d.value; });
colour.domain(dataExtent);
//ADD LABELS AND GRIDS FOR EACH MONTH FIRST
//SO THE GRID LINES APPEAR BEHIND THE SPIRAL
var monthLabels = g.selectAll(".month-label")
.data(months)
.enter()
.append("g")
.attr("class", "month-label");
monthLabels.append("text")
// .text(function (d) { return d; })
.attr("x", function (d, i) {
let labelAngle = (i * segmentAngle) + (segmentAngle / 2);
return x(labelAngle, labelRadius);
})
.attr("y", function (d, i) {
let labelAngle = (i * segmentAngle) + (segmentAngle / 2);
return y(labelAngle, labelRadius);
})
.style("text-anchor", function (d, i) {
return i < (months.length / 2) ? "start" : "end";
});
monthLabels.append("line")
.attr("x2", function (d, i) {
let lineAngle = (i * segmentAngle);
let lineRadius = chartRadius + 10;
// return x(lineAngle, lineRadius);
})
.attr("y2", function (d, i) {
let lineAngle = (i * segmentAngle);
let lineRadius = chartRadius + 10;
// return y(lineAngle, lineRadius);
});
// reset the days when new week starts
let firstDay = 0
//ASSUMING DATA IS SORTED, CALCULATE EACH DATA POINT'S SEGMENT VERTICES
data.forEach(function (d, i) {
let coil = Math.floor(i / segmentsPerCoil);
let position = +d.week - 1;
console.log(d)
// divide radius by 7 to get proportion for each day
const dayHeight = holeRadius / 7
// reset day of the week
if (i % 7 === 0) {
firstDay = 0
}
const newRadius = (dayHeight * firstDay) + 100
// increment the day
firstDay = firstDay + 1
//console.log("positions: " + i + " " + coil + " " + position);
let startAngle = position * segmentAngle;
let endAngle = (position + 1) * segmentAngle;
//console.log("angles: " + startAngle + " " + endAngle);
//console.log(holeRadius + " " + segmentsPerCoil + " " + coilWidth)
let startInnerRadius = newRadius + ((i / segmentsPerCoil) * coilWidth)
let startOuterRadius = newRadius + ((i / segmentsPerCoil) * coilWidth) + coilWidth;
let endInnerRadius = newRadius + (((i + 1) / segmentsPerCoil) * coilWidth)
let endOuterRadius = newRadius + (((i + 1) / segmentsPerCoil) * coilWidth) + coilWidth;
console.log(startInnerRadius, startOuterRadius, endInnerRadius, endInnerRadius, startAngle, endAngle)
//console.log("start radi: " + startInnerRadius + " " + startOuterRadius);
//console.log("end radi: " + endInnerRadius + " " + endOuterRadius);
//vertices of each segment
d.x1 = x(startAngle, startInnerRadius);
d.y1 = y(startAngle, startInnerRadius);
d.x2 = x(endAngle, endInnerRadius);
d.y2 = y(endAngle, endInnerRadius);
d.x3 = x(endAngle, endOuterRadius);
d.y3 = y(endAngle, endOuterRadius);
d.x4 = x(startAngle, startOuterRadius);
d.y4 = y(startAngle, startOuterRadius);
//CURVE CONTROL POINTS
let midAngle = startAngle + (segmentAngle / 2)
let midInnerRadius = newRadius + (((i + 0.5) / segmentsPerCoil) * coilWidth)
let midOuterRadius = newRadius + (((i + 0.5) / segmentsPerCoil) * coilWidth) + coilWidth;
//MID POINTS, WHERE THE CURVE WILL PASS THRU
d.mid1x = x(midAngle, midInnerRadius);
d.mid1y = y(midAngle, midInnerRadius);
d.mid2x = x(midAngle, midOuterRadius);
d.mid2y = y(midAngle, midOuterRadius);
//FROM https://stackoverflow.com/questions/5634460/quadratic-b%C3%A9zier-curve-calculate-points
d.controlPoint1x = (d.mid1x - (0.25 * d.x1) - (0.25 * d.x2)) / 0.5;
d.controlPoint1y = (d.mid1y - (0.25 * d.y1) - (0.25 * d.y2)) / 0.5;
d.controlPoint2x = (d.mid2x - (0.25 * d.x3) - (0.25 * d.x4)) / 0.5;
d.controlPoint2y = (d.mid2y - (0.25 * d.y3) - (0.25 * d.y4)) / 0.5;
//console.log(d);
});
var arcs = g.selectAll(".arc")
.data(data)
.enter()
.append("g")
.attr("class", "arc");
//STRAIGHT EDGES
/*
arcs.append("path")
.attr("d", function (d) {
let M = "M " + d.x1 + " " + d.y1;
let L1 = "L " + d.x2 + " " + d.y2;
let L2 = "L " + d.x3 + " " + d.y3;
let L3 = "L " + d.x4 + " " + d.y4;
return M + " " + L1 + " " + L2 + " " + L3 + " Z"
})
//.style("fill", function (d) { return colour(d.value); })
.style("fill", "white")
.style("stroke", "white")
*/
//CURVED EDGES
arcs.append("path")
.attr("d", function (d) {
//start at vertice 1
let start = "M " + d.x1 + " " + d.y1;
//inner curve to vertice 2
let side1 = " Q " + d.controlPoint1x + " " + d.controlPoint1y + " " + d.x2 + " " + d.y2;
//straight line to vertice 3
let side2 = "L " + d.x3 + " " + d.y3;
//outer curve vertice 4
let side3 = " Q " + d.controlPoint2x + " " + d.controlPoint2y + " " + d.x4 + " " + d.y4;
//combine into string, with closure (Z) to vertice 1
return start + " " + side1 + " " + side2 + " " + side3 + " Z"
})
.style("fill", function (d) { return colour(d.value); })
.style("stroke", "white")
//ADD LABELS FOR THE YEAR AT THE START OF EACH COIL (IE THE FIRST MONTH)
var yearLabels = arcs.filter(function (d) { return d.month == 1; }).raise();
yearLabels.append("path")
.attr("id", function (d) { return "path-" + d.year; })
.attr("d", function (d) {
//start at vertice 1
let start = "M " + d.x1 + " " + d.y1;
//inner curve to vertice 2
let side1 = " Q " + d.controlPoint1x + " " + d.controlPoint1y + " " + d.x2 + " " + d.y2;
return start + side1;
})
.style("fill", "none")
//.style("opacity", 0);
yearLabels.append("text")
.attr("class", "year-label")
.attr("x", 3)
.attr("dy", -5)
.append("textPath")
.attr("xlink:href", function (d) {
return "#path-" + d.year;
})
.text(function (d) { return d.year; })
// //DRAW LEGEND
//
// const legendWidth = chartRadius;
// const legendHeight = 20;
// const legendPadding = 40;
//
// var legendSVG = d3.select("#legend")
// .append("svg")
// .attr("width", legendWidth + legendPadding + legendPadding)
// .attr("height", legendHeight + legendPadding + legendPadding);
//
// var defs = legendSVG.append("defs");
//
// var legendGradient = defs.append("linearGradient")
// .attr("id", "linear-gradient")
// .attr("x1", "0%")
// .attr("y1", "0%")
// .attr("x2", "100%")
// .attr("y2", "0%");
//
// let noOfSamples = 20;
// let dataRange = dataExtent[1] - dataExtent[0];
// let stepSize = dataRange / noOfSamples;
//
// for (i = 0; i < noOfSamples; i++) {
// legendGradient.append("stop")
// .attr("offset", (i / (noOfSamples - 1)))
// .attr("stop-color", colour(dataExtent[0] + (i * stepSize)));
// }
//
// var legendG = legendSVG.append("g")
// .attr("class", "legendLinear")
// .attr("transform", "translate(" + legendPadding + "," + legendPadding + ")");
//
// legendG.append("rect")
// .attr("x", 0)
// .attr("y", 0)
// .attr("width", legendWidth)
// .attr("height", legendHeight)
// .style("fill", "url(#linear-gradient)");
//
// legendG.append("text")
// .text("Fewer nights")
// .attr("x", 0)
// .attr("y", legendHeight - 35)
// .style("font-size", "12px");
//
// legendG.append("text")
// .text("More nights")
// .attr("x", legendWidth)
// .attr("y", legendHeight - 35)
// .style("text-anchor", "end")
// .style("font-size", "12px");
//
});
function x(angle, radius) {
//change to clockwise
let a = 360 - angle;
//start from 12 o'clock
a = a + 180;
return radius * Math.sin(a * radians);
};
function y(angle, radius) {
//change to clockwise
let a = 360 - angle;
//start from 12 o'clock
a = a + 180;
return radius * Math.cos(a * radians);
};
function convertTextToNumbers(d) {
d.year = +d.year;
d.month = +d.month;
d.value = +d.value;
return d;
};
JSFiddle
Thanks for any help & suggestions!
Here is an example of spiralArc (kind of D3's arc with changing radius):
const spiralArc = (fromRadius, toRadius, width, fromAngle, toAngle) => {
const x1 = fromRadius * Math.sin(fromAngle);
const y1 = fromRadius * -Math.cos(fromAngle);
const x2 = (fromRadius + width) * Math.sin(fromAngle);
const y2 = (fromRadius + width) * -Math.cos(fromAngle);
const x3 = toRadius * Math.sin(toAngle);
const y3 = toRadius * -Math.cos(toAngle);
const x4 = (toRadius + width) * Math.sin(toAngle);
const y4 = (toRadius + width) * -Math.cos(toAngle);
return `
M ${x1},${y1}
L ${x2},${y2}
A ${fromRadius},${fromRadius} 1 0 1 ${x4},${y4}
L ${x3},${y3}
A ${fromRadius},${fromRadius} 0 0 0 ${x1},${y1}`;
}
const svg = d3.select('svg');
const g = svg.append('g')
.attr('transform', 'translate(300,300)')
const WIDTH = 10;
const BASE_RADIUS = 30;
const angle = Math.PI * 2 / 30;
for (let index = 0; index < 100; index++) {
const fromAngle = angle * index;
const toAngle = angle * (index + 1);
for (let level = 0; level < 5; level++) {
const fromRadius = BASE_RADIUS + index * 2 + WIDTH * level;
const toRadius = BASE_RADIUS + (index + 1) * 2 + WIDTH * level;
const path = spiralArc (fromRadius, toRadius, WIDTH, fromAngle, toAngle);
const color = `rgb(0,${192 + Math.random() * 64},255)`
g.append('path').attr('d', path).style('fill', color)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width="600" height="600" />

Gauge D3.js display value at the top of the needle

I'm kinda new to D3.js, and I managed to create a gauge with what I found on the Internet. But I couldn't find any gauge showing the current value at the top of the needle.
Something like this : what I want
Obviously I'd like the value to follow the needle. I tried to add "text" attribute to the needle itself, but it didn't work.
Here's a codepen link : http://codepen.io/kazu_codepen/pen/wGmGjv?editors=1010
Here's my js code :
// data which need to be fetched
var name = "azerty";
var value = 17;
var gaugeMaxValue = 100;
// data to calculate
var percentValue = value / gaugeMaxValue;
////////////////////////
var needleClient;
(function(){
var barWidth, chart, chartInset, degToRad, repaintGauge,
height, margin, numSections, padRad, percToDeg, percToRad,
percent, radius, sectionIndx, svg, totalPercent, width;
percent = percentValue;
numSections = 1;
sectionPerc = 1 / numSections / 2;
padRad = 0.025;
chartInset = 10;
// Orientation of gauge:
totalPercent = .75;
el = d3.select('.chart-gauge');
margin = {
top: 20,
right: 20,
bottom: 30,
left: 20
};
width = el[0][0].offsetWidth - margin.left - margin.right;
height = width;
radius = Math.min(width, height) / 2;
barWidth = 40 * width / 300;
//Utility methods
percToDeg = function(perc) {
return perc * 360;
};
percToRad = function(perc) {
return degToRad(percToDeg(perc));
};
degToRad = function(deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-first");
chart.append('path').attr('class', "arc chart-second");
chart.append('path').attr('class', "arc chart-third");
arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
repaintGauge = function ()
{
perc = 0.5;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc1.startAngle(arcStartRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
chart.select(".chart-third").attr('d', arc3);
}
/////////
var dataset = [{metric:name, value: value}]
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function(){
return dataset[0].metric;
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 6) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size",25)
.style("fill", "#000000");
texts.append("text")
.text(function(){
return dataset[0].value;
})
.attr('id', "Value")
.attr('transform', "translate(" + ((width + margin.left) / 1.4) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size",25)
.style("fill", "#000000");
texts.append("text")
.text(function(){
return 0;
})
.attr('id', 'scale0')
.attr('transform', "translate(" + ((width + margin.left) / 100 ) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function(){
return gaugeMaxValue/2;
})
.attr('id', 'scale10')
.attr('transform', "translate(" + ((width + margin.left) / 2.15 ) + ", " + ((height + margin.top) / 30) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function(){
return gaugeMaxValue;
})
.attr('id', 'scale20')
.attr('transform', "translate(" + ((width + margin.left) / 1.03 ) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
var Needle = (function() {
//Helper function that returns the `d` value for moving the needle
var recalcPointerPos = function(perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = 0;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
function Needle(el) {
this.el = el;
this.len = width / 2.5;
this.radius = this.len / 8;
}
Needle.prototype.render = function() {
this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
///////
/**
*
* I tried to add text here
*
*/
///////
return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));
};
Needle.prototype.moveTo = function(perc) {
var self,
oldValue = this.perc || 0;
this.perc = perc;
self = this;
// Reset pointer position
this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function() {
return function(percentOfPercent) {
var progress = (1 - percentOfPercent) * oldValue;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function() {
return function(percentOfPercent) {
var progress = percentOfPercent * perc;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
};
return Needle;
})();
needle = new Needle(chart);
needle.render();
needle.moveTo(percent);
})();
And here's my html code :
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css" src="gauge.css">
.chart-gauge
{
width: 400px;
margin: 100px auto
}
.chart-first
{
fill: #9FBD35;
}
.chart-second
{
fill: #F2BA3A;
}
.chart-third
{
fill: #FB3033;
}
.needle, .needle-center
{
fill: #000000;
}
.text {
color: "#112864";
font-size: 16px;
}
svg {
font: 10px sans-serif;
}
</style>
</head>
<body>
<div class="chart-gauge"></div>
<script type="text/javascript" src="./gaugeClient.js"></script>
<script type="text/javascript" src="./labels.js"></script>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</body>
</html>
If anyone could help, I've spent few days trying unsuccessfully.
Thanks.
Here's an updated codepen
Changes made:
1) As part of initialization, create element valueText
valueText = chart.append("text")
.attr('id', "Value")
.attr("font-size",16)
.attr("text-anchor","middle")
.attr("dy",".5em")
.style("fill", '#000000');
2) Also at initialization, create formatValue, to format percent
formatValue = d3.format('1%');
3) At each frame of the transition, compute the position of the text. It adds an offset of 45 units to self.len (the needle's length) to move the text outwards.
var thetaRad = percToRad(progress / 2);
var textX = - (self.len + 45) * Math.cos(thetaRad);
var textY = - (self.len + 45) * Math.sin(thetaRad);
4) Translate valueText based on the computed position and update its text from current progress
valueText.text(formatValue(progress))
.attr('transform', "translate("+textX+","+textY+")")
Well, I found out something waiting for a better solution.
I'm using this :
var trX = 180 - 210 * Math.cos(percToRad(percent / 2));
var trY = 195 - 210 * Math.sin(percToRad(percent / 2));
// (180, 195) are the coordinates of the center of the gauge.
displayValue = function() {
texts.append("text")
.text(function(){
return dataset[0].value;
})
.attr('id', "Value")
.attr('transform', "translate(" + trX + ", " + trY+ ")")
.attr("font-size",18)
.style("fill", '#FB0006');
}
[...] then calling this :
setTimeout(displayValue, 1350);
So that it doesn't appear right away, but when the needle has stopped at its position.
It works, in the way that the value appears on the top of the needle, but I still have two problems.
I'd really like it to follow the needle all the way long during the needle animation when it goes to its value, and I'd like something more general than using the coordinates I found out by calculating, is there a function which can give me the center of the gauge for example ?
I edited the pen given above to that version of the gauge. (http://codepen.io/kazu_codepen/pen/wGmGjv?editors=1010)
Anyway, this solution doesn't really fit my expectations, but I'll use it till I can find something better.

d3.js network curved path control [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
//latest fiddle
http://jsfiddle.net/rbLk2fbe/2/
I am trying to build this particular chart where I can control the curved paths to show a network. Two trunks that are spaced correctly - and then curved branches that leaf off to the various people.
var w = 600;
var h = 600;
var data = [{
"userName": "You",
"userImage": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSTzjaQlkAJswpiRZByvgsb3CVrfNNLLwjFHMrkZ_bzdPOWdxDE2Q"
}, {
"userName": "Johnny",
"userImage": "https://crossovercomicblog.files.wordpress.com/2012/08/johnny-depp-sexy.jpg"
}, {
"userName": "Jeri",
"userImage": "https://68.media.tumblr.com/avatar_3b6d6241698f_128.png"
}, {
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Angelina",
"userImage": "https://pbs.twimg.com/profile_images/713650489032908800/nO1dMt6M_400x400.jpg"
}, {
"userName": "Them",
"userImage": "https://68.media.tumblr.com/avatar_8f199caf2d82_128.png"
}];
var viz = d3.select("#viz")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(40,100)");
var patternsSvg = viz.append('g')
.attr('class', 'patterns');
var labelholder = viz.append("g")
.attr("class", "labelholder");
var treeholder = viz.append("g")
.attr("class", "treeholder");
var userholder = viz.append("g")
.attr("class", "userholder");
var smallRadius = 20;
var bigRadius = 30;
var smallX = smallRadius + (bigRadius/2);
var bigX = (bigRadius*2) + (smallRadius/2);
var verticalGap = (bigRadius * 2) - 5;
var count = data.length;
var extendedY = (count-2 * (smallRadius*2)) + ((bigRadius*2) * 2);
var arcRadiusLeft = (bigRadius / 2);
var arcRadiusRight = -(bigRadius / 2);
$.each(data, function(index, value) {
var defs = patternsSvg.append('svg:defs');
//big design
defs.append('svg:pattern')
.attr('id', index + "--" + value.userName.toLowerCase())
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', bigRadius * 2)
.attr('height', bigRadius * 2);
//small design
defs.append('svg:pattern')
.attr('id', index + "-" + value.userName.toLowerCase())
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', smallRadius * 2)
.attr('height', smallRadius * 2);
});
//plot people circles
var circle = userholder.append("g").selectAll("circle")
.data(data);
circle
.enter()
.append("svg:circle")
.attr("id", function(d) {
return d.userName.toLowerCase();
})
.attr("r", function(d, i) {
var rad = smallRadius;
//first and last items -- so you and them
if (i == 0 || i == count - 1) {
rad = bigRadius;
}
return rad;
})
.attr("cx", function(d, i) {
var cx;
if (i == 0) {
cx = 0; //first one
} else if (i == count - 1) {
cx = bigX; //last one
} else {
cx = smallX; //small ones
}
return cx;
})
.attr("cy", function(d, i) {
var cy;
if (i == 0) {
cy = 0;
} else if (i == count - 1) {
cy = verticalGap * (i-1) + extendedY + bigRadius;
} else {
cy = verticalGap * i
}
return cy;
})
.style("fill", function(d, i) {
var id = i + "-" + d.userName.toLowerCase(); //small circles
//large circles
if (i == 0 || i == count - 1) {
id = i + "--" + d.userName.toLowerCase();
}
return "url(#" + id + ")";
});
//plot people circles
//__labels
var labelholder = d3.select(".labelholder");
//__ enter
var labels = labelholder.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "left")
//__ update
labels
.attr("x", function(d, i) {
var displacement = (bigRadius/2) + smallRadius;
var cx = (smallRadius * 2);
if (i == 0) {
cx = bigRadius;
displacement = bigRadius/2;
}
if (i == count - 1) {
cx = (bigRadius * 2) + bigRadius;
displacement = bigRadius;
}
cx += displacement;
return cx;
})
.attr("y", function(d, i) {
var cy = verticalGap * i;
if (i == count - 1) {
cy += extendedY - (bigRadius/2);
}
return cy;
})
.text(function(d) {
return d.userName;
});
//__labels
var backbone = treeholder.append("g")
.append("svg:path");
backbone.attr("d", function(d, i) {
var sx = (bigRadius / 2) - (bigRadius / 2);
var tx = (bigRadius / 2) - (bigRadius / 2);
var r = smallRadius;
if (i == 0 || i == count - 1) {
r = bigRadius;
}
var sy = ((r / 2) * i) + (r);
var ty = verticalGap * (count - 2) - arcRadiusLeft;
var dr = 0;
return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
});
var displaceYBackboneRight = (bigRadius / 2) + 5;
var backbone = treeholder.append("g")
.append("svg:path");
backbone.attr("d", function(d, i) {
var sx = (bigRadius * 2) + smallRadius/2;
var tx = (bigRadius * 2) + smallRadius/2;
var r = smallRadius;
if (i == 0 || i == count - 1) {
r = bigRadius;
}
var sy = ((r / 2) * i) + (r) + smallRadius + displaceYBackboneRight;
var ty = verticalGap * (count - 2) + extendedY;
var dr = 0;
return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
});
//branches on the left
var leftpath = treeholder.append("g").selectAll("path.leftpath")
.data(data)
leftpath
.enter().append("svg:path")
.attr("class", function(d) {
return "leftpath";
});
leftpath.attr("d", function(d, i) {
var sx = 0;
var tx = arcRadiusLeft;
var sy = verticalGap * i - arcRadiusLeft;
var ty = verticalGap * i;
if (i != 0 && i != count - 1) {
return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
}
});
//branches on the left
var rightpath = treeholder.append("g").selectAll("path.rightpath")
.data(data)
rightpath
.enter().append("svg:path")
.attr("class", function(d) {
return "rightpath";
});
rightpath.attr("d", function(d, i) {
var sx = (bigRadius*2) + (smallRadius/2);
var tx = arcRadiusRight + (bigRadius*2) + (smallRadius/2);
var sy = verticalGap * i + (bigRadius / 2);
var ty = verticalGap * i + arcRadiusRight + (bigRadius / 2);
if (i != 0 && i != count - 1) {
return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
}
});
//old
http://jsfiddle.net/NYEaX/1811/
So the key here is to create the small arcs correctly using maths.
Something like this
path.attr("d", function (d, i) {
const sx = 0;
const sy = height/2;
//width - total width of chart from subject a to subject b
const a = width/2;
//a - distance between subject a and center
const b = (1.5-i)*distanceBetween;
//b - distance between trait 1 and trait 2 (between the dots)
const c = Math.sqrt(a * a + b * b);
//c - is the diagonal distance between subject a and trait 1
const angle=Math.atan(a/b);
//angle - between group to subject a
const r=1/2*c/Math.cos(angle);
//r - 1/2 the distance of c -- divided by cos of angle -- will create a radius to draw the arc
//also equals c/b*(c/2)
// const r=c/b*(c/2);
return `M${sx},${sy} A${r},${r} 0 0,${b>0?1:0} ${width},${height/2}`;
});
Live demo:
http://jsfiddle.net/blackmiaool/857edt69/3/
The first link in the question provides complete solution.
There's nothing difficult in the question. What you need is just a decent coordinate system.
Code to set the d of avatars:
.attr("cx", function (d, i) {
var cx;
if (i == 0) {
cx = 0; //first one
} else if (i == count - 1) {
cx = bigX; //last one
} else {
cx = smallX; //small ones
}
return cx;
})
.attr("cy", function (d, i) {
var cy;
if (i == 0) {
cy = 0;
} else if (i == count - 1) {
cy = verticalGap * (i - 1) + extendedY;
} else {
cy = verticalGap * i
}
return cy;
})
Code to shape left arcs:
leftpath.attr("d", function (d, i) {
var dist = bigRadius * 2;
var sx = 0;
var tx = arcRadiusLeft;
var sy = verticalGap * i - arcRadiusLeft;
var ty = verticalGap * i;
if (i != 0 && i != count - 1) {
return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
}
});
The left arcs are handled. I think you can handle the right ones yourself.
//latest js fiddle - stable with control for all paths in place - based on blackmiaool's answer.
http://jsfiddle.net/rbLk2fbe/2/
var w = 600;
var h = 600;
var data = [{
"userName": "You",
"userImage": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSTzjaQlkAJswpiRZByvgsb3CVrfNNLLwjFHMrkZ_bzdPOWdxDE2Q"
}, {
"userName": "Johnny",
"userImage": "https://crossovercomicblog.files.wordpress.com/2012/08/johnny-depp-sexy.jpg"
}, {
"userName": "Jeri",
"userImage": "https://68.media.tumblr.com/avatar_3b6d6241698f_128.png"
}, {
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Charlize",
"userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
},{
"userName": "Angelina",
"userImage": "https://pbs.twimg.com/profile_images/713650489032908800/nO1dMt6M_400x400.jpg"
}, {
"userName": "Them",
"userImage": "https://68.media.tumblr.com/avatar_8f199caf2d82_128.png"
}];
var viz = d3.select("#viz")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(40,100)");
var patternsSvg = viz.append('g')
.attr('class', 'patterns');
var labelholder = viz.append("g")
.attr("class", "labelholder");
var treeholder = viz.append("g")
.attr("class", "treeholder");
var userholder = viz.append("g")
.attr("class", "userholder");
var smallRadius = 20;
var bigRadius = 30;
var smallX = smallRadius + (bigRadius/2);
var bigX = (bigRadius*2) + (smallRadius/2);
var verticalGap = (bigRadius * 2) - 5;
var count = data.length;
var extendedY = (count-2 * (smallRadius*2)) + ((bigRadius*2) * 2);
var arcRadiusLeft = (bigRadius / 2);
var arcRadiusRight = -(bigRadius / 2);
$.each(data, function(index, value) {
var defs = patternsSvg.append('svg:defs');
//big design
defs.append('svg:pattern')
.attr('id', index + "--" + value.userName.toLowerCase())
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', bigRadius * 2)
.attr('height', bigRadius * 2);
//small design
defs.append('svg:pattern')
.attr('id', index + "-" + value.userName.toLowerCase())
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', smallRadius * 2)
.attr('height', smallRadius * 2);
});
//plot people circles
var circle = userholder.append("g").selectAll("circle")
.data(data);
circle
.enter()
.append("svg:circle")
.attr("id", function(d) {
return d.userName.toLowerCase();
})
.attr("r", function(d, i) {
var rad = smallRadius;
//first and last items -- so you and them
if (i == 0 || i == count - 1) {
rad = bigRadius;
}
return rad;
})
.attr("cx", function(d, i) {
var cx;
if (i == 0) {
cx = 0; //first one
} else if (i == count - 1) {
cx = bigX; //last one
} else {
cx = smallX; //small ones
}
return cx;
})
.attr("cy", function(d, i) {
var cy;
if (i == 0) {
cy = 0;
} else if (i == count - 1) {
cy = verticalGap * (i-1) + extendedY + bigRadius;
} else {
cy = verticalGap * i
}
return cy;
})
.style("fill", function(d, i) {
var id = i + "-" + d.userName.toLowerCase(); //small circles
//large circles
if (i == 0 || i == count - 1) {
id = i + "--" + d.userName.toLowerCase();
}
return "url(#" + id + ")";
});
//plot people circles
//__labels
var labelholder = d3.select(".labelholder");
//__ enter
var labels = labelholder.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "left")
//__ update
labels
.attr("x", function(d, i) {
var displacement = (bigRadius/2) + smallRadius;
var cx = (smallRadius * 2);
if (i == 0) {
cx = bigRadius;
displacement = bigRadius/2;
}
if (i == count - 1) {
cx = (bigRadius * 2) + bigRadius;
displacement = bigRadius;
}
cx += displacement;
return cx;
})
.attr("y", function(d, i) {
var cy = verticalGap * i;
if (i == count - 1) {
cy += extendedY - (bigRadius/2);
}
return cy;
})
.text(function(d) {
return d.userName;
});
//__labels
var backbone = treeholder.append("g")
.append("svg:path");
backbone.attr("d", function(d, i) {
var sx = (bigRadius / 2) - (bigRadius / 2);
var tx = (bigRadius / 2) - (bigRadius / 2);
var r = smallRadius;
if (i == 0 || i == count - 1) {
r = bigRadius;
}
var sy = ((r / 2) * i) + (r);
var ty = verticalGap * (count - 2) - arcRadiusLeft;
var dr = 0;
return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
});
var displaceYBackboneRight = (bigRadius / 2) + 5;
var backbone = treeholder.append("g")
.append("svg:path");
backbone.attr("d", function(d, i) {
var sx = (bigRadius * 2) + smallRadius/2;
var tx = (bigRadius * 2) + smallRadius/2;
var r = smallRadius;
if (i == 0 || i == count - 1) {
r = bigRadius;
}
var sy = ((r / 2) * i) + (r) + smallRadius + displaceYBackboneRight;
var ty = verticalGap * (count - 2) + extendedY;
var dr = 0;
return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
});
//branches on the left
var leftpath = treeholder.append("g").selectAll("path.leftpath")
.data(data)
leftpath
.enter().append("svg:path")
.attr("class", function(d) {
return "leftpath";
});
leftpath.attr("d", function(d, i) {
var sx = 0;
var tx = arcRadiusLeft;
var sy = verticalGap * i - arcRadiusLeft;
var ty = verticalGap * i;
if (i != 0 && i != count - 1) {
return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
}
});
//branches on the left
var rightpath = treeholder.append("g").selectAll("path.rightpath")
.data(data)
rightpath
.enter().append("svg:path")
.attr("class", function(d) {
return "rightpath";
});
rightpath.attr("d", function(d, i) {
var sx = (bigRadius*2) + (smallRadius/2);
var tx = arcRadiusRight + (bigRadius*2) + (smallRadius/2);
var sy = verticalGap * i + (bigRadius / 2);
var ty = verticalGap * i + arcRadiusRight + (bigRadius / 2);
if (i != 0 && i != count - 1) {
return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
}
});

D3 map visible area coordinates

here is my simple code for d3 scalable map:
winWidth = $(window).width();
winHeight = $(window).height();
projection = d3.geo.mercator()
.translate([0, 0])
.scale(winWidth / 2 / Math.PI);
zoom = d3.behavior.zoom()
.scaleExtent([1, 50])
.on("zoom", manageMap);
path = d3.geo.path()
.projection(projection);
svg = d3.select("#map").append("svg")
.attr("viewBox", "0 0 " + winWidth + " " + winHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + winWidth / 2 + "," + winHeight / 2 + ")")
.call(zoom);
g = svg.append("g");
d3.json("world-50m.json", function(error, world) {
g.append("path")
.datum(topojson.feature(world, world.objects.countries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
function manageMap() {
var t = d3.event.translate,
s = d3.event.scale;
t[0] = Math.min(winWidth / 2 * (s - 1), Math.max(winWidth / 2 * (1 - s), t[0]));
t[1] = Math.min(winHeight / 2 * (s - 1) + 230 * s, Math.max(winHeight / 2 * (1 - s) - 230 * s, t[1]));
zoom.translate(t);
g.style("stroke-width", 1/s).attr("transform", "translate(" + t + ")scale(" + s + ")");
svg.select("g").selectAll("circle")
.attr("cx", function(d) { return projection([d.lng, d.lat])[0]; })
.attr("cy", function(d) { return projection([d.lng, d.lat])[1]; })
.attr("r", 11/s);
}
Is there any simple way to have current visible area coordinates, when map is scaled and translated? I'm already try to project translation of map, but just got some strange numbers.
This will do it. I've also put it up on http://bl.ocks.org/sfinktah/1d38c8a268d893d769ed
Even if you have found your solution, this may be useful for future googlers.
function getScreenBounds() {
return [getPoint(0,0), getPoint()];
}
function getPoint(x,y) {
if (x == null) x = winWidth;
if (y == null) y = winHeight;
var container = g.node();
var svg = container.ownerSVGElement || container;
var point = svg.createSVGPoint();
point.x = x, point.y = y;
point = point.matrixTransform(container.getScreenCTM().inverse());
return formatLocation(projection.invert([point.x, point.y]), zoom.scale());
}
function formatLocation(p, k) {
var format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}

Categories

Resources