d3.js format axis uniformly in millions - javascript

I found this thread and it got me halfway to where I need to be and I'm wondering if anyone knows how I can adjust the solution to fit my needs.
So if I have some values in the thousands, and some values in the millions, does anyone know how I can set all of the ticks to be formatted in the millions? For instance, if I have a value for 800k it would show up as 0.8million instead.
This is what I get when using the above solution without adjusting it.

You don't need to use a SI prefix in this case. Given this domain:
var scale = d3.scaleLinear().domain([200000,1800000])
Going from 200 thousand to 1.8 million, you can simply divide the tick value by 1,000,000 and add a "million" string.
Here is the demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 100);
var scale = d3.scaleLinear().domain([200000,1800000]).range([20,550]);
var axis = d3.axisBottom(scale).tickFormat(function(d){return d/1000000 + " Million"});
var gX = svg.append("g").attr("transform", "translate(20, 50)").call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
EDIT: According to the comments, you want to show "million" only in the last tick. Thus, we have to check if the tick is the last one and conditionally formatting it:
var axis = d3.axisBottom(scale).tickFormat(function(d){
if(this.parentNode.nextSibling){
return d/1000000;
} else {
return d/1000000 + " Million";
}
});
Here is the demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 100);
var scale = d3.scaleLinear().domain([200000,1800000]).range([20,550]);
var axis = d3.axisBottom(scale).tickFormat(function(d){
if(this.parentNode.nextSibling){
return d/1000000} else { return d/1000000 + " Million"}});
var gX = svg.append("g").attr("transform", "translate(20, 50)").call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

How to display the values from this array in the console of javascript?

How can I display the values of the given array in javascript? In other words, how can use console.log over "pie" to display (42.9, 37.9 and 19.2)?
It tried console.log(Object.values(pie)) but it didn't work. Thanks a lot.
This is how I created the array:
var width = 350
height = 350
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#my_dataviz_b")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color =["#98abc5", "#8a89a6", "#7b6888"]
var annotations = ["Home win", "Draw game", "Away win"]
var data = d3.selectAll('.values_half_before').nodes();
var pie = d3.pie() //we create this variable, for the values to be readeable in the console
.value(function(d) {return d.innerHTML; })(data);
You can do it this way:
pie.forEach((item) => {
console.log(item.value)
});
If you are looking to log individual values of your array you could loop over them with a for loop.
for (let i = 0; i < pie.length; i++) {
console.log(pie[i].value);
}
You could also use console.table. This will display the values in a nice table overview.
console.table(pie);

Using d3, how can I create a histogram or bar plot where the last bar is the count of all values greater than some number?

Code link: https://plnkr.co/edit/jLkoMxdzArBBULHF80nb?p=preview
I have a data with some disperse values. It ranges from 61 to 1.2m.
How can I represent it in a Histogram in a way that makes sense?
Can I have the last bucket on d3 that is > 2000 for instance?
Something like this (greater than 5 minutes):
First of all you will need to arrange your data (if you haven't yet), where you just need to create a variable with the > 2000 values.
This is the way I did it (I started d3 last week and I don't have any previous knowledge on JavaScript, so there's probably a better way to do it):
var data = [];
for (var i = 0; i < oldData.length; ++i) {
if (oldData[i] >= 2000) {
data[i] = 2000;
}
else data[i] = oldData[i];
}
Next thing, is o set manually the ticks you want and the tickLabels that correspond to it:
var ticks = [0,200,400,600,800,1000,1200,1400,1600,1800,2000];
var tickLabels = [0,200,400,600,800,1000,1200,1400,1600,1800,"> 2000"];
(notice that you can change to "1,200" and so on if you want the separator)
And instead of calling the d3.axisBottom(x) directly to your chart, I like to create a separate xAxis variable, and set the ticks and tickLabels to it:
var xAxis = d3.axisBottom(x)
.tickValues(ticks)
.tickFormat(function(d,i){ return tickLabels[i] });
Finally you call the xAxis on your chart:
chart.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);

Unable to get zoom by dragging a rectangle over a d3 series chart to work properly

