How to get the path string for D3 Axis - d3.js

Is there an efficient way to get the path string of an axis generated in D3 from an axis generator function e.g. below as I need to get the intersection of a path added to the chart later on that bisects the X axis,
var xScale = d3.scaleLinear().domain([0,10]).range([0,width]);
var xAxis = g.append("g")
.attr("class","x-axis-right")
.attr("transform", `translate(0, ${height})`)
.style("fill","blue")
.style("stroke","none")
var xAxisCall = d3.axisBottom(xScale);
xAxis.transition(t).call(xAxisCall);

It makes little sense to me why would you want the d attribute, but anyway, this is how to do it:
In D3, getters are pretty much methods without the second argument. So, if this sets the d attribute...
selection.attr("d", foo);
... this gets it:
selection.attr("d");
Therefore, all you need is to select the desired path, which in your case would be:
xAxis.select("path").attr("d");
Here is the demo, look at the console:
var width = 500,
height = 100;
var svg = d3.select("svg");
var xScale = d3.scaleLinear().domain([0, 10]).range([10, width - 10]);
var xAxis = svg.append("g")
.attr("class", "x-axis-right")
.attr("transform", `translate(0, ${height - 30})`)
.style("fill", "blue")
.style("stroke", "none")
var xAxisCall = d3.axisBottom(xScale);
xAxis.call(xAxisCall);
var pathString = xAxis.select("path").attr("d");
console.log(pathString)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="100"></svg>

Related

How do I use event listeners to swtich my d3 bar chart where I only see one and not both?

I have 2 buttons that i want to use to control what data set I am using for my bar chart. Right now I can click on one and it shows my d3 graph without problems. But when I want to switch to the other graph, I click on the button and it shows me that graph on top of my previous graph. How do I make it so that when I switch between graphs, it only shows me one graph.
var djockey = 'top5jockey.csv'
var dtrainer = 'top5trainer.csv'
// Define SVG area dimensions
var svgWidth = 1500;
var svgHeight = 1000;
// Define the chart's margins as an object
var chartMargin = {
top: 30,
right: 30,
bottom: 130,
left: 30
};
// Define dimensions of the chart area
var chartWidth = svgWidth - chartMargin.left - chartMargin.right;
var chartHeight = svgHeight - chartMargin.top - chartMargin.bottom;
// Select body, append SVG area to it, and set the dimensions
var svg = d3.select("body")
.append("svg")
.attr("height", svgHeight)
.attr("width", svgWidth);
// Append a group to the SVG area and shift ('translate') it to the right and to the bottom
var chartGroup = svg.append("g")
.attr("transform", `translate(${chartMargin.left}, ${chartMargin.top})`);
var btnj = document.getElementById("Jockey")
btnj.addEventListener('click', function(e){
change(e.target.id)
})
var btnt = document.getElementById("Trainer")
btnt.addEventListener('click', function(e){
change(e.target.id)
})
function change(value){
if(value === 'Jockey'){
update(djockey);
}else if(value === 'Trainer'){
update(dtrainer);
}
}
function update(data){
d3.csv(data).then(function(data) {
console.log(data);
// Cast the hours value to a number for each piece of tvData
data.forEach(function(d) {
d.Count = +d.Count;
});
// Configure a band scale for the horizontal axis with a padding of 0.1 (10%)
var xScale = d3.scaleBand()
.domain(data.map(d => d.Name))
.range([0, chartWidth])
.padding(0.1);
// Create a linear scale for the vertical axis.
var yLinearScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.Count)])
.range([chartHeight, 0]);
// Create two new functions passing our scales in as arguments
// These will be used to create the chart's axes
var bottomAxis = d3.axisBottom(xScale);
var leftAxis = d3.axisLeft(yLinearScale).ticks(10);
// Append two SVG group elements to the chartGroup area,
// and create the bottom and left axes inside of them
chartGroup.append("g")
.call(leftAxis);
chartGroup.append("g")
.attr("class", "x_axis")
.attr("transform", `translate(0, ${chartHeight})`)
.call(bottomAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Create one SVG rectangle per piece of tvData
// Use the linear and band scales to position each rectangle within the chart
chartGroup.selectAll("#bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.Name))
.attr("y", d => yLinearScale(d.Count))
.attr("width", xScale.bandwidth())
.attr("height", d => chartHeight - yLinearScale(d.Count));
}).catch(function(error) {
console.log(error);
})
};
D3 has a function allowing you to remove all svg elements. Basically, you select the svg, then run .remove() at the top of your event listener. It will clear out all svg elements.

d3 - y-axis with log scale - ticks getting overlapped

