How to create a gradient that goes across different rectangles in D3JS? - d3.js

In D3JS V4: Suppose that you have six rectangles. How would you create a gradient that flows through from the first to the last?
I tried creating a group for the rectangles and then add the color-gradient-id to the group, but it still causes for the gradient to happen within each rectangle individually.

You have to set gradientUnits to userSpaceOnUse. According to the docs, userSpaceOnUse:
...represent values in the coordinate system that results from taking the current user coordinate system in place at the time when the gradient element is referenced
Here is a demo without userSpaceOnUse, which result is not what you want:
var svg = d3.select("svg");
var gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "50%")
.attr("x2", "100%")
.attr("y2", "50%");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "Black")
.attr("stop-opacity", 1);
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "white")
.attr("stop-opacity", 1);
var g = svg.append("g")
.style("fill", "url(#gradient)");
var rects = g.selectAll("foo")
.data(d3.range(7))
.enter()
.append("rect")
.attr("y", 20)
.attr("x", (d, i) => 20 + 50 * i)
.attr("width", 40)
.attr("height", 40);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400"></svg>
Now a demo with userSpaceOnUse:
var svg = d3.select("svg");
var gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "50%")
.attr("x2", "100%")
.attr("y2", "50%")
.attr("gradientUnits","userSpaceOnUse");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "Black")
.attr("stop-opacity", 1);
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "white")
.attr("stop-opacity", 1);
var g = svg.append("g")
.style("fill", "url(#gradient)");
var rects = g.selectAll("foo")
.data(d3.range(7))
.enter()
.append("rect")
.attr("y", 20)
.attr("x", (d, i) => 20 + 50 * i)
.attr("width", 40)
.attr("height", 40);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400"></svg>

Related

legend not visible in d3.js pie chart

I am trying to add legend to pie chart. I can see the legend element added in the inspect element but I am not able to see it. Could it be due to background color ? I looked into other questions but I am not able to solve the problem.
Can somebody explain what is wrong with my code ?
I also pasted the image below showing that the legend is attached to the svg.
Here is the code snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>barchart</title>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="piechart">
</div>
<script>
//chart dimensions
var width = 300;
var height = 300;
var radius = Math.min(width, height) / 2;
var labelHeight = 18;
var data = {
2011: 9,
2012: 12,
2013: 10,
2014: 8,
2015: 12,
2016: 20
}
//color scale
var color = d3.scaleOrdinal().range(["#126608", "#049a0d", "#587b08", "#048440", "#177c0a", "#4a8d36", "#3b712b", "#4a8b00", "#426b07", "#4b940b"]);
//arc
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) {
return d.value;
})
var svg = d3.select("#piechart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie_data = pie(d3.entries(data))
console.log(pie_data)
//svg.selectAll("path").remove()
var g = svg.selectAll("path")
.data(pie_data)
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.key)
});
g.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("fill", "white")
.text(function(d) {
return d.data.key
});
var legend = svg.append("g")
.attr("class", "legend")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "start")
.selectAll("g")
.data(pie_data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * 30 + ")";
});
legend.append("text")
.attr("x", radius + 30)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) {
return d.data.key;
});
legend.append("rect")
.attr("x", radius + 10)
.attr("width", 19)
.attr("height", 19)
.attr("fill", function(d) {
return color(d.data.key);
});
</script>
</body>
</html>
You simply missed to set a proper with for the content, but what you have to actually do is to implement margin convention for your chart:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>barchart</title>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="piechart">
</div>
<script>
//chart dimensions
var width = 500;
var height = 300;
var radius = Math.min(width, height) / 2;
var labelHeight = 18;
var data = {
2011: 9,
2012: 12,
2013: 10,
2014: 8,
2015: 12,
2016: 20
}
//color scale
var color = d3.scaleOrdinal().range(["#126608", "#049a0d", "#587b08", "#048440", "#177c0a", "#4a8d36", "#3b712b", "#4a8b00", "#426b07", "#4b940b"]);
//arc
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) {
return d.value;
})
var svg = d3.select("#piechart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie_data = pie(d3.entries(data))
//svg.selectAll("path").remove()
var g = svg.selectAll("path")
.data(pie_data)
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.key)
});
g.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("fill", "white")
.text(function(d) {
return d.data.key
});
var legend = svg.append("g")
.attr("class", "legend")
.attr("font-family", "sans-serif")
.attr("font-size", 20)
.attr("text-anchor", "start")
.selectAll("g")
.data(pie_data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * 30 + ")";
});
legend.append("text")
.attr("x", radius + 30)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) {
return d.data.key;
});
legend.append("rect")
.attr("x", radius + 10)
.attr("width", 19)
.attr("height", 19)
.attr("fill", function(d) {
return color(d.data.key);
});
</script>
</body>
</html>