I am trying to get zoom to work by dragging a rectangle over my series plot to identify the interval of zooming. Here is my plunkr
http://plnkr.co/edit/isaHzvCO6fTNlXpE18Yt?p=preview
You can see the issue by drawing a rectangle with the mouse over the chart - The new chart overshoots the boundary of the X and Y axes. I thought my group under the svg would take care of the bounds of the series (path) but I am clearly mistaken. After staring at it for a long time, I could not figure it out. Please ignore the angular aspect of the plunkr. I think the issue is somewhere in the
//Build series group
var series = svgGroup.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series");
//Build each series using the line function
series.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.series);
})
.attr("id", function (d) {
//While generating the id for each series, map series name to the path element.
//This is useful later on for dealing with legend clicks to enable/disable plots
legendMap[d.name] = this;
//Build series id
return buildPathId(d.name);
})
.style("stroke", function (d) {
//Use series name to get the color for plotting
return colorFcn(d.name);
})
.style("stroke-width", "1px")
.style("fill", "none");
Any help with this is appreciated.
Thank you very much.
I think the method renderChartWithinSpecifiedInterval(minX, maxX, minY, maxY, pixelCoordinates) maybe has some problem there.
It seems the parameter like max_x passed in line 130 are a very big value like time seconds
var svg = renderChartWithinSpecifiedInterval(min_X, max_X, min_Y, max_Y, false);
max_X,min_X are value like 1415171404335
min_Y = 0, max_Y = 100
But in dragDrop call in line 192
function gEnd(d,i){
svg.selectAll(".zoom-rect").remove();
var svgGp = svg.select("g");
var groupTransform = d3.transform(svgGp.attr("transform"));
var xOffset = groupTransform.translate[0];
var yOffset = groupTransform.translate[1];
var xBegin = Math.min(xStart,xDyn) - xOffset;
var xEnd = Math.max(xStart,xDyn) - xOffset;
var yBegin = Math.min(yStart,yDyn) - yOffset;
var yEnd = Math.max(yStart,yDyn) - yOffset;
renderChartWithinSpecifiedInterval(xBegin, xEnd, yBegin, yEnd, true);
//It seems here the parameters values are all pixels
like xBegin = 100, xEnd = 200
}
hope it helps!

How to update a D3 chart dynamically

