D3 update tick values without updating the domain - d3.js

I have an x-axis with long tick names, and I want to provide their abbreviations as tick values. Long tick names are used to set the domain.
axisElement = d3.axisBottom(scale)
.tickValues(tickValues); // these are long names when axis is first drawn
chart.append("g")
.attr("class", "bottomAxis")
.attr("transform", "translate(0, " + height + ")")
.call(axisElement);
I first draw the axis with proper long names which looks like this, and then try to update the ticks using axis.tickValues() function in D3.
updateTicks(tickValues) {
chart.select(".bottomAxis").call(this.axisElement.tickValues(tickValues));
}
But when D3 draws the ticks, it uses the old domain values in scale (which are the long names) and messes up the positioning of abbreviated names. It looks like this. I also get the following error:
Error: <g> attribute transform: Expected number, "translate(NaN,0)".
Which, I believe, is saying that it tried to translate the tick according to old domain values, but found abbreviated value so it can't translate.
I tried changing the scale's domain to abbreviated names, and then D3 positions them appropriately.
My question is, is it possible to change tickValues without changing the domain values?

You can do it like the following. You need to call axisBottom on your svg element and then use tickFormat to set the abbreviated text values. I have included a small example in the fiddle to show you. In the example, I am initially setting the long tick values first by default and then changing it to the abbreviated ones like you want to do.
var abbr = ["ll1", "ll2", "ll3", "ll4"];
svg
.call(d3.axisBottom(x).tickFormat(function(d, i) {
return abbr[i];
}));
JSFiddle - https://jsfiddle.net/tkw07c96/

Related

d3 multiple different graticules

I am trying to create a "proper" version of the logo at IFMMS Logo
using the graticule feature of d3.js.
At https://gist.run/?id=22361573b05b541ac9799037116aea8d you'll find the current state of the code - specifically version https://gist.run/?id=22361573b05b541ac9799037116aea8d&sha=dd6aef5c64e3ac4c1c917fd5e3c7bbf4b91c75a8
In a loop i am trying to set the graticule steps for different scales of the logo draft.
var lonsteps=6+col*2;
var latsteps=10;
var title='IFMMS Logo';
createLogo(title,id,cx,cy,scale,15,lonsteps,latsteps,debug);
in the "createLogo" function i am using these steps as outlined in the answer:
https://stackoverflow.com/a/19033063/1497139
I get
instead of
so instead of having 6/8/10 steps for the longitudinal grid lines I get three times 6 steps. It seems as if the first setting "overrides" all others although i assign different values.
What is causing this and how can it be fixed?
If you pass a parameter (svgid) to a function use it and don't use the global variable (id) with the same value.
function createLogo(title,svgid,cx,cy,scale,strokeScaleFactor,lonsteps,latsteps,debug) {
// ...
ggraticule.selectAll("path.feature"+svgid)
.data(graticule.lines)
.enter().append("path")
.attr("class", "feature"+svgid)
.attr("d", path);
// ...
}
One indication of the problem can be that your scale is not working.
The reason a HTML ID should be unique.
fix it by making the graticule defs id unique and use this new id.
var ggraticule=defs.append("g")
.attr("id","graticule-"+svgid)
.attr("class", "graticule")
.attr("stroke-width",scale/strokeScaleFactor);
// ...
use(svg,"graticule-"+svgid);
You still have a problem with the background, choose a color and see the effect.
In D3 v5+ you can use the step method on your geoGraticules like so:
var graticules = d3.geoGraticule().step([15, 15])
The default step is 10° and 10° for longitude and latitude.

NVD3.js multiChart x-axis labels is aligned to lines, but not bars

I am using NVD3.js multiChart to show multiple lines and bars in the chart. All is working fine, but the x-axis labels is aligned only to the line points, not bars. I want to correctly align labels directly below the bars as it should. But I get this:
With red lines I marked where the labels should be.
I made jsFiddle: http://jsfiddle.net/n2hfN/
Thanks!
As #Miichi mentioned, this is a bug in nvd3...
I'm surprised that they have a TODO to "figure out why the value appears to be shifted" because it's pretty obvious... The bars use an ordinal scale with .rangeBands() and the line uses a linear scale, and the two scales are never made to relate to one another, except in that they share the same endpoints.
One solution would be to take the ordinal scale from the bars, and simply adjust it by half of the bar width to make the line's x-scale. That would put the line points in the center of the bars. I imagine that something similar is done in the nv.models.linePlusBarChart that #LarsKotthoff mentioned.
Basically, your line's x-scale would look something like this:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
...where xScaleBars is the x-scale used for the bar portion of the chart.
By combing through the source code for nvd3, it seems that this scale is accessible as chart.bars1.scale().
Maybe someday the authors of nvd3 will decide that their kludge of a library deserves some documentation. For now, I can show you the kind of thing that would solve the problem, by making a custom chart, and showing how the two scales would relate.
First, I'll use your data, but separate the line and bar data into two arrays:
var barData = [
{"x":0,"y":6500},
{"x":1,"y":8600},
{"x":2,"y":17200},
{"x":3,"y":15597},
{"x":4,"y":8600},
{"x":5,"y":814}
];
var lineData = [
{"x":0,"y":2},
{"x":1,"y":2},
{"x":2,"y":4},
{"x":3,"y":6},
{"x":4,"y":2},
{"x":5,"y":5}
];
Then set up the scales for the bars. For the x-scale, I'll use an ordinal scale and rangeRoundBands with the default group spacing for nvd3's multiBar which is 0.1. For the y-scale I'll use a regular linear scale, using .nice() so that the scale doesn't end on an awkward value as it does by default in nvd3. Having some space above the largest value gives you some context, which is "nice" to have when trying to interpret a chart.
var xScaleBars = d3.scale.ordinal()
.domain(d3.range(barData.length))
.rangeRoundBands([0, w], 0.1);
var yScaleBars = d3.scale.linear()
.domain([0, d3.max(barData, function(d) {return d.y;})])
.range([h, 0])
.nice(10);
Now here's the important part. For the line's x-scale, don't make a separate scale, but just make it a function of the bars' x-scale:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
Here's the complete example as a JSBin. I've tried to document the major sections with comments so it's easy to follow the overall logic of it. If you can figure out from the nvd3 source code exactly what each of the elements of the multiChart are called and how to set the individual scales of the constituent parts, then you might be able to just plug in the new scale.
My feeling on it is that you need to have a pretty good handle on how d3 works to do anything useful with nvd3, and if you want to customize it, you're probably better off just rolling your own chart. That way you have complete knowledge and control of what the element classes and variable names of the parts of your chart are, and can do whatever you want with them. If nvd3 ever gets proper documentation, maybe this will become a simple fix. Good luck, and I hope this at least helps you get started.