draw two circles using D3 and by appending SVG

I am trying to draw two circles using D3 and by appending SVG.
The code provided below is not producing desired result.
It draws the first green circle but not the next one.
Please help me to understand what is wrong with the code provided below ...
<!DOCTYPE HTML>
<html>
<head>
<title>Selection Method
</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script>
d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500)
.style('background', '#dff0d8')
.append("g")
.attr("transform", "translate(0,0)")
.append("circle")
.attr("cx", 20)
.attr("cy", 20)
.attr("r", 10)
.attr("fill", "green")
.append("circle")
.attr("cx", 70)
.attr("cy", 70)
.attr("r", 10)
.attr("fill", "black")
</script>
</body>
Your code attempts to append a second circle to the first circle. Simply "outdenting" the code doesn't change the scope.
Here is a trivial change that draws what you likely expect:
d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500)
.style('background', '#dff0d8')
.append("g")
.attr("transform", "translate(0,0)")
.append("circle")
.attr("cx", 20)
.attr("cy", 20)
.attr("r", 10)
.attr("fill", "green")
d3.select('g') // <- Note the reselection of the existing 'g' element
.append("circle")
.attr("cx", 70)
.attr("cy", 70)
.attr("r", 10)
.attr("fill", "black")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Adding labels to map legend

I can't quite get this. Trying to modify:
https://bl.ocks.org/duspviz-mit/9b6dce37101c30ab80d0bf378fe5e583
to be able add low value to the left of the bar and high value to right. If anyone could point in the right direction, or show another example, would be much appreciated.
Just copy the ticks of the axis for the boundary of the domain.
Made the svg a bit bigger to fit the texts
<html>
<head>
<script src="https://d3js.org/d3.v4.js"></script>
<style type="text/css">
.axis text {
font: 10px sans-serif;
}
.axis line, .axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
svg{
padding-left: 50px;
}
</style>
</head>
<body>
<div id="legend1"></div>
<script type="text/javascript">
var w = 300, h = 50;
var key = d3.select("#legend1")
.append("svg")
.attr("width", w+100)
.attr("height", h+50);
var legend = key.append("defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "100%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
legend.append("stop")
.attr("offset", "0%")
.attr("stop-color", "#f7fcf0")
.attr("stop-opacity", 1);
legend.append("stop")
.attr("offset", "33%")
.attr("stop-color", "#bae4bc")
.attr("stop-opacity", 1);
legend.append("stop")
.attr("offset", "66%")
.attr("stop-color", "#7bccc4")
.attr("stop-opacity", 1);
legend.append("stop")
.attr("offset", "100%")
.attr("stop-color", "#084081")
.attr("stop-opacity", 1);
key.append("rect")
.attr("width", w)
.attr("height", h - 30)
.style("fill", "url(#gradient)")
.attr("transform", "translate(50,10)");
var y = d3.scaleLinear()
.range([300, 0])
.domain([68, 12]);
var yAxis = d3.axisBottom()
.scale(y)
.ticks(5);
key.append("g")
.attr("class", "y axis")
.attr("transform", "translate(50,30)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -30)
.attr("dy", "0")
.attr("dx", "0")
.attr("fill", "#000")
.style("text-anchor", "end")
.text("axis title");
var extras = key.select(".y.axis").selectAll(".dummy")
.data(y.domain())
.enter()
.append("g")
.attr("class", "tick")
.attr("transform", d => `translate(${y(d)},0)` );
extras.each( function (d) {
d3.select(this).append("line").attr("stroke", "#000").attr("y2", 6);
d3.select(this).append("text").attr("fill", "#000").attr("y", 9).attr("dy", "0.71em").text(d);
});
</script>
</body>
</html>

D3.js removing data on rect click

With a lot of help I've been able to click on rectangles to remove them.
But how can I also remove the data? Currently there is some strange behaviour, such as previously deleted rectangles reappearing and creation of a rectangle being in a different position to the click. I think all these are caused by the fact that the data is not being deleted.
I thought perhaps .exit() might work, but it doesn't.
Here's a working snippet.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.active {
stroke: #000;
stroke-width: 2px;
}
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var data = [{
x: 100,
y: 200
},
{
x: 200,
y: 300
},
{
x: 300,
y: 200
},
{
x: 400,
y: 300
}
];
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.x_pos
})]).range([0, width]);
svg.append("rect")
.attr("x", 100)
.attr("y", 250)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
svg.append("rect")
.attr("x", 102)
.attr("y", 252)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
y: d3.event.y
};
data.push(newData);
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'circle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
})
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("x", d.x = d3.event.x)
// .attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
d3.select(this)
.remove();
}
</script>
This question is a follow on from here.
First of all, your data array has 4 elements, but you are appending only 2 rectangles. That happens because you are selecting previously existent rectangles in that SVG. Therefore, instead of:
svg.selectAll("rect")
.data(data)
.enter().append("rect")
It should be:
svg.selectAll(null)
.data(data)
.enter().append("rect")
Back to your question:
D3 has methods to manipulate elements based on data, but not to manipulate data based on elements.
Thus, you have to change the data array yourself. For instance, inside removeElement:
function removeElement(d) {
data = data.filter(function(e){
return e != d;
});
};
Here is your code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.active {
stroke: #000;
stroke-width: 2px;
}
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var data = [{
x: 100,
y: 200
},
{
x: 200,
y: 300
},
{
x: 300,
y: 200
},
{
x: 400,
y: 300
}
];
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.x_pos
})]).range([0, width]);
svg.append("rect")
.attr("x", 100)
.attr("y", 250)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
svg.append("rect")
.attr("x", 102)
.attr("y", 252)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
svg.selectAll(null)
.data(data)
.enter().append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
y: d3.event.y
};
data.push(newData);
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'circle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
})
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("x", d.x = d3.event.x)
// .attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e){
return e != d;
});
d3.select(this)
.remove();
}
</script>

