nvd3 + lineplusbarchart + allign axises - javascript

not sure if I am going about this the right way but here goes...
So i have the this example see fiddle here
using lineplusbarchart and i am building on it from this question i posted here:
SO question
I have edited the lineplusbarchart to show the labels on the xaxis:
chart.xAxis.tickFormat(function(d) {
var dx = testdata[0].values[d] && testdata[0].values[d].x || 0;
return dx;
})
.showMaxMin(false);
but i am still having a couple of issues to get what i want...
1 -> how can i make the y1 and y2 axis be alligned? (ideally it would be good if there was only one axis)
2 -> how do i remove the y2 axis? (soution here but this does not work as I then want the 2 axis aligned)
3 -> how do i make the thickness of the barchart part for label1 and label5 to be the same thickness as the others(lable2,3 and 4)?

hope this helps:
you can use chart.lines.forceY() to set a range. To make it
work with dynamic values I'd suggest to find the overall max value of the attached data
and use it for the bar and the lines. Eg:
var maxValue = d3.max(d3.entries(testdata), function(d) {
return d3.max(d3.entries(d.value.values), function(e) {
return e.value.y;
});
}),
minValue = 0;
chart.bars.forceY([minValue, maxValue]);
chart.lines.forceY([minValue, maxValue]);
Your posted solution is exactly what I would do too.
Remove padData()

Related

The change of polygon contain make my demo wrong in D3

I'm using D3 to draw some pieces of information on canvas. And I got my aim in version 3 but failed in version 4 (Of course, I had changed the updated functions in version 4 such as d3.geo.mercator() to d3.geoMercator()). I debugged some relative functions and found the projection function was different.
In Version 3:
function projection(point) {
point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
return [ point[0] * k + δx, δy - point[1] * k ];
}
In Version 4:
function projection(point) {
point = projectRotate(point[0] * radians, point[1] * radians);
return [point[0] * k + dx, dy - point[1] * k];
}
My code Snippets is following:
var d3Projection = d3.geoMercator().scale(1).translate([0, 0]);
// var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geoPath().projection(d3Projection);
// var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
I debugged there and found the value of 'δx' was different from 'dx', which is the same condition on 'δy(dy)'
Debugging in Version 4
Debugging in Version 3
Could you someone explain the differences in this condition, please?
And what's the meaning of dx(δx), dy(δy) ?
D3 V3 correct demo.
D3 V4 The center coordinate is wrong. wrong demo.
_______________________________________________________________________________
I found the different code which result into my wrong demo
This code was changed from v3.5.17, if I change this line code back to v3.5.16, everything will be OK. I will get the reason.
The projection function in vanilla D3v3 and v4 is identical, your problem is that you're using some modified D3v3 version. If you try running this snippet in your D3v3 environment (assiming the topojson is loaded in var topo):
var features = topojson.feature(topo, topo.objects.counties);
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
console.log(JSON.stringify(d3Path.bounds(features)));
you'll get
[[-3.141592653589793,-3.141592653589793],[3.141592653589793,3.141592653589793]]
If however you load current https://d3js.org/d3.v3.min.js, the same exact snippet will produce
[[2.110384445824674,-0.5558860193147427],[2.1337212032517625,-0.5298056661805025]]
which is the same as V4. So you either need to fix your code to work with current D3 (v3 and v4), or modify your D3v4 in the same way as D3v3.

How to get axis ticks out of d3 without rendering in SVG?

