D3 - using strings for axis ticks - javascript

I want to crate a bar chart using strings as the labels for the ticks on the x-axis (e.g., Year 1, Year 2, etc instead of 0,1,2, etc).
I started by using the numeric values for the x-axis (e.g., 0,1,2,3, etc) as follows:
1) I generate my ranges:
x = d3.scale.ordinal()
.domain(d3.range(svg.chartData[0].length)) //number of columns is a spreadsheet-like system
.rangeRoundBands([0,width], .1);
y = d3.scale.linear()
.domain(Math.min(d3.min(svg.chartData.extent),0), Math.max(d3.min(svg.chartData.extent),0)])
.range([height, 0])
.nice();
2) Make the axes:
d3.svg.axis()
.scale(x);
3) Redraw the axes:
svg.select(".axis.x_axis")
.call(make_x_axis().orient("bottom").tickSubdivide(1).tickSize(6, 3, 0));
This works well with default numeric axis labels.
If I try to use an array of strings for the x tickValues like this....
d3.svg.axis()
.scale(x).tickValues(svg.pointsNames); //svg.pointsNames = ["Year 1", "year 2", etc.]
... when I redraw the chart (with or without changes to the data/settings), the labels swap like this.
Notice how Col 1 takes the place of Col 0 and vice versa.
Do you know why this happens?