d3 version 4 issue with rectangle and text

This method works in v3 to append rectangles with text inside but fails in v4. I getting an error message "read property 'querySelectorAll'" but it may not be relevant to this piece of code. Any suggestions would be appreciated.
group = vis.selectAll(".rectangle")
.data(data);
gEnter = group.enter()
.append("g")
.attr("class", "rectangle")
.attr("fill", function (d) { return d.colour; });
gEnter.append("rect")
.attr("class", "rectband");
group.selectAll(".rectband")
.attr("width", 18)
.attr("height", 18)
.style("opacity", .5)
.style("stroke", "black")
.style("cursor", "move");
svgEnter = group.enter()
.append("svg")
.attr("height", 18)
.attr("class", "interval")
.attr("width", 10)
.attr("x", 20)
.attr("y", 20);
svgEnter.append("text")
.attr("class", "intervalLabel")
.attr("x", 6)
.attr("y", 14)
.style("pointer-events", "none")
.text(function (d) { return (d.name); });
Your code kinda works but your text elements are on top of each other and in an svg container that's not wide enough to show their content.
Anyway, if you make your svg wider, the text shows just fine:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var vis = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 400);
group = vis.selectAll(".rectangle")
.data([
{
colour: 'red',
name: 'one'
},
{
colour: 'green',
name: 'two'
}
]);
gEnter = group.enter()
.append("g")
.attr("class", "rectangle")
.attr("fill", function(d) {
return d.colour;
});
gEnter.append("rect")
.attr("class", "rectband")
.merge(gEnter)
.attr("width", 18)
.attr("height", 18)
.style("opacity", .5)
.style("stroke", "black")
.style("cursor", "move");
svgEnter = group.enter()
.append("svg")
.attr("height", 18)
.attr("class", "interval")
.attr("width", 30)
.attr("x", 20)
.attr("y", 20);
svgEnter.append("text")
.attr("class", "intervalLabel")
.attr("x", 6)
.attr("y", 14)
.style("pointer-events", "none")
.text(function(d) {
return (d.name);
});
</script>
</body>
</html>

Resources