Setting up Scale Domain and Bar Width for multiple bar charts in one page

I am very new to D3. I have a large JSON array that I am pulling into D3 via AJAX to make bar graphs. The array is used to generate multiple slides in a Bootstrap carousel, where each slide is an element of the array (i below) with corresponding sub elements. I have run into issues setting up x and y scale's by extracting directly from this large array so I have put the necessary values in separate arrays (although this obviously has not solved the problem yet).
These arrays, which are the same length as the number of slides, are maxDefectCounts() and numberOfBars(). Note, they are not associative arrays/JSON, they are data that I have extracted from the JSON.
I would like the height of the largest bar in each slide to be equal to maxDefectCounts[i] where i is the current slide. I would like the width of the bar to be numberOfBars[i] / width of SVG that the bar chart is bound to.
For the y-scale I have tried the following with no luck. I'm not sure if you can bind a separate data element to a scale function (note that I am using j instead of I in the function below so as not to be confused with i, which is the slide number)
var y = d3.scale.linear()
.range([height,0])
.data(maxDefectCounts)
.enter()
.domain([0,function(d,j){return d[j];}]);
For width what I am currently doing is iterating through the different bars in d.binSummary and using a fixed value of barWidth (this is for testing). Since each slide has a different number of bars, I would like this to be proportional to numberOfBars[i] instead of this fixed value
var bar = paretoBox.selectAll("g")
.data(function(d) {return d.binSummary;})
.enter()
.append("g")
.attr("transform",function(d,j) {
return "translate(" + j * barWidth + ",0)";
});
Any advice would be much appreciated, thank you in advance.
I identified a workaround. Since independent values are needed for each slide, I built a loop that takes the original JSON as an input and builds a slide for each element. This allows the extraction of the needed information. A similar approach is used here with regard to building multiple charts with a single input array.

D3.js: Hide the last tick on an axis?

I am using D3.js to draw an axis with ticks. I would like to hide only the last tick on the y-axis.
Maybe a picture will make it clearer. This is what my axis looks like at the moment - I'd like to hide the "21" and its associated tick.
Here is my current code:
var yAxisScale = d3.svg.axis().orient("left");
yAxisScale.scale(y_position);
yAxisScale.ticks(20).tickFormat(function(d) { return d+1; });
var yAxis = vis.selectAll("g.y.axis").data([1]);
Is there a way I can hide only the last tick, regardless of how many ticks there are on the y-axis?
I tried adding this line, but it doesn't work, even though the selectAll expression appears to return the right element.
d3.select(d3.selectAll("g.y.axis g")[0].pop()).style('opacity', 1e-6);
The opacity is still set to 1.
you should to take a look at axis.tickSize(). it allows you to set the size of the major, minor, and end ticks.
also see here for similar question:
Remove end-ticks from D3.js axis

D3 multi line chart - strange animation

I have created a multi line chart with a simple animation. In the beginning there are no data and after clicking a button new values are emulated and the line "move" left. The move is animated using a "shift".
The problem occurs when the lines "fill" the whole graph area (that means there are y values for all x values) and then the lines are animated in a different way. It looks like the y values are animated on a curve, not slided to the left.
The animation works good for both axes:
svg.selectAll("g .x.axis")
.transition()
.duration(500)
.ease("linear")
.call(xAxis);
svg.selectAll("g .y.axis")
.transition()
.duration(500)
.ease("linear")
.call(yAxis);
And not for lines (this code helped me a lot)
svg.selectAll("g .city path")
.data(processedData).transition().duration(500)
.ease("linear")
.attr("d", function(d, i) { return line(d.values); })
.attr("transform", null);
The Fiddle is accessible here.
Thanks for help.
The problem is that you're deleting data when there is too much. The way d3 matches data to existing data (when you call the .data() function) is by index. That is, the first element in the array that you pass to .data() matches the first bound data element, regardless of what the data actually looks like.
What happens in your case is that as soon as you start deleting data, the individual data points are updated instead of shifted. That's why you're seeing the "squiggle" -- it's updating each data point to its new value, which is the value the data point to the right had before.
With the code you currently have, this is hard to fix because you are not matching the data for individual lines explicitly. I would recommend that you have a look at nested selections which allow you to draw multiple lines and still explicitly match the data for individual ones. The key is to use the optional second argument to .data() to supply a function that tells it how to match the data (see the documentation). This way you can tell it that some data points disappeared and the other ones should be shifted.
you can get around this problem in 2 step.
in function update() : redraw you .data() with the new point at the end but without remove the first old point (with animation), like that each key is the same before and after transition.
at the end of function update() : you can remove the old value and redraw .data() without animation.

Resources