I am working on an application that uses foursquare data.
//this is the series chart that has had some delving into - but there are some bugs still running around here.
So we have a batch of data - Health & Beauty, Restaurants, Cafe, Public Houses. -- there would be a COUNT of them -- and a SUMMATION of checkout information. So I want this chart to be able to show the NUMBER of venues, but also indicate how POPULAR they are.. so for example the number of pubs may be smaller, but the number of checkins higher as they are more popular. So in that instance want to reverse the colors of the circles.
There are some bugs with the current code attempts.
_latest jsfiddle
phase1
using "V" instead of "L" but couldn't make it work properly for the time being.
phase 2 I think it works more consistently but there are some issues. Also, I am not sure about the data and the scaling of the circles. (I've added extra labels so that it is visible what the value of the circles are)
phase 3
changed the getCircleSize a bit even though I believe a more consistent thing to do would be something like this layerSet.push(parseInt(getPercentage(layerArray[i], meansPerGroup[0])*60, 10));
so here the first step draws the circles by size order first... so in this case by count.. but maybe there is a bug here reversing the color to indicate the checkin count instead - so maybe we need to sort by count,checkin order - that way the first circle to get painted follows correctly.
// Create Circles
function setCircles(items) {
// sort elements in order to draw them by size
items.sort(function(a, b) {
return parseFloat(b.value) - parseFloat(a.value);
});
var circlelayer = svg.append("g")
.attr("class", "circlelayer");
var circle = circlelayer.selectAll("circle")
.data(items);
circle.enter().append("circle")
.attr("class", function(d, i) {
if (d.l == 0) {
return "blue";
}
return "gold";
})
.attr("cy", 60)
.attr("cx", function(d, i) {
var distance = calculateDistance(d, items);
if (d.l == 1) {
distancesL1.push(distance);
} else {
distancesL0.push(distance);
}
return distance;
})
.attr("r", function(d, i) {
return Math.sqrt(d.value);
})
.attr("filter", function(d) {
return "url(#drop-shadow)";
});
circle.exit().remove();
}
json structure to look something like this
[{
"label": "Health and Beauty",
"count": 30,
"checkin": 100
}, {
"label": "Restaurants",
"count": 23,
"checkin": 200
}, {
"label": "Cafes",
"count": 11,
"checkin": 900
}, {
"label": "Public Houses",
"count": 5,
"checkin": 1000
}]
I'm not sure I understand what is your problem but I've decided to try to create that chart from you screenshot with sample data from your plunker. Here is my result:
My script is making sure that smaller circle is always on top of the rage one so both circles are always visible. Here you can find my code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
html {
font-family: sans-serif;
}
</style>
</head>
<body>
<div id="container">
</div>
<script>
const data = [{
"name": "Twitter",
"vists": "15 billion",
"unit": "per day",
"layer1value": 15000000,
"layer2value": 450
}, {
"name": "Facebook",
"vists": "5 billion",
"unit": "per day",
"layer1value": 4000000,
"layer2value": 5000000
}, {
"name": "Google",
"vists": "5 billion",
"unit": "per day",
"layer1value": 5000000,
"layer2value": 25000
}, {
"name": "Netflix",
"vists": "10 billion",
"unit": "per day",
"layer1value": 3000000,
"layer2value": 2200
}, {
"name": "Ebay",
"vists": "8 billion",
"unit": "per day",
"layer1value": 2500000,
"layer2value": 4900000
}, {
"name": "Klout",
"vists": "2 billion",
"unit": "per day",
"layer1value": 1000000,
"layer2value": 45
}];
/*
* Finding max and min layer size
*/
const values = data.reduce((acumulator, datum) => [...acumulator, datum.layer1value, datum.layer2value], []);
const maxValue = Math.max(...values);
const minValue = Math.min(...values);
/*
* Creating scale based on the smallest and largest layer1value or layer2value
*/
const radiusScale = d3.scaleLinear()
.domain([minValue, maxValue])
.range([10, 150]); // min and max value of the circle
const width = 900;
const height = 500;
const orangeColour = '#ffb000';
const blueColour = '#00a1ff';
// Creating svg element
const svg = d3.select('#container').append('svg').attr('width', width).attr('height', height);
let xPos = 0; // position of circle
/*
* iterate over each datum and render all associated elements: two circles, and two labels with pointer lines
*/
for (let i = 0; i < data.length; i++) {
const d = data[i]; // current data point
const currMaxRadius = radiusScale(Math.max(d.layer1value, d.layer2value)); // get largest radius within current group of two layers
xPos += currMaxRadius; // add that radius to xPos
// create group element containing all view elements for current datum
const group = svg.append('g')
.attr('transform', `translate(${xPos}, ${height / 2})`);
group.append('circle')
.attr('r', radiusScale(d.layer1value))
.style('fill', blueColour);
group.insert('circle', d.layer2value > d.layer1value ? ':first-child' : null) // if layer2value is larger than layer1value then insert this circle before the previous one
.attr('r', radiusScale(d.layer2value))
.style('fill', orangeColour);
xPos += currMaxRadius * 0.9;
/*
* ADDING LABEL UNDERNEATH THE CIRCLES
*/
group.append('text')
.text(d.name)
.attr('dy', radiusScale(maxValue) + 40) // add 40px of padding so label is not just bellow the circle
.style('text-anchor', 'middle');
group.append('line')
.attr('y1', radiusScale(d.layer2value))
.attr('y2', radiusScale(maxValue) + 20) // add 20px of padding so the pointer line is not overlapping with label
.style('stroke', orangeColour);
/*
* ADDING LABEL AT THE ANGLE OF 45deg RELATIVE TO THE CIRCLES
*/
// we are creating new group element so we can easily rotate both line and label by -45deg
const rotatedLabelGroup = group.append('g').style('transform', 'rotate(-45deg)');
rotatedLabelGroup.append('line')
.attr('x1', radiusScale(d.layer2value))
.attr('x2', radiusScale(maxValue) + 20)
.style('stroke', orangeColour);
rotatedLabelGroup.append('text')
.text(d.vists)
.attr('dx', radiusScale(maxValue))
.attr('dy', -5); // this way label is slightly above the line
}
</script>
</body>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With