I am trying to use log scale for y-axis on my line chart.
Here is my code:
var yScale_for_axis = d3.scaleLog().domain([1,d3.max(vals)]).range ([height,0]);
g.append("g")
.call(d3.axisLeft(yScale_for_axis).tickFormat( d3.format(".1e"));
The ticks are getting overlapped with each other. Heres how it looks:
What should I do to make it look like this?
Look at the snippet - it seems to be working. Maybe it's tick formatting which makes ticks overlap:
const width = 400, height = 500;
// Append SVG
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create scale
const scale = d3.scaleLog()
.domain([1, 5000])
.range([20, height - 20]);
// Add scales to axis
const yAxis = d3.axisLeft()
.scale(scale);
//Append group and insert axis
svg.append("g")
.attr('transform', 'translate(150, 0)')
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

color category in d3.js

histogram
I want to plot histogram using d3.js where i have dataset of around 13000 points which is divided into 2 clusters . I want to color both of them but when i use category color it only shows first one.In the input file i have Droplet_no, Amplitude, Cluster.
Here is my code :
<script type="text/javascript">
d3.csv("test_F06.csv",function type(d){
d.Droplet_no = +d.Droplet_no;
d.Amplitude = +d.Amplitude;
return d;} , function(data){
var width = 600;
height = 500;
padding = 50;
var colorColumn = "Cluster";
var map = data.map(function(i){return parseInt(i.Amplitude);})
var x = d3.scale.linear()
.domain([0, d3.max(map)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x);
var numbins = 3000;
var histogram = d3.layout.histogram()
.bins(x.ticks(numbins))
(map);
var y = d3.scale.linear()
.domain([0, d3.max(histogram.map(function(i){return i.length;}))])
.range([0, height/2]);
var colorScale = d3.scale.category10();
var canvas = d3.select("body").append("svg")
.attr("width", width+padding)
.attr("height", height+ padding)
.append("g")
.attr("transform", "translate(20,0)")
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
.attr("class", "bar")
var group = canvas.append("g")
.attr("tansform","translate(0, " + height + ")")
bars.append("rect")
.attr("x", function(d){return x(d.x);})
.attr("y", function(d){return 500-y(d.y);})
.attr("width", function(d){return d.dx;})
.attr("height", function(d){ return y(d.y);})
.attr("fill", function(d){return colorScale(d[colorColumn]);});
})
</script>
Can anyone help me?
I am attaching image of the plot as well

Proper way to draw gridlines

Okay, I'm starting to get a little more familiar with D3 but am still a little hazy on some things. I'm now trying to draw grid lines but am realizing that I may be hacking away versus doing it correctly. I tried to add some gridlines, using a tutorial, but ended up with a lot of code that I seem to be jimmy rigging in order to get it to line up properly. I was wondering if anyone could point me to a better way of writing this...
The original code is this.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, -467], [3,-90],[468,481]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
//define X axis this is rly a function, remember, variables can hold functions in JS
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.tickSize(-h, 0, 0)
; //Set rough # of ticks
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(1)
.tickSize(-w, 0, 0)
;
//create the circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
// draw axes here
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(0," + (h - padding) +")")
.call(xAxis);
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(" + padding + ",0)" )
.call(yAxis);
// end draw axes here
</script>
and the code I added in the second link is here
var vis = svg.append("svg:g")
.attr("transform", "translate(20,0)")
var rules = vis.append("svg:g").classed("rules", true)
rules.append("svg:g").classed("grid x_grid", true)
.attr("transform", "translate(-20,"+h+")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(-h,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("grid y_grid", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSize(-w,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("labels x_labels", true)
.attr("transform", "translate(-20,"+ h +")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(0)
.tickFormat("")
// .tickFormat(d3.time.format("%Y/%m"))
)
rules.append("svg:g").classed("labels y_labels", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSubdivide(1)
.tickSize(0, 0, 0)
.tickFormat("")
)
Again, really appreciate any help
Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().
svg.selectAll("line.horizontalGrid").data(yScale.ticks(4)).enter()
.append("line")
.attr(
{
"class":"horizontalGrid",
"x1" : margin.right,
"x2" : width,
"y1" : function(d){ return yScale(d);},
"y2" : function(d){ return yScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "black",
"stroke-width" : "1px"
});
I would suggest to use d3.svg.axis().scale() to tie up the grid to your coordinates. I drew a quick example based on your code: http://jsfiddle.net/temirov/Rt65L/1/
The gist is to use the existing scales, x and y, and to use ticks as grid. Since yAxis and xAxis are already defined we can just re-use them. Here is the relevant code:
//Draw a grid
var numberOfTicks = 6;
var yAxisGrid = yAxis.ticks(numberOfTicks)
.tickSize(w, 0)
.tickFormat("")
.orient("right");
var xAxisGrid = xAxis.ticks(numberOfTicks)
.tickSize(-h, 0)
.tickFormat("")
.orient("top");
svg.append("g")
.classed('y', true)
.classed('grid', true)
.call(yAxisGrid);
svg.append("g")
.classed('x', true)
.classed('grid', true)
.call(xAxisGrid);
You could use the ticks() function of your scale to get the tick values and then use them in a data call to draw the lines.
var ticks = xScale.ticks(4);
rules.selectAll("path.xgrid").data(ticks).enter()
.append("path")
.attr("d", function(d) {
return "M" + xScale(d) + " " + padding + "L" + xScale(d) + " " + (h-padding);
});
You may prefer using a line generator for the grid lines instead of creating the path manually. This works similarly for y grid lines, only that the y coordinate is constant and ranges from 0 to width of graph. You may need to adjust the start and end values to make it look "nice".
In the d3fc project we have created a gridlines component that renders in exactly the same way as the D3(v4) axis.
Here's an example of the usage:
var width = 500, height = 250;
var container = d3.select("#gridlines")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.range([0, 100]);
var yScale = d3.scaleLinear()
.range([0, 100]);
var gridline = fc.annotationSvgGridline()
.xScale(xScale)
.yScale(yScale);
container.append("g")
.call(gridline);
Which renders as follows:
The spacing of the gridlines is determined by the ticks supplied by the associated axes.
Disclosure: I am a core contributor to the d3fc project!
Following #arete's idea, you can use the following to avoid re-drawing unnecessarily the gridline:
function createsGrid(data) {
var grid = gridLine.selectAll("line.horizontalGrid").data(scaleY.ticks());
grid.enter()
.append("line")
.attr("class","horizontalGrid");
grid.exit().remove();
grid.attr({
"x1":0,
"x2": width,
"y1": function (d) { return scaleY(d); },
"y2": function (d) { return scaleY(d); }
});
}
and define the following in your CSS file
line.horizonalGrid{
fill : none;
shape-rendering : crispEdges;
stroke : black;
stroke-width : 1.5px;
}
You could just use innerTickSize, instead of tickSize:
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.innerTickSize(-h);
Use tickSizeInner()
// x axis
var x = d3.scaleLinear().range([0, width]).domain([0, 100000]);
svg
.append("g")
.call(d3.axisBottom(x).ticks(10).tickSizeInner(-height))

How can I plot positive and negative values for both axes in a scatter plot with d3?

I'm using d3 to build a scatter plot and the set of values I have for the x and y axes both have positive and negative values. This is my first attempt at using d3 and I've gathered from introductory tutorials such as On the tenth day of Xmas, get dirty with data using d3.js, that the key is setting up the x and y scales correctly.
Next, I found this tutorial: Bar Chart with Negative Values, which has almost helped me get it right (I think). My data set is too hefty to place here, but here is the code I have a sample of my data to work with:
<div id="compareAll"></div>
<script>
window.onload = function() {
var dataSet = [ [4,4], [-4,4], [-4,-4], [4,-4], ];
var x0 = Math.max(-d3.min(dataSet[0]), d3.max(dataSet[0]));
var xScale = d3.scale.ordinal()
.domain([-x0,x0])
.range([0,10]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataSet[1])
.rangeRoundBands([0,10]);
var svg = d3.select("#compareAll")
.append("svg")
.attr("width", 10)
.attr("height",10)
svg.selectAll("circle")
.data(dataSet)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r",4);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickSize(1);
var yAxis = d3.svg.axis()
.scale(yScale)
.tickSize(1);
svg.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0,10)")
.call(xAxis);
svg.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(10,0)")
.call(yAxis);
}
</script>
Frankly, I don't understand the use of the ordinal scale in the Bar Chart example, however I got seemly worse results when I took it out and made the yScale with the same syntax as the xScale.
Any explanation of the best way to go about working with positive and negative values for both axes would be appreciated. Let me know if I'm missing something obvious. I through together this example from a much larger project as to make it as specific and easy to read as possible.
Thanks!
There's nothing special about using negative values for your axes - you should simply be using linear scales for your axes, and setting the domain to [min, max], where min might well be negative.
See a working version of your code here: http://jsfiddle.net/nrabinowitz/qN5Sa/
A few notes on this:
You should definitely use linear scales for a scatterplot, unless you want more of a matrix. A standard x-scale might look like this:
var xScale = d3.scale.linear()
.domain([-5, 5])
.range([0,w]);
The d3.svg.axis() component includes an .orient() setting - by default, this is "bottom", i.e. a horizontal axis with numbers below the line. To make a correctly oriented y-axis, use .orient('left').
I didn't include the code to determine the min/max domain for each axis based on your data, because I figured it would complicate the example. But you can do this fairly easily if you don't know the bounds of your data:
// assuming data like [[x0,y0], [x1,y1]]
var xmin = d3.min(data, function(d) { return d[0] }),
xmax = d3.max(data, function(d) { return d[0] }),
ymin = d3.min(data, function(d) { return d[1] }),
ymax = d3.max(data, function(d) { return d[1] });

Resources