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" />
Related
I have a d3 radial chart created using some community sample and stack-overflow posts.
Here the two bottom labels and numbers are in mirrored form (A13 and A14). Looking for some snippets to transform only this two in counter-clockwise with numbers top (next to the chart) then label so that it will be in better readable form.
JSFiddle
var data = [
{"name":"A11","value":217,"color":"#fad64b"},
{"name":"A12","value":86,"color":"#f15d5d"},
{"name":"A13","value":79,"color":"#f15d5d"},
{"name":"A14","value":82,"color":"#f15d5d"},
{"name":"A15","value":101,"color":"#fad64b"},
{"name":"A16","value":91,"color":"#fad64b"}
];
var width = 500;
var height = 300;
var barHeight = height / 2 - 15;
var formatNumber = d3.format('s');
var color = d3.scale.ordinal()
.range(['#F15D5D', '#FAD64B']);
var svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'radial')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var extent = [0, d3.max(data, function(d) { return d.value; })];
var lastNum = extent[1];
var percentageOne = (lastNum*25)/100;
var percentageTwo = (lastNum*50)/100;
var percentageThree = (lastNum*75)/100;
var tickValues = [percentageOne, percentageTwo, percentageThree, lastNum];
var barScale = d3.scale.linear()
.domain(extent)
.range([0, barHeight]);
var keys = data.map(function(d, i) {
return d.name;
});
var numBars = keys.length;
// X scale
var x = d3.scale.linear()
.domain(extent)
.range([0, -barHeight]);
// X axis
var xAxis = d3.svg.axis()
.scale(x).orient('left')
.tickFormat(formatNumber)
.tickValues(tickValues);
// Inner circles
var circles = svg.selectAll('circle')
.data(tickValues)
.enter().append('circle')
.attr('r', function(d) {
return barScale(d);
})
.style('fill', 'none')
.style('stroke-width', '0.5px');
// Create arcs
var arc = d3.svg.arc()
.startAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return x; //(i * 2 * Math.PI) / numBars;
})
.endAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return y; //((i + 1) * 2 * Math.PI) / numBars;
})
.innerRadius(0);
// Render colored arcs
var segments = svg.selectAll('path')
.data(data)
.enter().append('path')
.each(function(d) {
d.outerRadius = 0;
})
.attr('class', 'bar')
.style('fill', function(d) {
return d.color;
})
.attr('d', arc);
// Animate segments
segments.transition().ease('elastic').duration(1000).delay(function(d, i) {
return (25 - i) * 10;
})
.attrTween('d', function(d, index) {
var i = d3.interpolate(d.outerRadius, barScale(+d.value));
return function(t) {
d.outerRadius = i(t);
return arc(d, index);
};
});
// Outer circle
svg.append('circle')
.attr('r', barHeight)
.classed('outer', true)
.style('fill', 'none')
.style('stroke-width', '.5px');
// Apply x axis
svg.append('g')
.attr('class', 'x axis')
.call(xAxis);
// Labels
var labelRadius = barHeight * 1.025;
var labels = svg.selectAll('foo')
.data(data)
.enter()
.append('g')
.classed('labels', true);
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-path' + i; })
.attr('d', function(d) {
return 'm0 ' + -(barScale(d.value) + 4) + ' a' + (barScale(d.value) + 4) + ' ' + (barScale(d.value) + 4) + ' 0 1,1 -0.01 0';
});
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-pathnum' + i; })
.attr('d', function(d){
return 'm0 ' + -(barScale(d.value) + 14) + ' a' + (barScale(d.value) + 14) + ' ' + (barScale(d.value) + 14) + ' 0 1,1 -0.01 0';
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-path' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.name.toUpperCase();
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-pathnum' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.value;
});
You need to modify the path for the specific elements that need to be flipped. To do this, I start by storing the angle in your data object:
.each(function(d,i) {
d.outerRadius = 0;
var angleStart = (i/numBars) * 360;
var angleEnd = ((i+1)/numBars) * 360;
d.angle = (angleStart + angleEnd) / 2;
})
Then I tested the angle when creating the paths for the text and I reverse the path for the flipped text case:
var len = barScale(d.value) + 4;
if(d.angle > 91 && d.angle < 269) {
len += 8; // the flipped text is on the inside of the path so we need to draw it further out
return 'M -0.01 ' + (-len) + ' A ' + len + ' ' + len + ' 0 1,0 0 ' + (-len);
}
else {
return 'M 0 ' + (-len) + ' A' + len + ' ' + len + ' 0 1,1 -0.01 ' + (-len);
}
Then, you need to flip your '% around the path' for the placement of the text along the reversed path:
.attr('startOffset', function(d, i) {
if(d.angle > 91 && d.angle < 269)
return (100 - (i * 100 / numBars + 50 / numBars)) + '%';
else
return i * 100 / numBars + 50 / numBars + '%';
})
Working fiddle can be found here: https://jsfiddle.net/FrancisMacDougall/mnrqokqL/
With this result:
I am try to fill colors conditionally but it seems if else loop is not entering the third condition.
Here is the working code but with some error in If Else statement
code
var w = 400;
var h = 40;
var barPadding = 1;
var heybaby = 10;
var aa = 15;
var bb = 17;
var cc = 13;
var dd = 8;
var ee = 11;
var ff = 19;
var gg = 8;
var hh = 15;
var ii = 14;
var jj = 15;
var kk = 16;
var ll = 11;
var mm = 13;
var nn = 12;
var data1heatmap = [ aa, bb, cc, dd, ee, ff, gg, hh, ii, jj,kk, ll, mm, nn ];
//Create SVG element
var svg = d3.select("#chart1")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox","0 0 400 40")
.attr("preserveAspectRatio","xMinYMin meet")
.append("g")
.attr("transform", "translate(1,1)");
svg.selectAll("rect")
.data(data1heatmap)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / data1heatmap.length);
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 25)
.attr("rx", 0)
.attr("ry", 0)
.attr("fill", function(d,i) {
if (i <= 5) {
return "rgb(" + (d *5) + ", " + (d * 1) + ", " + (d * 1) + ")";
}
else if (5 < i <= 9) {
return "rgb(" + (d * 1) + ", " + (d * 3) + ", " + (d * 5) + ")";
}
else {
return "rgb(" + (d * 2) + ", " + (d * 4) + ", " + (d * 2) + ")";
}
});
Here is the working fiddle enter link description here
I dont understand why it is not reading the last condition ?
To be honest, I don't whether I should post this as an answer.
But anyway, your else if statement is wrong.
else if (5 < i && i <= 9) {
return "rgb(" + (d * 1) + ", " + (d * 3) + ", " + (d * 5) + ")";
}
Your mistake :
else if (5 < i <= 9) // this will be always true if the value of i is greater than 5
So, it wan't entering the else condition.
Hope this helps.
var w = 400;
var h = 40;
var barPadding = 1;
var heybaby = 10;
var aa = 15;
var bb = 17;
var cc = 13;
var dd = 8;
var ee = 11;
var ff = 19;
var gg = 8;
var hh = 15;
var ii = 14;
var jj = 15;
var kk = 16;
var ll = 11;
var mm = 13;
var nn = 12;
var data1heatmap = [ aa, bb, cc, dd, ee, ff, gg, hh, ii, jj,kk, ll, mm, nn ];
//Create SVG element
var svg = d3.select("#chart1")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox","0 0 400 40")
.attr("preserveAspectRatio","xMinYMin meet")
.append("g")
.attr("transform", "translate(1,1)");
svg.selectAll("rect")
.data(data1heatmap)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / data1heatmap.length);
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 25)
.attr("rx", 0)
.attr("ry", 0)
.attr("fill", function(d,i) {
if (i <= 5) {
return "rgb(" + (d *5) + ", " + (d * 1) + ", " + (d * 1) + ")";
}
else if (5 < i && i <= 9) {
return "rgb(" + (d * 1) + ", " + (d * 3) + ", " + (d * 5) + ")";
}
else {
return "rgb(" + (d * 2) + ", " + (d * 4) + ", " + (d * 2) + ")";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class = "col-md-6" id = "chart1" >
var w = 400;
var h = 40;
var barPadding = 1;
var heybaby = 10;
var aa = 15;
var bb = 17;
var cc = 13;
var dd = 8;
var ee = 11;
var ff = 19;
var gg = 8;
var hh = 15;
var ii = 14;
var jj = 15;
var kk = 16;
var ll = 11;
var mm = 13;
var nn = 12;
var data1heatmap = [ aa, bb, cc, dd, ee, ff, gg, hh, ii, jj,kk, ll, mm, nn ];
//Create SVG element
var svg = d3.select("#chart1")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox","0 0 400 40")
.attr("preserveAspectRatio","xMinYMin meet")
.append("g")
.attr("transform", "translate(1,1)");
svg.selectAll("rect")
.data(data1heatmap)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / data1heatmap.length);
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 25)
.attr("rx", 0)
.attr("ry", 0)
.attr("fill", function(d,i) {
if (i <= 5) {
return "rgb(" + (d *5) + ", " + (d * 1) + ", " + (d * 1) + ")";
}
else if (5 < i && i <= 9) {
return "rgb(" + (d * 1) + ", " + (d * 3) + ", " + (d * 5) + ")";
}
else {
return "rgb(" + (d * 2) + ", " + (d * 4) + ", " + (d * 2) + ")";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class = "col-md-6" id = "chart1" >
I have been trying to merge a set of paths in d3, So that one color blends into the other so it appears as though forms a gradient i tried to use to create a gradient but its always in a single direction which does not work out.
Fiddle here https://jsfiddle.net/roug3/jnpe5v3p/
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {x: 200 , y : 200 , angle : 30};
var etxD = {etxSN : "TX500"};
if(d3.select(".arc"+etxD.etxSN).node()){
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for(var i = 1;i<= arcLevel;i++){
dataset[i] = [i];
}
var color = ["#ee4035","#f37736","#fdf498","#7bc043","#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc"+etxD.etxSN , true).classed("arcSegment" , true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function (d) {
return pie(d);
})
.enter();
arcP.append("path").
attr("class" , function (d, i) {
return "arcID"+etxD.etxSN+i;
})
.attr("fill", function (d, i, j) {
// var cspan = Math.floor(Math.random() * arcLevel);
return color[ j ];
})
.attr("d", function (d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform" , "translate("+x+","+y+")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Any Suggestions
Thanks
This is as close as I could get it : https://jsfiddle.net/thatoneguy/jnpe5v3p/2/
With the help of these :
http://jsfiddle.net/Qh9X5/1110/
http://www.w3schools.com/svg/svg_grad_radial.asp
Basically you have to create a radial blur using the dataset :
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
Then select this to fill your paths :
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {
x: 200,
y: 200,
angle: 30
};
var etxD = {
etxSN: "TX500"
};
if (d3.select(".arc" + etxD.etxSN).node()) {
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for (var i = 1; i <= arcLevel; i++) {
dataset[i] = [i];
}
var color = ["#ee4035", "#f37736", "#fdf498", "#7bc043", "#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc" + etxD.etxSN, true).classed("arcSegment", true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function(d) {
return pie(d);
})
.enter();
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
var count = -1;
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
It isn't perfect but will put you on the right track :) Hope this helps
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;
}
});
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");
}