I'm implementing a 2d chart using canvas. I want to reuse d3's logic for generating the chart's axes. d3 does quite a lot of good work in generating axes and I'd like to take advantage of it.
(Note: For backward-compatibility reasons I'm stuck using d3v3 for the time being.)
Consider the following d3 code:
let scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
let axis = d3.svg.axis()
.scale(scale)
.ticks(num_ticks)
.tickSize(10)
.orient("bottom");
I can render this into a chart div with:
svg.selectAll('.x-axis').call(axis);
I want to be able to programmatically get the tick data out of axis, including the formatted labels, by writing a function that behaves as follows:
ticks = get_axis_ticks(axis)
ticks should hold each tick position (as a Date in this particular case) and the corresponding d3-generated label.
[[Date(...), "Wed 19"],
[Date(...), "Fri 21"],
[Date(...), "Apr 23"],
[Date(...), "Tue 25"],
...]
I could then use this data to paint an axis on my canvas.
I've dug into d3v3 source (in particular here: https://github.com/d3/d3/blob/v3.5.17/src/svg/axis.js) but I find it very difficult to tease apart the logic from the SVG manipulation.
Help would be much appreciated.
One idea I have is to use the scale function you have created to generate the ticks you desire and push them into an array.
As a very simple example, if you would like 10 ticks, each incrementing by a unit of 1, you could do something like this: https://jsfiddle.net/Q5Jag/3148/
//define dummy values
var lo = 1;
var hi = 10;
var width = 512
var scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
//define your function
var get_x_axis = function() {
let axisArr = [];
for(var i = 0; i < 10; i++ ) {
//calculate your value
axisArr.push(scale(i))
}
return axisArr
}
//call it
let axisTicks = get_x_axis()
//log it
console.log(axisTicks)
I'm not sure if this is what you're looking for, but if you need further help just ask.
I was able to get this working. I found the time formatting strategy in the d3 docs: https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format_multi I believe this is the strategy that d3 itself uses by default when users do not provide a custom format.
I learned that simply calling scale.ticks(N) will return Nish ticks suitable for rendering on an axis. These tick values are chosen on natural boundaries. E.g., if you're working with a time axis, these ticks will be aligned on minute, hour, day boundaries.
Here's a solution:
let time_format = d3.time.format.multi([
[".%L", d => d.getMilliseconds()],
[":%S", d => d.getSeconds()],
["%I:%M", d => d.getMinutes()],
["%I %p", d => d.getHours()],
["%a %d", d => d.getDay() && d.getDate() !== 1],
["%b %d", d => d.getDate() !== 1],
["%B", d => d.getMonth()],
["%Y", () => true]
]);
let get_ticks = (width, lo, hi, num_ticks) => {
let scale = d3.time.scale().range([0, width]).domain([lo, hi]);
return scale.ticks(num_ticks).map(t => [t, time_format(t)]);
};

Unevenly space in D3 axis using rangePoints

I'm using D3 (v3) to build a plot with two axis. One of them it isn't working correctly when I add two or more objects. My piece of code is:
//Set upperLevelyScale
var previousValues = 0;
var upperLevelyScale = d3.scale.ordinal()
.domain(Object.keys(valuesY_numbers).sort())
.rangePoints((function(){
var values = Object.values(valuesY_numbers).map(function(x){
previousValues += (x * itemSize);
return previousValues});
values.unshift(0);
return values;
})());
when domain is ["MyValue1"] and rangePoints is [0, 170], the tick of the axis shows perfectly at the middle of the axis. But if domain is ["MyValue1", "MyValue2"] and rangePoints is [0,170,320] the ticks are not really covering their part but other.
What I'm trying to set up is just: MyValue1 is from 0 to 170, MyValue2 is from 170 to 320, etc. I have tried with '.range' also but still not working.
Thanks in advance.

NVD3 Cumulative line chart displays wrong y values

I have converted a line chart into a cumulative line chart and its y values are not displayed correctly. The range of the y axis should be 80.00 - 140.00 but instead I get -0.08 - 0.20. Has anyone managed to tweak their normalization code below to make it work with all kinds of ranges?
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (lines.y()(point, pointIndex) - v) / (1 + v)
};
return point;
})
Any help will be greatly appreciated.
I know that this question is somewhat old, but I am convinced that the normalization code for the cumulative line chart is not conceptually correct. Furthermore, the NVD3 cumulative line chart implementation is actually an index chart implementation (see Mike Bostock's example). A cumulative line chart would be more like this, I think. The cumulative chart can be easily achieved using the NVD3 line chart and some quick modifications to the underlying data.
If we take Bostock to be correct, and we really do wish to achieve an indexed line chart, then the indexify function in NVD3 should be changed to:
/* Normalize the data according to an index point. */
function indexify(idx, data) {
if (!indexifyYGetter) indexifyYGetter = lines.y();
return data.map(function(line, i) {
if (!line.values) {
return line;
}
var indexValue = line.values[idx];
if (indexValue == null) {
return line;
}
var v = indexifyYGetter(indexValue, idx);
// TODO: implement check below, and disable series if series
// causes a divide by 0 issue
if ((Math.abs(v) < 1e-6) && !noErrorCheck) {
// P.S. You may have to set a higher threshold (~1e-6?).
// I don't know I didn't run any tests...
line.tempDisabled = true;
return line;
}
line.tempDisabled = false;
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (indexifyYGetter(point, pointIndex) - v) / v
};
return point;
});
return line;
})
}
I asked a related question to the authors of NVD3 and plan to submit a pull request. Note that percentage change charts are really only meaningful when all of the underlying data is positive. When you start throwing negative values into the mix, percentage change loses all of its meaning.
What I found works is to insert another point with a y value of 0 at the beginning of the sequence of points.
Given a list of data points in the form [ [x1,y1], [x2,y2], ... [xn,yn]] ],
something like values.upshift([0,0]) works for me (the x value is arbitrary, but i just use 0 or values[0][0]) to insert to the front of the list.
(I'm getting the same thing with that chart. I'm still looking into it, but I hope this helped.)

