D3 tick with background - d3.js

How can a D3 axis tick have a background color?
A brute way of doing so it to append a rect element inside each g.tick and have a fill on it, but it's quite difficult to achieve, since the rect has to be the same size as the text inside the tick..
Here's a basic ticks example by Mike Bostock (and another with graph)
I took a screenshot and marked (red border) where I want the ticks to have background color:
Does anyone know of any sane way of having background color on Ticks?
Thanks

Another option would be to make a filter like this:
var filter = svg.append("defs").append("filter")
.attr("x", "0")
.attr("y", "0")
.attr("width", "1")
.attr("height", "1")
.attr("id", "background")//id of the filter
filter.append("feFlood")
.attr("flood-color", "red");
filter.append("feComposite")
.attr("in", "SourceGraphic");
and to ticks add the filter like this:
g.selectAll(".tick text").attr("filter","url(#background)");
working code here

I wouldn't dismiss your rect idea so quickly. It's pretty simple to implement and allows you to adjust the size of the "background" however you want. Here's how it would look with your 3 extra pixel:
d3.selectAll(".tick").each(function(d,i){
var tick = d3.select(this),
text = tick.select('text'),
bBox = text.node().getBBox();
tick.insert('rect', ':first-child')
.attr('x', bBox.x - 3)
.attr('y', bBox.y - 3)
.attr('height', bBox.height + 6)
.attr('width', bBox.width + 6)
.style('fill', d3.schemeCategory20[i % 20]);
});
Full example:
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 0, bottom: 20, left: 0},
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.scalePoint()
.domain([0, 1, 2])
.range([0, width])
.padding(1);
var y = d3.scaleLinear()
.domain([-1e6, 2e6])
.range([height, 0]);
g.append("g")
.attr("transform", "translate(" + x(0) + ",0)")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(20, "s"));
g.append("g")
.attr("transform", "translate(" + x(1) + ",0)")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(20)
.tickFormat(d3.format(".0s")));
g.append("g")
.attr("transform", "translate(" + x(2) + ",0)")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(20)
.tickFormat(d3.formatPrefix(".1", 1e6)));
d3.selectAll(".tick").each(function(d,i){
var tick = d3.select(this),
text = tick.select('text'),
bBox = text.node().getBBox();
tick.insert('rect', ':first-child')
.attr('x', bBox.x - 3)
.attr('y', bBox.y - 3)
.attr('height', bBox.height + 6)
.attr('width', bBox.width + 6)
.style('fill', d3.schemeCategory20[i % 20]);
});
</script>

SVG text background color with padding
<svg width="200" height="200">
<defs>
<filter x="-0.5" y="-0.5" width="2" height="2" id="solid">
<feFlood flood-color="#BDBDBD"></feFlood>
<feComposite in="SourceGraphic"></feComposite></filter>
</defs>
<text x="50" y="50" font-size="13" fill="#fff" filter="url(#solid)">7%</text>
</svg>

Related

d3.js scatter plot point error: condition add

