Related
I'm learning to use D3.js, and although the height of my rects on the y axis coincides with the value of the axis, they go to the bottom of my SVG, instead of positioning themselves on the x axis:
enter image description here
I share the snippet of my code:
let data = {
"month": "Enero",
"expenses": [{
"id": 1,
"category": "Comida",
"total": 1500
},
{
"id": 2,
"category": "Vicio",
"total": 2000
},
{
"id": 4,
"category": "Ropa",
"total": 1000
},
{
"id": 3,
"category": "Alquiler",
"total": 2100
}]
}
let width = 800;
let height = 1000;
let padding = 50;
let xScale;
let yScale;
let gastos = data.expenses;
console.log(gastos)
let generateScale = () => {
yScale = d3.scaleLinear().domain([d3.max(gastos, (item) => { return item["total"] + 500 }), d3.min(gastos, (item) => { return item["total"] - 500 })]).range([padding, height - padding]);
xScale = d3.scaleLinear().domain([d3.min(gastos, (item) => { return item["id"] - 1 }), d3.max(gastos, (item) => { return item["id"] + 1 })]).range([padding, width - padding]);
}
let svg = d3.select('#canvas')
.attr('width', 800)
.attr('height', 1000)
let drawRect = () => {
svg.selectAll('rect')
.data(gastos)
.enter()
.append('rect')
.attr('y', function (d) {
return yScale(d['total']);
})
.attr('height', function (d) {
return d['total'];
})
.attr('x', (item) => {
return xScale(item['id']);
})
.attr('width', 40)
.attr('fill', 'black')
.attr('transform', 'translate(0, 0)')
}
let generateAxes = () => {
let xAxis = d3.axisBottom(xScale)
let yAxis = d3.axisLeft(yScale)
svg.append('g')
.call(xAxis)
.attr('id', 'x-axis')
.attr('transform', 'translate(0, ' + (height - padding) + ')')
svg.append('g')
.call(yAxis)
.attr('id', 'y-axis')
.attr('transform', 'translate(' + padding + ', 0)')
}
generateScale()
generateAxes()
drawRect()
I tried modifying the 'height' and 'y' attribute but did not get the expected results.
.attr('y', function (d) {
return yScale(d['total']);
})
.attr('height', function (d) {
return d['total'];
})
Removing the scale, subtracting the height, etc.
I want to apply a scale to circles generated with d3 force from an array.
This code produces the right x axis, but there are loads of circles produced with the class 'goalamount' and they are all offscreen by tens of thousands of pixels. There should only be six circles in the goalamount class and they should all scale to the xaxis - what am I doing wrong?
const data = [{
x: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x === data[dotindex].x) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
var u = d3.select('svg')
.append("g")
.attr("class", "goalAmounts")
.selectAll('goalAmounts')
.data(data)
u.enter()
.append("circle")
// .attr( "transform", "translate(" + 2000 + "," + 50 + ")")
.attr("r", d => amt(d.amt))
.merge(u)
.attr("fill", d => d.colour)
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
u.exit().remove()
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
svg.selectAll("circle")
.data(data)
.enter()
.append("g")
.attr("class", "circles")
.append("circle")
.attr("transform", "translate(" + 100 + "," + 650 + ")")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(pxX / 2, pxY / 2))
.force('collision', d3.forceCollide().radius(function(d) {
return d.amt
}))
.on('tick', ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>
Use d3.forceX and d3.forceY instead, so you draw the nodes towards their intended position. Also, d3-force populates x and y properties of the nodes, so you need to use d.x1 or something instead. scX(d.x) caused the huge values of the nodes.
const data = [{
x1: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x1: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x1;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x1, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x1, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x1 === data[dotindex].x1) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
const circles = svg.append("g")
.attr("class", "circles")
.attr( "transform", "translate(" + 100 + "," + 100 + ")")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x1))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force("x", d3.forceX(d => scX(d.x1)))
.force("y", d3.forceY(d => scY(d.y2)))
.force('collision', d3.forceCollide().radius(d => rad(d.rad)))
.on("tick", ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>
I am working on a d3js horizontal chart - the designers are specific in having the labels this way.
I've built the following - but would like to model it more on older code that had animation properties.
//current chart
https://codepen.io/anon/pen/ZmJzXZ
//static vertical chart http://jsfiddle.net/pg886/201/
//animated vertical chart http://jsfiddle.net/Qh9X5/12073/
-- d3js code
var data = [{
"name": "Apples",
"value": 20,
},
{
"name": "Bananas",
"value": 12,
},
{
"name": "Grapes",
"value": 19,
},
{
"name": "Lemons",
"value": 5,
},
{
"name": "Limes",
"value": 16,
},
{
"name": "Oranges",
"value": 26,
},
{
"name": "Pears",
"value": 30,
}];
//sort bars based on value
data = data.sort(function (a, b) {
return d3.ascending(a.value, b.value);
})
//set up svg using margin conventions - we'll need plenty of room on the left for labels
var margin = {
top: 15,
right: 25,
bottom: 15,
left: 60
};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graphic").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data, function (d) {
return d.value;
})]);
var y = d3.scale.ordinal()
.rangeRoundBands([height, 0], .3)
.domain(data.map(function (d) {
return d.name;
}));
//make y axis to show bar names
var yAxis = d3.svg.axis()
.scale(y)
//no tick marks
.tickSize(0)
.orient("right");
var gy = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bars")
//append rects
bars.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return y(d.name);
})
.attr("height", y.rangeBand())
.attr("x", 0)
.attr("width", function (d) {
return x(d.value);
});
//add a value label to the right of each bar
bars.append("text")
.attr("class", "label")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 + 4;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(d.value) + 3;
})
.text(function (d) {
return d.value;
});
var labels =
bars.append("text")
.attr("class", "labels")
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 - 30;
})
.attr("x", 0)
.text(function (d) {
return d.name;
});
I had a similar chart that I've made a few modifications to that might fill your requierments, so I'll be basing my answer of my own code.
I'll just go through the most relevant part of the question and you can just have a look at the code and hopefully figure out how it works yourself.
The inital animation works exactly the same way as in the third link you posted:
.transition().duration(speed)
.delay((_, i) => delay * i)
we set a delay so that each bar appear one at a time.
I've also set it up so that you can change the data using the d3js update pattern.
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
Since you didn't specify how you want to update the new data it's just a year filter for now.
Here's all the code:
var init = [
{"year": "2017", "name": "Apples", "value": 20},
{"year": "2017", "name": "Bananas","value": 12},
{"year": "2017", "name": "Grapes", "value": 19},
{"year": "2017", "name": "Lemons", "value": 5},
{"year": "2017", "name": "Limes", "value": 16},
{"year": "2017", "name": "Oranges", "value": 26},
{"year": "2017", "name": "Pears","value": 30},
{"year": "2018", "name": "Apples", "value": 10},
{"year": "2018", "name": "Bananas","value": 42},
{"year": "2018", "name": "Grapes", "value": 69},
{"year": "2018", "name": "Lemons", "value": 15},
{"year": "2018", "name": "Limes", "value": 26},
{"year": "2018", "name": "Oranges", "value": 36},
{"year": "2018", "name": "Pears","value": 20}
];
chart(init)
function chart(result) {
var format = d3.format(",.0f")
var years = [...new Set(result.map(d => d.year))]
var fruit = [...new Set(result.map(d => d.name))]
var options = d3.select("#year").selectAll("option")
.data(years)
.enter().append("option")
.text(d => d)
var svg = d3.select("#graphic"),
margin = {top: 25, bottom: 10, left: 50, right: 45},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([margin.left, width - margin.right])
var y = d3.scaleBand()
.range([margin.top, height - margin.bottom])
.padding(0.1)
.paddingOuter(0.5)
.paddingInner(0.5)
var xAxis = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
var yAxis = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left},0)`)
update(d3.select("#year").property("value"), 750, 250)
function update(input, speed, delay) {
var data = result.filter(f => f.year == input)
var sum = d3.sum(data, d => d.value)
x.domain([0, d3.max(data, d => d.value)]).nice()
svg.selectAll(".x-axis").transition().duration(speed)
.call(d3.axisTop(x).tickSizeOuter(0));
data.sort((a, b) => b.value - a.value)
y.domain(data.map(d => d.name))
svg.selectAll(".y-axis").transition().duration(speed)
.call(d3.axisLeft(y));
yAxis.selectAll("text").remove()
yAxis.selectAll("line").remove()
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
var value = svg.selectAll(".value")
.data(data, d => d.name)
value.exit().remove();
value.enter().append("text")
.attr("class", "value")
.attr("opacity", 0)
.attr("dy", 4)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.merge(value)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.attr("x", d => x(d.value) + 5)
.text(d => format((d.value / sum) * 100) + " %")
var name = svg.selectAll(".name")
.data(data, d => d.name)
name.exit().remove();
name.enter().append("text")
.attr("class", "name")
.attr("opacity", 0)
.attr("dy", -5)
.attr("y", d => y(d.name))
.merge(name)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name))
.attr("x", d => x(0) + 5)
.text(d => d.name)
}
var select = d3.select("#year")
.style("border-radius", "5px")
.on("change", function() {
update(this.value, 750, 0)
})
}
body {
margin: auto;
width: 650px;
font: 12px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="graphic" width="600" height="380"></svg><br>
Choose year:
<select id="year"></select>
i am having some trouble with a d3.js v4 stacked bar chart trying to create a hover effect
the goal is to have a the entire stack highlight and a tooltip appear on hover. i think i am looking for away to get the location coordinates and size for the rect to draw over the entirety of the stack
i'm using this modified d3-tip library for v4 for tooltips
https://github.com/VACLab/d3-tip
A picture of the issue speaks a thousand words:
http://nicholasmahoney.com/gj/ex.jpg
--here, i can get pretty close to the x position and i can get the width by using x(d.fellow) and x.bandwith but can't figure out the y component. i'd like the black bars in this example to be the same place and size as the stacked bars
EDIt: adding jsfiddle with code and data,
here is the exact code in action:
http://jsfiddle.net/nickmahoney/ddjbumrx/6/
<svg width="500" height="500">
</svg>
<script>
var svg = d3.select("svg"),
margin = {
top: 40,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.05)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var data = [ { "fellow": "demo", "primary": 1, "assistant": 1, "observer": 0, "instructor": 0 }, { "fellow": "alpha", "primary": 22, "assistant": 8, "observer": 0, "instructor": 0 }, { "fellow": "betta", "primary": 0, "assistant": 4, "observer": 0, "instructor": 0 }, { "fellow": "gamma", "primary": 4, "assistant": 2, "observer": 0, "instructor": 0 }, { "fellow": "donkey", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 },{ "fellow": "donkey", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 } , { "fellow": "eeee", "primary": 22, "assistant": 8, "observer": 0, "instructor": 0 }, { "fellow": "ffff", "primary": 0, "assistant": 4, "observer": 0, "instructor": 0 }, { "fellow": "gaggggmma", "primary": 4, "assistant": 2, "observer": 0, "instructor": 0 }, { "fellow": "aaaa", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 },{ "fellow": "ddddddefef", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 }];
// fix pre-processing
var keys = [];
for (key in data[0]){
if (key != "fellow")
keys.push(key);}
data.forEach(function(d){
d.total = 0;
keys.forEach(function(k){
d.total += d[k];
})
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Name:</strong> <span style='color:red'>" + d.fellow + "<br><strong>Primary:</strong>" + d.primary + "</span>";
})
svg.call(tip);
//data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.fellow; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.fellow); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
;
//tooltip bars
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return 40+ x(d.fellow) ; })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.primary) ; })
.attr("height", function(d) { return y(d.primary) ; })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
;
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
;
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) {return d;});
</script>
Here's a fork of your fiddle: http://jsfiddle.net/s54tbyxb/
You're already computing the total value based on the desired keys. I'm just using that value to determine the y component and the height.
Relevant changes in the code:
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("x", function(d) { return x(d.fellow) ; })
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.total) ;
})
.attr("height", function(d) { return height-y(d.total) ; })
Hope this helps.
I would like to show labels on the circles of the scatter plot when they are not colliding/overlapping. A simple example of scatter plot with labels is as follows:
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
The example is available on fiddle:
https://jsfiddle.net/8e7qmzw8/1/
How can I apply collision detection in the given example to show the labels for non-collided circles?
Here's a brute force search approach:
gdots
// filter out those in a colliding state
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
// if it has a collision with another, stop looking
if (!isCollide){
// if it's not itself
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
// if they overlap
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
});
Running code:
var data = [
{"x": -123, "y": 63, "r": 37, "c": "#50C2E3", "name": "A"},
{"x": 71, "y": 0, "r": 15, "c": "#50C2E3", "name": "B"},
{"x": 3845, "y": 77, "r": 15, "c": "#50C2E3", "name": "C"},
{"x": 3176, "y": 90, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": -17, "y": 56, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": 1357, "y": 58, "r": 15, "c": "#50C2E3", "name": "E"},
{"x": 7684, "y": 75, "r": 15, "c": "#50C2E3", "name": "F"}
];
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
if (!isCollide){
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
.node {
cursor: pointer;
}
.dot {
opacity: .7;
cursor: pointer;
}
.axis path,
.axis line {
fill: none;
stroke: rgb(31, 119, 180);
shape-rendering: crispEdges;
}
text {
stroke: none;
fill: #666666;
font-size: .6em;
font-family: "Helvetica Neue"
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"></script>
<div id="d3"></div>