I am trying to create an easy way to create and update donut charts (And other charts, later) in an application. This is built on top of D3.
I've created a drawDonutChart as shown below:
var data = [50, 50];
var dataTwo = [75, 25];
var options = {
colors: ["#0074D9", "#7FDBFF"],
};
function createDonutChart(data, height, width, domElement, options) {
var radius = Math.min(width, height);
var color = d3.scale.category20();
var donut = d3.layout.pie().sort(null);
var arc = d3.svg.arc()
.innerRadius(radius/4)
.outerRadius(radius/2);
var svg = d3.select(domElement).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(donut(data))
.enter().append("path")
.attr("fill", function(d, i) {
if (options.colors == null || options.colors == "undefined") {
return color(i);
} else {
return options.colors[i];
}
}).attr("d", arc);
}
var donutChart = createDonutChart(data, 50, 50, ".slices", options);
when you have a in the page, this will draw a donut chart there.
I would like to write a function updateDonutChart(originalChart, newData), pass it (donutChart, dataTwo) and have it transition this data into the chart instead of the old data.
I've been looking at multiple examples on the D3 website but I haven't been able to get something to work this way (where you simply pass the old chart, and new data). It might be simple, I am just new to D3 :)
Thanks in advance for the help.
So, it wasn't clear to me from the signature of your update call if you wanted to create a new SVG under a possibly new DOM element every time you updated the chart. Also, I am not sure if you are using the word transition to specify a D3 transition of just a change in the data (although that would be simple enough to add). I any case, a simple adaptation to your code to do what you want is in this FIDDLE.
function updateDonutChart(data, options) {...

Display only whole values along y axis in d3.js when range is 0-1 or 0-2

I am using d3js to display a realtime representation of the views of a website. For this I use a stack layout and I update my dataset by JSON at the moment.
When there is only 1 or 2 views being displayed on the y axis, which is dynamic related to the amount of views in the graph, the axis labels are: 1 => 0, 0.2, 0.4, 0.6, 0.8, 1, the axis labels are: 2 => 0, 0.5, 1, 1.5, 2 This makes no sense for my dataset since it displays views of a page, and you can't have half a view.
I have a linear scale in d3js I base my y axis on
var y_inverted = d3.scale.linear().domain([0, 1]).rangeRound([0, height]);
According to the documentation of rangeRound() I should only get whole values out of this scale. For drawing my axis I use:
var y_axis = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(y_inverted.axis = d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5));
Because it is a realtime application I update this every second by calling:
function update(){
y_inverted.domain([yStackMax, 0]);
y_axis.transition()
.duration(interval)
.ease("linear")
.call(y_inverted.axis);
}
yStackMax is calculated from a stacklayout, as far as I know the data used for the y values only contain integers.
var yStackMax = d3.max(layers, function(layer) {
return d3.max(layer, function(d) {
return d.y0 + d.y;
});
});
I have tried several things to get a proper value for my y axis.
d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5).tickFormat(d3.format(",.0f"))
Got me the closest sofar, but it still displays 0, 0, 0, 1, 1, 1
Basically what I want is to only have 1 tick when yStackMax is 1, 2 ticks when it's 2, but it should also work if yStackMax is 12 or 1,000,000
Short answer: You can dynamically set the number of ticks. Set it to 1 to display only two tick labels:
var maxTicks = 5, minTicks = 1;
if (yStackMax < maxTicks) {
y_axis.ticks(minTicks)
}
else {
y_axis.ticks(maxTicks)
}
Long Answer (going a bit off topic):
While playing with your example I came up with a rather "complete solution" to all your formatting problems. Feel free to use it :)
var svg = d3.select("#svg")
var width = svg.attr("width")
var height = svg.attr("height")
var yStackMax = 100000
var interval = 500
var maxTicks = 5
var minTicks = 1
var y_inverted = d3.scale.linear().domain([0, 1]).rangeRound([0, height])
var defaultFormat = d3.format(",.0f")
var format = defaultFormat
var y_axis = d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(minTicks)
.tickFormat(doFormat)
var y_axis_root;
var decimals = 0;
function countDecimals(v){
var test = v, count = 0;
while(test > 10) {
test /= 10
count++;
}
return count;
}
function doFormat(d,i){
return format(d,i)
}
function init(){
y_axis_root = svg.append("g")
.attr("class", "y axis")
// I modified your example to move the axis to a visible part of the screen
.attr("transform", "translate(150,0)")
.call(y_axis)
}
// custom formatting functions:
function toTerra(d) { return (Math.round(d/10000000000)/100) + "T" }
function toGiga(d) { return (Math.round(d/10000000)/100) + "G" }
function toMega(d) { return (Math.round(d/10000)/100) + "M" }
function toKilo(d) { return (Math.round(d/10)/100) + "k" }
// the factor is just for testing and not needed if based on real world data
function update(factor){
factor = (factor) || 0.1;
yStackMax*=factor
decimals = countDecimals(yStackMax)
console.log("yStackMax decimals:",decimals, factor)
if (yStackMax < maxTicks) {
format = defaultFormat
y_axis.ticks(minTicks)
}
else {
y_axis.ticks(maxTicks)
if (decimals < 3 ) format = defaultFormat
else if(decimals < 6 ) format = toKilo
else if(decimals < 9 ) format = toMega
else if(decimals < 12) format = toGiga
else format = toTerra
}
y_inverted.domain([yStackMax, 0]);
y_axis_root.transition()
.duration(interval)
.ease("linear")
.call(y_axis);
}
init()
setTimeout(update, 200)
setTimeout(update, 400)
setTimeout(update, 600)
You can try it together with this html snippet:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js"></script>
</head>
<body>
<div><svg id="svg" width="200" height="300"></svg></div>
<script src="axis.js"></script>
<button id="button1" onclick="update(10)">+</button>
<button id="button2" onclick="update(0.1)">-</button>
</body>
</html>
I know it is a bit off topic but I usually like to provide running examples/solutions. Regard the additional formatting stuff as a bonus to the actual problem.
If you ask for a certain number of ticks (via axis.ticks() ) then d3 will try to give you that many ticks - but will try to use pretty values. It has nothing to do with your data.
Your solutions are to use tickFormat, as you did, to round all the values to integer values, only ask for one tick as Juve answered, or explicitly set the tick values using axis.tickValues([...]) which would be pretty easy used in conjunction with d3.range
rangeRound will not help in this case because it relates to the output range of the scale, which in this case is the pixel offset to plot at: between 0 and height.
Going off of Superboggly's answer, this is what worked for me. First I got the max (largest) number from the y domain using y.domain().slice(-1)[0] and then I built an array of tick values from that using d3.range()...
var y_max = y.domain().slice(-1)[0]
var yAxis = d3.svg.axis()
.scale(y)
.tickValues(d3.range(y_max+1))
.tickFormat(d3.format(",.0f"))
Or just let the ticks as they are and "hide" decimal numbers
d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5).tickFormat(function(d) {
if (d % 1 == 0) {
return d3.format('.f')(d)
} else {
return ""
}
});
Here is the code:
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));

Categories

Resources