Add the condition issuse
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
const margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3.select("#my_dataviz")
.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})`);
//Read the data
d3.csv("https://gist.githubusercontent.com/FANJIYU0825/5626c7449001fc46895412fda1b5c139/raw/9e996d1edec867b2f590285c4e1e769dd482b91c/clean_data").then(function (data) {
// Add X axis
const x = d3.scaleLinear()
.domain([d3.min(data, (d) => d.Rating) - 0.1, d3.max(data, (d) => d.Rating),])
.range([0, width]);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
// Add Y axis
const y = d3.scaleLinear()
.domain([d3.min(data, (d) => d.Size) - 5, d3.max(data, (d) => d.Size) + 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.join("circle")
.attr("cx", (d, i) => {if (d.Category == "BUSINESS") return x(d.Rating);})
.attr("cy", (d, i) => {if (d.Category == "BUSINESS") return y(d.Size);})
.attr("r", 7)
.style("fill", "#69b3a2")
})
</script>
Not add condition look fine
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
const margin = { top: 10, right: 30, bottom: 30, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3.select("#my_dataviz")
.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})`);
//Read the data
d3.csv("https://gist.githubusercontent.com/FANJIYU0825/5626c7449001fc46895412fda1b5c139/raw/9e996d1edec867b2f590285c4e1e769dd482b91c/clean_data").then(function (data) {
// Add X axis
const x = d3.scaleLinear()
.domain([d3.min(data, (d) => d.Rating) - 0.1, d3.max(data, (d) => d.Rating),])
.range([0, width]);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
// Add Y axis
const y = d3.scaleLinear()
.domain([d3.min(data, (d) => d.Size) - 5, d3.max(data, (d) => d.Size) + 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.join("circle")
.attr("cx", (d, i) => { return x(d.Rating); })
.attr("cy", (d, i) => { return y(d.Size); })
.attr("r", 7)
.style("fill", "#69b3a2")
})
</script>
I get confused becasuse the last part of csv data get wrong position
I am not so sure why the data scale wrong other poitn position is fine but the last data erro no matter what the data is. The scale will show at the part of the top.
At the time that I add the condition.
Other is not different from the sample of
enter link description here
enter image description here

How to draw grid lines only inside area

I need to draw axis grid lines only inside areas in Area Chart, written in D3 (version 4) .
Have any solutionss of this issue?
Use negative width and height for the axis tick length
And then use a CSS style to stroke the grid.
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.call(d3.axisLeft(y));
g.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickSize(-height)
.tickFormat("")
);
g.append("g")
.attr("class", "grid")
.call(d3.axisLeft(y)
.tickSize(-width)
.tickFormat("")
);
Edit
If it is inside the Area define a clipping path that equals the area and group the grid lines to this clipping path.
Using the example from https://www.mattlayman.com/blog/2015/d3js-area-chart/
var data = [
{ x: 0, y: 10, },
{ x: 1, y: 15, },
{ x: 2, y: 35, },
{ x: 3, y: 20, },
];
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 575 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.x; })])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var area = d3.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
var svg = d3.select("svg#area")
.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("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("clipPath")
.attr("id", "area-clip")
.append("path")
.attr("d", area(data));
var grid = svg.append("g")
.attr("clip-path","url(#area-clip)");
grid.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(xAxis.tickSize(-height).tickFormat(""));
grid.append("g")
.attr("class", "grid")
.call(yAxis.tickSize(-width).tickFormat(""));
.area {fill:steelblue;}
.grid line {fill:none; stroke:red; stroke-width:1;}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="area"></svg>

how to show ordinal axis in d3

I am new to d3 and having issue with showing the label of ordinal axis:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta charset="utf-8">
<title>D3: Loading data from a CSV file</title>
</head>
<body>
<script type="text/javascript">
var margin = {top: 20, right: 20, bottom: 30, left: 40},
w = 600 - margin.left - margin.right,
h = 300 - margin.top - margin.bottom;
var padding = 40;
var data = [
{ "Food": "Apples", "Deliciousness": 9 },
{ "Food": "Green Beans", "Deliciousness": 5 },
{ "Food": "Egg Salad Sandwich", "Deliciousness": 4 },
{ "Food": "Cookies", "Deliciousness": 10 },
{ "Food": "Liver", "Deliciousness": 2 },
{ "Food": "Burrito", "Deliciousness": 7 },
];
data.forEach(function(d) {
d.Deliciousness = +d.Deliciousness;
});
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left+"," +
margin.top+")");
var xScale = d3.scaleBand()
.domain(d=>d.Food)
.range([0,w])
.paddingInner(0.2);
xScale.domain(data.map(function(d) { return d.Food; }));
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, d=>d.Deliciousness)])
.rangeRound([h,0]);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x',(d,i) => margin.left + i*w/data.length)
.attr('y',d=>yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height',d =>h-yScale(d.Deliciousness))
.attr('fill',function(d){
if (d===30) return "red";
return "rgb(0,0,"+d.Deliciousness*10+")" ;});
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(d=>d.Deliciousness)
.attr("x", (d,i)=>(padding + i*w/data.length))
.attr("y", d=>yScale(d.Deliciousness)+15)
.attr("fill","white");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h-margin.bottom) + ")")
.call(xAxis);
</script>
</body>
</html>
The x axis is somehow overlapping with the chart, how to properly use the margin?
And in terms of ordinal axis, other than list all the categories manually in .domain(), what are the other ways to special xScale in .domain().range() call? Thanks
It comes from the location definition of the x-axis.
You can change:
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h-margin.bottom) + ")")
.call(xAxis);
with:
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.left + "," + h + ")")
.call(xAxis);
Here is the demo:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta charset="utf-8">
<title>D3: Loading data from a CSV file</title>
</head>
<body>
<script type="text/javascript">
var margin = {top: 20, right: 20, bottom: 30, left: 40},
w = 600 - margin.left - margin.right,
h = 300 - margin.top - margin.bottom;
var padding = 40;
var data = [
{ "Food": "Apples", "Deliciousness": 9 },
{ "Food": "Green Beans", "Deliciousness": 5 },
{ "Food": "Egg Salad Sandwich", "Deliciousness": 4 },
{ "Food": "Cookies", "Deliciousness": 10 },
{ "Food": "Liver", "Deliciousness": 2 },
{ "Food": "Burrito", "Deliciousness": 7 },
];
data.forEach(function(d) {
d.Deliciousness = +d.Deliciousness;
});
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right + padding)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left+"," +
margin.top+")");
var xScale = d3.scaleBand()
.domain(d=>d.Food)
.range([0,w])
.paddingInner(0.2);
xScale.domain(data.map(function(d) { return d.Food; }));
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, d=>d.Deliciousness)])
.rangeRound([h,0]);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x',(d,i) => margin.left + i * ((w + 20 ) / data.length))
.attr('y',d=>yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height',d =>h-yScale(d.Deliciousness))
.attr('fill',function(d){
if (d===30) return "red";
return "rgb(0,0,"+d.Deliciousness*10+")" ;});
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(d=>d.Deliciousness)
.attr('x',(d,i) => margin.left + i * ((w + 20 ) / data.length))
.attr("y", d=>yScale(d.Deliciousness)+15)
.attr("fill","white");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.left + "," + h + ")")
.call(xAxis);
</script>
</body>
</html>
x-axis location compared to its container (here svg) is defined by the transform attribute. Which in this case is a translation. To define a translation, we give to the transform attribute this value: translate(dx,dy).
dx: As rects are given a x offset of margin.left (.attr('x',(d,i) => margin.left + i*w/data.length)) we also need to translate the x-axis horizontally by margin.left.
dy: And as rects have their base starting at h (.rangeRound([h, 0]);), we also need to translate the x-axis vertically by h.
I have also modified the x position of bars and labels using:
.attr('x',(d,i) => margin.left + i * ((w + margin.right ) / data.length))
instead of:
.attr('x',(d,i) => margin.left + i*w/data.length)
Finally, as the last bar is half outside the graph, you can increase the svg container's width, by replacing:
.attr("width", w + margin.left + margin.right)
with:
.attr("width", w + margin.left + margin.right + padding)
Concerning your final question, this https://github.com/d3/d3-axis might give you additional details on how to use d3 axes.

