I have lines and labels that are both being generated from an external data source. What I want is for each iteration of these to be put in a group, so the code looks something like this:
<g>
<text>Text</text>
<line></line>
</g>
<g>
<text>Text</text>
<line></line>
</g>
<g>
<text>Text</text>
<line></line>
</g>
...
...
...
This is my code now which groups all of the elements in one group element:
var group = svg.append("g");
var labels = group.selectAll('text')
.data(data)
.enter()
.append('text')
.attr("class", "text")
.attr('x',function (d) { return xScale(d['Untitled']) + 50})
.attr('y',function (d) { return yScale(d['Untitled2']) - 31.2})
.style("text-anchor", "start")
.style("text-decoration", "underline")
.style("cursor", "move")
.text(function(d) { return d.name });
var lines = group.selectAll('line')
.data(data)
.enter()
.append('line')
.attr('class', 'line')
.style("stroke", "#000")
.style("stroke-width", .7)
.attr('x1',function (d) { return xScale(d['Untitled'])})
.attr('y1',function (d) { return yScale(d['Untitled2'])})
.attr('x2',function (d) { return xScale(d['Untitled']) + 50})
.attr('y2',function (d) { return yScale(d['Untitled2']) - 30});
You could do something like this:
const data = [
{ id: 1, value: 10, label: 'aaa' },
{ id: 2, value: 20, label: 'bbb' },
{ id: 3, value: 30, label: 'ccc' }
];
const svg = d3.select('body').append('svg')
.attr('width', 500)
.attr('height', 500);
const g = svg.selectAll('g').data(data, (d) => {
return d.id;
});
const groupEnter = g.enter().append('g');
groupEnter
.append('text')
.text((d) => {
return d.label;
})
groupEnter
.append('line', (d) => {
return d.value;
})
Here is a working jsfiddle
Basically, the idea is to take the return selection of g.enter().append('g'); and call append two times, the first one to append the text and the second to append the line.
I hope it helps.
Related
I was using d3 version 4.5 earlier in my project for d3 pack circles. Now I have used latest version and got difference in pack layout symmetry. Before Image and After Image
Here is my code in both cases. Want to have same symmetry as it was in earlier version. Is there any new way to get this symmetry in latest version of d3.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var bubble = d3.pack(data)
.size([diameter, 185])
.padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes)
.descendants())
.enter()
.filter(function(d) {
return !d.children;
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle")
.attr("r", function(d) {
return d.r;
//return d.data.Radius;
})
.style("fill", function(d, i) {
return d.data.fillColor;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
node.append("text")
.each(function(d) {
var arr = d.data.Name.split(" ");
for(i = 0; i < arr.length; i++) {
if(arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this)
.append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i)
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
}
d3.select(this)
.append("title")
.text(d.data.Name);
});
d3.select(self.frameElement)
.style("height", 185 + "px");
d3.selectAll(".node")
.on("mouseover", function(d) {
var circle = d3.select(this)
.select("circle");
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r;
});
})
.on("mousemove", function(d) {
})
.on("mouseleave", function(d) {
var circle = d3.select(this)
.select("circle");
circle.transition()
.duration(500)
.attr("r", function(d) {
return d.r;
});
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r / 3;
});
});
You were right, it's this commit in d3-hierarchy between 1.1.1 and 1.1.2, which in turn was introduced between d3 4.5.0 and 4.5.1. It addresses this issue, about packing circles more condensely.
I recommend just accepting the changes, but if you really don't want to change the layout, import d3-hierarchy 1.1.1 *after* d3` to override the hierarchy module. This returns the same layout as the older version of d3:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
The downside is that by making the package versions out of sync, you might break something now or in the future, so it's not a long term sustainable approach.
To test use the following snippet and comment/uncomment the script imports.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var data = {
children: [{
Name: 'Economy',
fillColor: 'grey',
Count: 12
},
{
Name: 'Politics',
fillColor: 'grey',
Count: 10
},
{
Name: 'ESG',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Tech',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Leisure',
fillColor: 'pink',
Count: 4
},
{
Name: 'Coronavirus',
fillColor: 'pink',
Count: 4
},
{
Name: 'Blockchain',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Sports',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Coding',
fillColor: 'purple',
Count: 1,
},
{
Name: 'India',
fillColor: 'purple',
Count: 1,
}
],
};
var bubble = d3.pack(data).size([diameter, 185]).padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d) {
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle").attr("r", function(d) {
return d.r;
//return d.data.Radius;
}).style("fill", function(d, i) {
return d.data.fillColor;
});
node.append("text").each(function(d) {
var arr = d.data.Name.split(" ");
for (i = 0; i < arr.length; i++) {
if (arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i).attr("fill", "white").attr("font-size", function(d) {
return d.r / 3;
});
}
d3.select(this).append("title").text(d.data.Name);
});
d3.select(self.frameElement).style("height", 185 + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
<div id="trending-topic"></div>
I am thoroughly stumped and would love a helping hand from anyone who can kick me in the right direction. I'm trying to create groups g and a single rectangle inside them. The join works great for the g parent but it does not create the children. What am I missing? (I'm thinking in joins!)
I've tried replacing the join with an enter().append('g') to no avail either, so I'm missing something.
Here is a jsfiddle.
var svg = d3.select("div#canvas")
.append("svg")
.attr("width", '100%')
.attr("height", '100%');
let NodeData = [
{
'x': 20,
'y': 20,
'id': 'abc',
'fill': '#D4C2F1'
},
{
'x': 20,
'y': 80,
'id': 'def',
'fill': '#B3D2C5'
},
];
function updateNodes(data) {
var groups = svg.selectAll('g').data(data)
.join('g')
.attr('node-id', function (d) { return d.id; })
.attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });
var rects = groups.selectAll('rect')
.data(function (d) { return d; })
.join('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 80)
.attr('height', 20)
.attr('stroke', '#666666')
.attr('fill', function (d) { return d.fill; });
}
updateNodes(NodeData);
selection.data() requires an array (or function that returns an array). You are not passing an array to .data() when trying to create the child rects. You are passing an object - an individual item in the original data array, so no elements are entered.
To fix this you can simply use:
var rects = groups.selectAll('rect')
.data(function (d) { return [d]; })
Updated fiddle,
But, this is not the best approach if you are just passing the parent's datum as is to a single child. You don't need to use a nested enter/update/exit cycle, you can just append to the parents:
var groups = svg.selectAll('g').data(data)
.join('g')
.attr('node-id', function (d) { return d.id; })
.attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });
var rects = groups.append("rect")
.attr('x', 0)
.attr('y', 0)
.attr('width', 80)
.attr('height', 20)
.attr('stroke', '#666666')
.attr('fill', function (d) { return d.fill; });
Modified fiddle
The new child elements (rects) inherit the bound data of their parents, as you can see here:
var svg = d3.select("div#canvas")
.append("svg")
.attr("width", '100%')
.attr("height", '100%');
let NodeData = [
{
'x': 20,
'y': 20,
'id': 'abc',
'fill': '#D4C2F1'
},
{
'x': 20,
'y': 80,
'id': 'def',
'fill': '#B3D2C5'
},
];
function updateNodes(data) {
var groups = svg.selectAll('g').data(data)
.join('g')
.attr('node-id', function (d) { return d.id; })
.attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });
var rects = groups.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 80)
.attr('height', 20)
.attr('stroke', '#666666')
.attr('fill', function (d) { return d.fill; });
rects.each(function(d) {
console.log(d);
})
}
updateNodes(NodeData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.0/d3.min.js"></script>
<div id="canvas" style="border: 1px solid #D9D9D9; width: 100%; height: 600px; margin-top: 6px"></div>
I am new to d3.js and please forgive me if this sounds like a naive question. I have plotted a line (d3 v4) which can be draggable by its end points. The end points are rectangle.
The current output looks as below :
This is how it looks
The challenge that i am facing is - when i start dragging the point, the line seems to take its origin from the top left corner. When i drag the second point of the same line, the line drags / moves as expected.
The sample data looks as below :
The sample data
Requesting your suggestions / inputs on how to fix the above issue.
Below is the attached code that i am using :
var margin = { top: 0, right: 0, bottom: 0, left: 0 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
// Creating the colour Category
var color = d3.scaleOrdinal(d3.schemeCategory10);
var y = d3.scaleLinear().range([390, 0]);
// Scale the range of the data
y.domain([0, d3.max(data, function (d) { return Math.max(d.nonpromotedprice, d.promotedprice)*1.2; })]).nice();
// Line for the 1st Block
var lines = svg.selectAll("line")
.data(data)
.enter()
.append('line')// attach a line
.style("stroke", "#E6EAEE")
.style("stroke-width", 8) // colour the line
.attr("x1", function (d) { return d.x_value; }) // x position of the first end of the line
.attr("y1", function (d) { return y(d.nonpromotedprice); }) // y position of the first end of the line
.attr("x2", function (d) { return d.x_value; }) // x position of the second end of the line
.attr("y2", function (d) { return y(d.promotedprice); });
// Add the Y Axis
svg.append("g")
.attr("class", "grid")
.attr("fill", "lightgrey")
.attr("stroke-width", 0.7)
.attr("stroke-opacity", 0.2)
.call(d3.axisLeft(y)
.tickSize(-400)
.tickFormat(""));
var topEndPoints = data.map(function (line, i) {
return {
'x': line.x_value,
'y': line.nonpromotedprice,
'marker': 'marker-start',
'lineIndex': i
};
});
var bottomEndPoints = data.map(function (line, i) {
return {
'x': line.x_value,
'y': line.promotedprice,
'marker': 'marker-end',
'lineIndex': i
};
});
var MiddleEndPoints = data.map(function (line, i) {
return {
'x': line.x_value,
'y': line.avgprice,
'marker': 'marker-middle',
'lineIndex': i
};
});
var endPointsData = topEndPoints.concat(bottomEndPoints, MiddleEndPoints);
// Pointer to d3 rectangles
var endPoints = svg
.selectAll('rect')
.data(endPointsData)
.enter()
.append('rect')
.attr("width", 12)
.attr("height", 8)
.attr("x", function (d) { return d.x - 6; })
.attr("y", function (d) { return y(d.y); })
//.attr("cx", function (d) { return d.x; })
//.attr("cy", function (d) { return d.y; })
//.attr('r',7)
.attr("fill", function (d) { return color(d.x); })
.call(d3.drag()
//.origin(function(d) { return y(d.y); })
.subject(function() {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// draw the logo
svg.selectAll("image")
.data(data)
.enter()
.append("svg:image")
.attr("xlink:href", function (d) { return d.logo; })
//.append("rect")
.attr("x", function (d) { return d.x_value - 13; })
.attr("y", function (d) { return y(d.nonpromotedprice + 35); })
.attr("height", 25)
.attr("width", 25);
function dragstarted() {
d3.select(this).classed("active", true).attr('y', d.y = y(d3.event.y));
}
function dragged(d, i) {
var marker = d3.select(this);
// Update the marker properties
marker
//.attr('cx', d.x = d3.event.x)
.attr('y', d.y = d3.event.y);
// Update the line properties
lines
.filter(function (lineData, lineIndex) {
return lineIndex === d.lineIndex;
})
.attr('x1', function (lineData) {
return d.marker === 'marker-start' ? lineData.x1 = d.x : lineData.x1;
})
.attr('y1', function (lineData) {
return d.marker === 'marker-start' ? lineData.y1 = d.y : lineData.y1;
})
.attr('x2', function (lineData) {
return d.marker === 'marker-end' ? lineData.x2 = d.x : lineData.x2;
})
.attr('y2', function (lineData) {
return d.marker === 'marker-end' ? lineData.y2 = d.y : lineData.y2;
});
}
function dragended() {
d3.select(this).classed("active", false);
Shiny.setInputValue("pricechanged",
{price: (d3.max(data, function (d) { return Math.max(d.nonpromotedprice, d.promotedprice); }) -(d3.event.y / 390)* d3.max(data, function (d) { return Math.max(d.nonpromotedprice, d.promotedprice); }))*1.19},
{priority: "event"}
);
}
I simplified example as much as possible. I have data.csv file and want to create elements as below (Result). Is there some elegant way? Thank you.
Data (data.csv):
id, name, value
1, fruits, apple
2, fruits, pear
3, fruits, strawberry
4, vegetables, carrot
5, vegetables, celery
...
Result:
<g class="groups" id="fruits">
<circle class="some" id="apple"/>
<circle class="some" id="pear"/>
<circle class="some" id="strawberry"/>
...
</g>
<g class="groups" id="vegetables">
<circle class="some" id="carrot">
<circle class="some" id="celery">
...
</g>
I tried something like this:
d3.csv("data.csv", function(data) {
var svg = ...
var groups = svg.selectAll(".groups")
.data(data)
.enter().append("g")
.attr("class", "groups")
.attr("id", function(d) { return d.name; });
groups.selectAll(".some")
.data(data, function(d) { return d.id; })
.enter().append("circle")
.attr("class", "some")
.attr("id", function(d) { return d.value; });
});
But it selects all lines. I don't know how to select and enter only lines with the same name as parent g element.
You want to use the nest operator for this:
var byName = d3.nest().key(function(d) { return d.name; })
.entries(data);
var groups = svg.selectAll(".groups").data(byName)
.enter().append("g")
.attr("class", "groups")
.attr("id", function(d) { return d.key; });
var circles = groups.selectAll(".some")
.data(function(d) { return d.values; })
.enter().append("circle")
.attr("class", "some")
.attr("id", function(d) { return d.value; });
Using d3.js, how would I modify the following code to add a nested, yellow-filled circle of radius "inner_radius" to each of the existing generated circles:
var circleData = [
{ "cx": 300, "cy": 100, "radius": 80, "inner_radius": 40},
{ "cx": 75, "cy": 85, "radius": 50, "inner_radius": 20}];
var svgContainer = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500);
var circles = svgContainer.selectAll("circle")
.data(circleData)
.enter()
.append("circle");
var circleAttributes = circles
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.radius; })
.style("fill", function (d) { return "red"; });
As imrane said in his comment, you will want to group the circles together in a g svg element. You can see the updated code here with relevant changes below.
var circles = svgContainer.selectAll("g")
.data(circleData)
.enter()
.append("g");
// Add outer circle.
circles.append("circle")
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.radius; })
.style("fill", "red");
// Add inner circle.
circles.append("circle")
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.inner_radius; })
.style("fill", "yellow");