Update
Just sort the svg.pointsNames before you apply them as tickValues. Make sure you sort them in exactly the same way that you sort your data. This way, a one-one mapping is always maintained between your labels and tick values.
Also if I may, check out the tickFormat` function here. This seems a better option to me.
//Tick format example
chart,xAxis.tickFormat(function(d, i){
return "Year" + d //"Year1 Year2, etc depending on the tick value - 0,1,2,3,4"
})

Thanks for that...I used this function with an inline if-clause to handle a different x-axis series with names instead of numbers.
The factions Array consists of all relevant names sorted by the indexes of the series who then just get matched with its corresponding index in the data.
xAxis = d3.svg.axis().scale(xScale)
.tickFormat(function(d) {
if(seriesX == 'seriesValue'){
return factions[d]}
else{
return d}
})
.orient("bottom");

Related

Issue with displaying correct domain values in legend tick

I am working on continuous color legend using d3.interpolateViridis. I have problem in displaying the legend tick values. I want to display my min(at one end) and max(at another end) (domain values) in legend. I tried changing ticks value but no help.
Here is my code snippet:
//scale
var colorScale2 = d3.scaleSequential(d3.interpolateViridis).domain([0, 0.38]);
//other code
var legendscale = d3.scaleLinear()
.range([0, legendheight - margin.top - margin.bottom])
.domain(colorscale.domain());
// scale tick
var legendaxis = d3.axisRight()
.scale(legendscale)
.tickSize(16)
.ticks(2);
Also, I have shared JS fiddle link where it takes tick as 0.0 and 0.2(this is supposed to be max value: 0.38).
https://jsfiddle.net/shru90/e42vcLy0/30/
Note: My min value is 0 and max is 0.38(which can vary based on data)
By default, the axis puts ticks at nice, round values. If there are specific values that you want to have tick marks for, then you can set the tickValues:
d3.axisRight()
.scale(legendscale)
.tickSize(16)
.tickValues(legendscale.domain());

D3 unusual stacked bar chart

I need to do an unusual stacked bar chart and acutally, i have no real idea how to do it.
In general it sounds really easy:
I have a CSV file with different values who can either be int or string. Each row has the same amount of values.
I now need to do a stacked bar chart who does the following:
-One bar for every column
-Every column needs to have the same height (cause same amount of entries for each column)
And EACH bar needs to have a different amount of stacks, one stack for every different value which exists. (The more often a value exists, the larger this stack should be)
An example CSV file would be:
Day, Value
Mo, 5
Mo, 3
Tu, 5
Tu, 6
So for the Day row i need 2 stacks the same height and for the Value row 3 stacks where one is 1/2 height and the two other 1/4 each.
And that's the problem which i have. Every example i can find on the internet works with the same amount of stacks for each bar. (For example: https://bl.ocks.org/mbostock/3886394 or https://bl.ocks.org/mbostock/1134768)
Any sugestions how i can solve this problem?
You need to handle each column as a separate stack and position them in your chart. This fiddle: https://jsfiddle.net/gwhn1sgv/2/ shows how.
The data for i. e. the first column need to be transformed to a histogram like this:
var column = 'Day';
var histo = { Mo: 2, Tu: 2 }
Then you can stack and transform them:
var keys = Object.keys(histo);
var stack = d3.stack()
.keys(keys)([histo])
//unpack the column
.map((d, i) => {
return {key: keys[i], data: d[0]}
});
so they look like this:
var stack = [
{ key: 'Mo', data: [0, 2] },
{ key: 'Tu', data: [2, 4] }
]
If you have a band scale
var x = d3.scaleBand()
.domain(data.columns);
you can build the bar chart column at x(column):
chart.append('g')
.selectAll('rect')
.data(stack)
.enter().append('rect')
// position in the x-axis
.attr('x', x(column))
.attr('y', d => y(d.data[0]))
.attr("height", d => y(d.data[1]) - y(d.data[0]))
.attr("width", x.bandwidth());

Rescale axis ticks and text in d3.js multi y-axis plot

I have a parallel coordinates plot that is based off this code: http://bl.ocks.org/syntagmatic/2409451
I am trying to get the tick marks and the numbers on the y axes to scale from the min to the max of the data rather than autoscaling to the conveniently linear numbers like it currently done.
I have not been able to find any example of using d3 or js where a plot of any sort does this unless the data happens to land on those values.
I have been able to just show the min and max value, but cannot get ticks between these by replacing the 3rd line of //Add an axis and title with:
.each(function(d) {d3.select(this).call(d3.svg.axis().scale(y[d]).tickValues(y[d].domain()).orient("left")); })
For reference, the data file is read in as a .csv and ends up looking like this with alphabet representing the headings in the .csv file:
var example_data = [
{"a":5,"b":480,"c":250,"d":100,"e":220},
{"a":1,"b":90,"c":50,"d":33,"e":88}
];
EDIT:
The main issue is iterating over the array that has the domains for each column to create a new array with the tick values. Tick values can be set using:
d3.svg.axis().scale(y[d]).tickValues(value 1[d],value 2[d], etc)
y[d] is set by:
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(cars[0]).filter(function(d) {
return d != "name" && (y[d] = d3.scale.linear()
.domain(d3.extent(cars, function(p) { return +p[d]; }))
.range([h, 0]));
}));
Since you have the min and the max you can map them in any way you want to any scale you want [y0,yn]. For example with y0 = 100, yn = 500 (because HTML counts from top and down).
Here I use a linear scale
d3.scale.linear()
.domain([yourMin,yourMax])
.range([y0,yn]);
Does this help?

Add labels to a d3 bar chart

I have a d3 barchart for which I pull data from firebase. I want to add the labels to the x-axis. Here is the code of my barchart:
new Firebase('https://exampl.firebaseIO.com/example').on('value', function (snapshot) {
var lst = [];
snapshot.forEach(function(childSnapshot) {lst.push(childSnapshot.val());});
var magValue = new crossfilter(lst).dimension(function (d) {return d.count;});
var magLabel = new crossfilter(lst).dimension(function (d) {return d.Owner;});
dc.barChart("#dc-magnitude-chart")
.width(480)
.height(150)
.margins({top: 10, right: 10, bottom: 20, left: 40})
.dimension(magValue) // the values across the x axis
.group(magValue.group().reduceSum(function(d) {return d.count;})) // the values on the y axis
.transitionDuration(500)
.centerBar(true)
.gap(56) // bar width Keep increasing to get right then back off.
.x(d3.scale.linear().domain([0.5, 7.5]))
.elasticY(true)
.xAxis().tickFormat(function(v) {return v;});
dc.renderAll();
});
magValue is the simple count of occurences and it is diplayd on the x-axis. I want the names that are stored in magLabel variable to be displayed below the counts. Thanks.
For reference: in the comments on #bencripps answer, the OP talks about using xAxis.tickValues(['One','two','three','four','five','six','seven']).
tickValues is actually if you want to specify custom ticks within the scale you're using. Right now, you're using a linear scale:
.x(d3.scale.linear().domain([0.5, 7.5]))
so it expects your tick values to be points on that scale where it can draw ticks. If you had something more like xAxis.tickValues([1, 2, 3, 4, 5, 6, 7]), it should work.
However, it sounds like you don't actually want a linear scale. d3 also has other scale types, and it sounds like the one you want is an Ordinal Scale. Ordinal scales are the scales you typically think of for bar charts; they're a type of scale that has a discrete domain. In this case, you could try changing your scale to something like:
.x(d3.scale.ordinal().domain(['One','two','three','four','five','six','seven']))
so it uses an ordinal scale instead. Since you're using dc.js, you'll also need to specify
.xUnits(dc.units.ordinal)
so that it knows to use ordinal marks.
Here is some simple code for adding an X axis; you will have to fiddle with the domain, and range to get the desired length, and number of ticks.
var XaxisScale = d3.scale.linear()
.domain([0,150]) //you will have to set the domain, not sure what you want
.range([0, magValue.length)
var xAxis = d3.svg.axis()
.scale(XaxisScale)
.ticks( magValue.length ) // this you will need to set as well
var xAxisGroup = $('"#dc-magnitude-chart').append("g")
.attr('class', 'axis')
.attr('transform', 'translate(5,8)')
.call(xAxis);
note, the .call(xAxis) is what is actually appending the X axis to your chart.

D3js: How to generate X axis labels/ticks if data for one of axis is 0?

In my case i have dynamic data which loads into d3Js graph. everything works perfeclty if i have userdata like :
Name : bla, y: 1, x:1
Name : bla2, y: 1, x:2
Name : bla3, y: 3, x:3
Name : bla4, y: 7, x:4
but the problem comes when i have a data like this:
Name : bla, y: 0, x:1
Name : bla2, y: 0, x:2
Name : bla3, y: 0, x:3
Name : bla4, y: 0, x:4
then whole y axis have no labels at all. how do i show predefined labels in this case?
Here is the code to generate axis :
var x = d3.scale.linear().range([0, width]);
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");
y.domain(d3.extent(data, function(d) { return d.y; })).nice();
x.domain([0, d3.max(data, function(d) { return d.x; })]).nice();
The problem stems not from the fact that all your values are zero, but from the fact that all your values are the same and that your domain therefore has zero width (the max and min domain values are the same).
The linear scale falls apart in this case, because you are telling to create a linear relationship such that both the start and end values of the range are equal to the same value in the domain. You don't get a divide-by-zero error like you would if your range was zero width, but you don't get a meaningful scale, or meaningful tick values.
Even the .nice() function can't help you, since it's designed to extend the domain to the next tick value after determining an appropriate tick spacing based on your domain.
Working fiddle based on your code, demonstrating the problem:
http://fiddle.jshell.net/LJdZZ/
How can you fix it? You have to force the domain's start and end points to be different. There are a few different approaches you can use, you'll have to decide which is appropriate for your data:
If you know that your domain is normally going to be small integers, you can add and subtract 1 from the max and min values:
y.domain(d3.extent(data, function (d) {
return d.y;
}).map(function(d, i) { //map the two-element array returned by d3.extent
if (i) return d+1; //if i=1
return d-1; //if i=0
});
)
.nice();
If you will normally want the domain to include zero you can force it to include both zero and one:
y.domain(d3.extent(data.concat([{y:0},{y:1}]), function (d) {
return d.y;
}))
.nice();
If your data values and scale are likely to vary such that you don't want to force any values normally, you can check if the domain width is zero and only then set a specific domain:
y.domain(d3.extent(data, function (d) {
return d.y;
}))
.nice();
var yDomain = y.domain();
if (yDomain[0] == yDomain[1] ) {
y.domain([yDomain[0], yDomain[0] + 1])
.nice();
}
Adaption of the fiddle with the last method implemented:
http://fiddle.jshell.net/LJdZZ/1/
(You might want to make the domain check & correction a separate function that you call with the scale as a parameter. That way you can use it for both linear scales, without cluttering up your main code with extra variables.)

Categories

Resources