Google Charts Bubble Charts categorical x and y axes instead of numeric

I have created a beautiful bubble chart using Google Charts. Here is a shot of the chart:
The numbers along the x-axis represent individual customers. The numbers along the y-axis represent individual products. As you all can see, there are about 23 customers and 7 products.
The problem is that the X and Y axes can only be numeric (as far as I know from the documentation). I wish to be able to display the string values for the customers and products along the axes instead of just representative integers. This will make it easier to understand what we are looking at.
Does anyone know how this can be accomplished?
I do have JS arrays which contain the customer and product strings. Their integer indices correspond to the numbers that show up on the chart. For example:
customers[6] = "Microsoft"
customers[7] = "Dell"
...
But right now just the integers show up.
Any help would be greatly appreciated! Thanks!
Here is the code I used to define the chart:
var options = {
'title':'Customer / Product Grid',
'width': 1000,
'height':500
};
//for customer product grid
var customer_product_grid_data_table = new google.visualization.DataTable();
customer_product_grid_data_table.addColumn('string', 'Customer and Product');
customer_product_grid_data_table.addColumn('number', 'Customer');
customer_product_grid_data_table.addColumn('number', 'Product');
customer_product_grid_data_table.addColumn('number', 'Profit Margin');
customer_product_grid_data_table.addColumn('number', 'Proportion of Sales');
for (var i = 1; i < customer_product_grid_data.length; i ++){
customer_product_grid_data_table.addRow([
'',
customer_product_grid_data[i][0],
customer_product_grid_data[i][1],
(customer_product_grid_data[i][3] - customer_product_grid_data[i][2]) / customer_product_grid_data[i][3] * 100,
customer_product_grid_data[i][3] / qnty_sell_total
]);
}
var chart = new google.visualization.BubbleChart(document.getElementById('customer_product_grid'));
chart.draw(customer_product_grid_data_table, options);
Judging from all the searching I did, and also the answer given here by jmac, I decided the only way to go was a Javascript hack to replace the axes numbers with words. The code I implemented is here:
/*
*
* The following 2 functions are a little hacky, they have to be done after calling the "draw" function
* The bubble chart originally displays only numbers along the x and y axes instead of customer or product names
* These 2 functions replace those numbers with the words for the customers and products
*
*/
for ( var i = -2; i < products.length + 1; i ++ ){
$('#customer_product_grid svg text[text-anchor="start"]:contains("'+i+'")').text(function(j,t){
if (t == i){
if (i >= products.length || i < 0){
return " ";
}
return products[i];
}
});
}
for ( var i = -2; i <= customers.length + 3; i ++ ){
$('#customer_product_grid svg text[text-anchor="end"]:contains("'+i+'")').text(function(j,t){
if (i >= customers.length + 1 || i <= 0){
return " ";
}else if (t == i){
return customers[i-1];
}
});
}
Basically, you just make a for loop that iterates through all the integers that you are showing on the x and y axes. Do some if...else stuff to either replace the integer with an element from the array, or just make it blank.
Keep in mind for the above code to work properly, you need to have the following property in the chart options -> vAxis: { textPosition: 'in' }
Unfortunately, there is no easy way to do this as bubble charts (or anything that uses numerical series for an axis value). You can work around it as explained here.
The general concept is to eliminate your axis labels on the 'main chart' and adjust the gridlines to match the amount of labels you want. Then create an additional dummy chart which shows only the categories, and use that to show the labels.
Unfortunately, this is how it has to be until Google decides to implement the entire ICU pattern set for chart axes...

Categories