I would like to place images between the axis labels and the axis line or the bars in my case. Now it's a bit tricky because I don't have much space. I am restricted by the graph size and I have to work with the current dimensions. I tried the option of adding tickPadding() to the y-axis but that meant I went over the graph size and the labels were cut-off. is there a way I could move the bars to the right? or make the width a bit smaller?
here is my code for the y-axis and the bars:
let yScale_h = d3.scaleBand()
.range([0, height])
.padding(0.2);
let xScale_h = d3.scaleLinear()
.range([0, width]);
let yAxis = d3.axisLeft()
.scale(yScale_h)
.tickSize(0);
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
One way to manually offset the bars to the right is to reduce the scale range, and add the padding to the 'x' property of the bars.
This example adds a padding of 20px:
let xScale_h = d3.scaleLinear()
.range([0, width - 20]); // Reduce the range by 20px
...
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('x', 20) // Move bars to the right by 20px
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
Related
I have been able to make a scatter plot with zoom and pan functionality where the axes scale properly and everything works well. Now I am trying to figure out how to add gridlines, but running into some issues. I have started with only adding x-axis gridlines to figure things out. I have attached a fiddle with a working example to build from.
I commented out the initial gridlines when the graph is generated, because they would remain after zooming causing clutter, and I will add them back later when I get things working. When zooming the gridlines appear to be drawn correctly, but they do not match up with the x-axis labels, and the x-axis labels disappear after zooming or panning.
If you comment out line 163 and uncomment line 164 you can see the basic graph without any gridlines. Clicking the plot button will always generate a new graph. I have left behind some commented out code of different things that I have tried from searching through stackoverflow.
Example is using d3.js - 5.9.2
JSFiddle: https://jsfiddle.net/eysLvqkh/11/
HTML:
<div id="reg_plot"></div>
<button id="b" class="myButton">plot</button>
Javascript:
var theButton = document.getElementById("b");
theButton.onclick = createSvg;
function createSvg() {
// clear old chart when 'plot' is clicked
document.getElementById('reg_plot').innerHTML = ""
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 55},
svg_dx = 1200,
svg_dy =600,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// append svg to div element 'reg_plot' and set zoom to our function named 'zoom'
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom));
// clip path - sets boundaries so points will not show outside of the axes when zooming/panning
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "10px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom - margin.top})`)
.call(xAxis).style("font-size", "10px")
// add x and y grid lines
x_axis.call(xAxis.scale(xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(yScale).ticks(20).tickSize(-chart_dx));
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
// re-draw circles using new scales
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// re-scale axes and gridlines
x_axis.call(xAxis.scale(new_xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(new_yScale).ticks(20).tickSize(-chart_dx));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
For anyone looking, I have solved this problem. I have updated the javascript in the original post, and updated the jsfiddle. If you are copying this code to your local machine where you are using d3.js 7.4.4 or higher then you need to change the lines that say d3.event.transform.... to just e.transform.
Looking to stylize a bar chart using a D3 SVG. Right now it contains a number on the y axis and, on the x axis, day by day labels. There is a tick mark for each day, where I want only the first date of the week displayed. How can I show just one a week? Code below:
d3.csv("us-counties-cases.csv", function(data) {
filteredData = data.filter(function(row) {
return row['county'] == 'New York City';
});
var x = d3.scaleBand()
.range([ 0, width])
.domain(filteredData.map(function(d) {
return d.date;
}))
.padding(0.2);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Add Y axis
var max = d3.max(filteredData, function(d) { return d.cases; });
var y = d3.scaleLinear()
.domain([0, max * 1.2])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(filteredData)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.date);
})
.attr("width", x.bandwidth())
.attr("fill", "#b3b3b3")
.attr("height", function(d) {
return height - y(0);
})
.attr("y", function(d) {
return y(0);
})
// Animation
svg.selectAll("rect")
.transition()
.duration(200)
.attr("y", function(d) {
return y(d.cases);
})
.attr("height", function(d) {
return height - y(d.cases);
})
.delay(function(d,i){
return(i*50)
})
})
Usually you can use axis.tick() to set the arguments that controls how ticks are displayed. Among other things, you can set how many ticks to visualize.
However, as you can read in the documentation, the effect of this method and its arguments depends on the axis’ scale type. If you are using scaleBand, axis.tick() has no effect and you should use axis.tickValues to set the tick values explicitly. Similarly, if you want to change the tick format, it's necessary to use axis.tickFormat.
For your specific case, the following code modifies the x axis to show only one tick every 7 days:
d3.axisBottom(x).tickValues(x.domain().filter( (d,i) => !(i % 7) ))
I ran into a common problem while making a bar graph in d3: my y-axis was upside down. After some googling, I found out that the best way to fix this is by reversing the y-domain. The only problem is, when I did that, the bars on my graph switched positions, so the largest was at the beginning instead of the end. I need the y-axis to be correct, without changing my bars.
Bars in correct positions, but the y-axis is upside-down
Bars are incorrect, but the y-axis is right-side-up
Here is the code: https://codepen.io/lucassorenson/pen/rPRadR?editors=0010
const yScale = d3.scaleLinear()
.domain([0, d3.max(json, (d) => d[1])])
.range([h - padding, padding]);
const yAxis = d3.axisLeft(yScale);
This is the code that I changed. If you reverse the range ([padding, h - padding]), the bars are correct but the axis is not.
The fix is simply to exchange the callback functions of the attributes "height" and "y".
Your code was:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('height', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('y', function(d){
return h - yScale(d[1]) - padding
})
change it to:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('y', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('height', function(d){
return h - yScale(d[1]) - padding
})
With the attribute "y" you typically set the upper edge of the rectangle and with "height" you denote how far it goes down.
I am using D3 version 4 to parse through data and graph by date.
I have scaled everything to work nicely with zoom, however I cannot seem to keep the line from overflowing outside of the axes. I would like it to stay inside the chart and just cut off parts when the user zooms in.
Using a clip path, the lines/dots are cut off at the axes boundary, but when the user zooms in they overflow past but are still missing the original piece that was cut off (i.e. only half of a dot, just bigger and overflowing).
The whole project is viewable here: https://codepen.io/lahesty/pen/NzMVjj
Here are some important/relevant pieces:
// scale, set ranges
var x = d3.scaleLinear()
.range([0, width-100])
.domain(d3.extent(data, function(d) { return d.inspected_at; }));
var y = d3.scaleLinear()
.range( [height, 0])
.domain(d3.extent(data, function(d) { return d.temperature; }));
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);
//// clip path
defs = svg
.append('g')
.attr('width', 100)
.attr('height', 0)
.append('defs')
defs.append('clipPath')
.attr('id', 'clipper')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height)
//append line
svg.append('g')
.append("path")
.attr('clip-path', 'url(#clipper)')
.attr("class", "line")
.attr("d", line(data))
.attr("stroke", "blue")
function zoomed() {
svg.selectAll(".line")
.attr("transform", d3.event.transform);
svg.selectAll("circle")
.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)))
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)))}
I have updated your codepen here. I slightly changed how lines and circles are appended, and bound the clip path to a g group which houses the line/circles.
svg.append('g')
.attr('clip-path', 'url(#clipper)') .selectAll('path.line').data([data])
.enter().append("path")
.attr("class", "line")
.attr("d", line)
.attr("stroke", "blue");
// draw the plot data
svg.append('g').attr('clip-path', 'url(#clipper)') .selectAll("circle.dot")
.data(data)
.enter()
This way it clips the entire grouping.
I'm writing a simple bar chart that draws a rect element for each piece of data. This is a part of my code without scales, which works fine:
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => (i*4))
.attr("y", (d) => h - d[1]/50)
However, if I add y-scale, my bars flip over, and if I add x-scale, I can only see one bar. Here's my code with scales:
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
//some other code
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i*4))
.attr("y", (d) => yScale(d[1]/50))
Here's my CodePen with scales, that's what it looks like: http://codepen.io/enk/pen/vgbvWq?editors=1111
I'd be really greatful if somebody told me what am I doing wrong.
D3 scales are not messing with your chart. The problem here is simple.
Right now, this is your dataset:
[["1947-01-01", 243.1], ...]
As you can see, d[0] is a string (representing a date), not a number. And, in d3.scaleLinear, the domain is an array with two values only.
The solution here is using a scaleBand instead:
const xScale = d3.scaleBand()
.domain(dataset.map(d => d[0]))
.range([padding, w - padding]);
And changing the code of the rectangles to actually use the scales (I made several changes, check them):
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("x", (d => xScale(d[0]))) //xScale
.attr("height", (d => h - yScale(d[1]))) //yScale
.style("y", (d => yScale(d[1])))
.attr("data-date", (d) => d[0])
.attr("data-gdp", (d) => d[1])
.attr("class", 'bar')
Here is your updated CodePen: http://codepen.io/anon/pen/NdJGdo?editors=0010