How can I add a background colour behind PNG, when image URL is pulled from my D3.js dataset?

I am making a scatterplot, and pulling in an image fill for each circle on the plot. The problem is that the images are PNG's with transparent backgrounds. This means my overlapping circles show through each other:
Seen here - http://i.stack.imgur.com/bphon.png
I have tried setting a background colour with the CSS, but it seems to be completely overwritten by the .style("fill") in the JS. And I am looking to pull in 30ish images, so I don't want to have to save them all to be able to load the images with my CSS.
So, my question is, is there a way to put a white background behind my PNGs, while pulling those PNGs from URL's contained in my dataset?
Thanks for the help
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis-text {
font-family: sans-serif;
font-size: 11px;
background-color: white;
font-weight: bold;
}
.teamcircle {
background-color: white;
}
</style>
</head>
<body>
<div>
<input type="button" id="playerbtn" value="See Player View">
<input type="button" id="teambtn" value="See Team View">
</div>
<div id="data">
<div id="player-circles">
</div>
</div>
<script type="text/javascript">
//Width and height
var margin = {top: 50, right: 20, bottom: 30, left: 40};
var w = 960 - margin.left - margin.right;
var h = 500 - margin.top - margin.bottom;
//Create scale functions
var xScale = d3.scale.linear()
.range([0, w]);
var yScale = d3.scale.linear()
.range([h, 0]);
// var color = d3.scale.color();
// Define the Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Load the TEAM data set
var teamdata = d3.tsv("team.tsv", function(error, teamdata) {
if (error) throw error;
teamdata.forEach(function(d) {
d.entriesper60 = +d.entriesper60;
d.carryinpercent = +d.carryinpercent;
});
xScale.domain(d3.extent(teamdata, function(d) { return d.carryinpercent; })).nice();
yScale.domain(d3.extent(teamdata, function(d) { return d.entriesper60; })).nice();
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("class", "axis-text")
.attr("x", w)
.attr("y", -6)
.style("text-anchor", "end")
.text("Carry-in %");
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "axis-text")
.attr("y", -20)
.attr("z", 0)
.style("text-anchor", "middle")
.text("Entries/60")
// DEFS & Pattern for images
svg.append("defs")
.selectAll("pattern")
.data(teamdata)
.enter()
.append("pattern")
.attr('id', function(d, i) {
return d.name;
})
// .attr('patternUnits', 'userSpaceOnUse')
.attr('width', 20)
.attr('height', 20)
.append("image")
.attr("xlink:href", function(d) {
return d.image + d.name;
})
.attr('width', 20)
.attr('height', 20)
.attr("transform", "translate(2, 2)");
// Create TEAM Circles
svg.selectAll("circle")
.data(teamdata)
.enter()
.append("circle")
.attr("class", "teamcircle")
.style("stroke", function(d) { return d.hex; })
.style("stroke-width", 2)
.style("stroke-opacity", .8)
.attr("r", 12)
.attr("cx", function(d) { return xScale(d.carryinpercent); })
.attr("cy", function(d) { return yScale(d.entriesper60); })
.attr("fill", function(d) { return "url(#" + d.name + ")";
});
});
</script>
</body>
</html>
The best way I can think of is to create a group for every circle, and create a circle with a white background first. Something like this:
var teamCircle = svg.selectAll("g.teamcircle")
.data(teamdata)
.enter()
.append("g")
.attr("class", "teamcircle")
.transform(function(d){return "translate(" + xScale(d.carryinpercent) + "," + yScale(d.entriesper60) + ")"});
teamCircle.append("circle")
.attr("fill", "white")
.attr("r", 12)
teamCircle.append("circle")
.style("stroke", function(d) { return d.hex; })
.style("stroke-width", 2)
.style("stroke-opacity", .8)
.attr("r", 12)
.attr("fill", function(d) { return "url(#" + d.name + ")";

D3.js figure staying out of twitter bootstrap's div section

The codes are the following: I am trying to create two "div" .Each div has a picture. However, the pictures are collapsing together after all the texts. They do not stay in their individual div.
<div class="container">
<div class ="row">
<h1> Title </h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" >
var margin = {top: 20, right: 30, bottom: 30, left: 100},
width = 800 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var formato = d3.format("0.0");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formato);
var svg = d3.select("body").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 dataFile_1 = "data.csv"
d3.csv(dataFile_1, function(error1, data1) {
data1.forEach(function(d) {
d.petitionRate = +d.petitionRate;
});
x.domain(data1.map(function(d) { return d.state; }));
y.domain([0, d3.max(data1, function(d) { return d.petitionRate; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Participation rate");
svg.selectAll(".bar")
.data(data1)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.state); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.petitionRate); })
.attr("height", function(d) { return height - y(d.petitionRate)});
</div>
<div class ="row">
If I put a picture here as the first one. Both of them do not stay in their div.
</div>
</div>
Please help me understand what the problem is here.
Thanks.
This line:
var svg = d3.select("body").append("svg")
appends d3s svg to the end of the html body. If you want the svg in the
<div class ="row">
directly preceding your JavaScript code do this:
<div class ="row" id="visRow">
and then append to that div:
var svg = d3.select("#visRow").append("svg")

Resources