How to set Chart dimensions to change as per given data - javascript

Hi i am new to d3js so i am trying out simple example codes
so here's the code
d3.selectAll("div")
.data([100,180,200,400,450])
.transition()
.duration(2000)
.style("width", function(d) { return d +"px"; });
http://jsfiddle.net/YjED4/
what i want to achieve is set the size of the chart dynamically to the length of the max data that i am passing i.e charts max length should be on a max scale of 0-500 given the max data i have passed is 450
the problem is when i pass data like
.data([1,18,20,40,4])
i get a chart like this
http://jsfiddle.net/NnLU9/
which beats the purpose of the chart.
So any pointers on how to proceed on this will be helpful

D3 is an amazing technology and I hope you stick with it.
The problem with your code is that it does not have a concept of an x-axis or y-axis, your code just has data that change the width by pixels. Without an x or y axis your chart will not scale properly.
You need something like this in your code:
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
The code snippet states that the values are from 0 to the max of the data set, and the pixel range is from 0 pixels to 420 pixels. So if your max is 40, then 40 will map to 420 pixels.
Then the following will scale your chart:
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return x(d) + "px"; })
.text(function(d) { return d; });
notice the code that says return x(d) + 'px', what is does is when you pass a value let's say '20' into the function x(d) it will scale it to whatever you had scaled your x-axis to. So '20' will be 210 pixels in the function above, '0' would be 0 pixels, and '40' would be '420' pixels.
This is the example I used to get started in d3, I think Mike Bostock can explain it better than anyone: http://bost.ocks.org/mike/bar/
Hope this helps.

Related

nvd3 - Rescale Y axis and show Outlier Data points

I am creating a lineChart with NVD3 that is currently plotting my points correctly. The default behavior of the Y axis automatically scales the ticks on the Y axis. Then when a series is selected or deselected, the Y axis automatically resizes according to the present series.
There are two problems with this:
When the resizing happens, the Y axis flips, making the values decrease as you move up the Y axis.
More importantly, the automatic initial Y axis scale dramatically underestimates the Y axis values, causing many data points (outliers) to not be shown in the visual.
I have played around with Ydomain and forceY to set the min and max values of my data on the Y axis, but then I lose the functionality of the auto-scaling when selecting or deselecting a series.
I would like to have my visual preserve the default behavior of auto-scaling the Y axis when selecting and deselecting series, but also have it not cut the Y axis off early, thus showing all outlier data points.
Any help is much appreciated.
Current JS code:
d3.json('..\\static\\data\\hydrograph_data.json', function(data) { // open more dynamic file path
nv.addGraph( function() {
var getX = function(d) { return d.time_mili }
var getY = function(d) { return d.value }
var getMax = function(d) { return d.max }
var min = d3.min(data, function(d) { return d3.min(d.values, getY)})
var max = d3.max(data, function(d) { return d3.max(d.values, getMax)})
var chart = nv.models.lineChart()
.x( getX ) // this value is stored in miliseconds since epoch (converted in data_format.py with datetime)
.y( getY )
.color(d3.scale.category10().range())
.useInteractiveGuideline(true)
//.yDomain([min, max])
.margin({left: 90})
// .showControls(false)
;
chart.xAxis
.axisLabel(" Date ")
.ticks(5)
.tickFormat(function(d) {
return d3.time.format('%y-%d-%m')(new Date(d))
});
chart.yAxis
//.axisLabel('Discharge (cubic feet per second)')
.ticks(5)
.tickFormat(function(d) { return d3.format(",")(d) + " cfps"});
d3.select('#hydrograph svg')
.datum(data)
.call(chart);
//TODO: Figure out a good way to do this automatically
nv.utils.windowResize(chart.update);
return chart;
});
});
p.s.
I am working on a fiddle, but a sample of my data in the fiddle does not produce the same problems, so it is difficult to recreate without the entire data file present, which I cannot do in jsfiddle.
edit:
temporary solution was to create a dummy value point at the start of the series with value 0 as seen in NatasaPeic's answer to the issue.

D3 axis without using d3.svg.axis() method

I am a beginner in D3.js . I need to draw a graph with axis as per below image.Any working fiddle will help me a lot.
I have checked the below fiddle but not sure how to tweak it as per my requirement.
You can create the scale (which is completely independent from the axis) and use its parameters to create the svg elements for your axis; in your case the horizontal line and a circle + text as ticks.
Specifically, you could use the scale range() to get the horizontal line length; and the scale ticks(n) functions to get n evenly spaced points in the domain of the scale. By passing these points to the scale you get the x coordinate to use for your ticks.
xScale = d3.scale.linear().domain([0.0, 1.0]).range([0, width]);
var axis = svg.append('g').attr('class', 'xaxis');
axis.append('line').attr({
'x1': xScale.range()[0],
'y1': height,
'x2': xScale.range()[1],
'y2': height
});
ticks = axis.selectAll('.ticks')
.data(xScale.ticks(10))
.enter()
.append('g')
.attr('class', 'tick');
ticks.append('circle')
.attr({
'cx': function(d) { return xScale(d); },
'cy': height,
'r': 5
});
ticks.append('text')
.attr({
'x': function(d) { return xScale(d); },
'y': height,
'dy': 20 // Move the text a little under the line
})
.text(function(d) { return d; });
Here is a minimal working JSFiddle: http://jsfiddle.net/LeartS/yGesr/

D3 brushing on grouped bar chart

I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367
I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. I will try to include the relevant bits of code below. The chart is tracking the time to fix broken builds by day and then grouped by portfolio. So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. Any help you can give would be greatly appreciated. Thank you.
// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// Define the X axis
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.tickFormat(dateFormat)
.orient("bottom");
After binding the data...
// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
main_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
mini_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
// Create brush for mini graph
var brush = d3.svg.brush()
.x(mini_x0)
.on("brush", brushed);
After adding the axis's, etc.
// Create the bars
var bar = main.selectAll(".bars")
.data(nested)
.enter().append("g")
.attr("class", function(d) { return d.key + "-group bar"; })
.attr("fill", function(d) { return color(d.key); } );
bar.selectAll("rect").append("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("class", function(d) { return d.portfolio; })
.attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; })
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); })
.attr("y", function(d) { return main_y(d.buildFixTime); })
.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
Here is the brush function (trying several different options)...
function brushed() {
main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());
//main.select("rect")
//.attr("x", function(d) { return d.values; })
//.attr("width", function(d) { return d.values; });
bar.select("rect")
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); });
//.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
//.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
main.select(".x.axis").call(main_xAxis);
}
The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. So the problem is right at the top of the brushing function:
function brushed() {
main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale.
According to the wiki, if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. Ordinal scales don't have an invert() method to convert range values into domain values.
So, you have a few options on how to proceed:
You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand().
You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain) are included in the range returned by brush.extent(). Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles.
Or you can leave the domain of main_x0. be, and change the range instead. By making the range of the graph larger, you space out the bars greater. In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway.
But what should the new range be? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. If you used these values as the range on the main graph, your entire graph would be squished down to just that width. That's the opposite of what you want. What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area.
So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions:
the 20% mark of the new range width is at 0;
the 60% mark of the new range width is at 100.
Therefore,
the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
the start of the new range is at (0 - (20/100)*250) = -50;
the end of the new range is at (-50) + 250 = 200.
Now you could do all the algebra for figuring out this conversion yourself. But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range.
Specifically, we need a linear scale, with its output range set to be the actual range of the plotting area. Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. And from there, we-can resize the other ordinal scale and reposition all the rectangles.
In code:
//Initialization:
var main_xZoom = d3.scale.linear()
.range([0, main_width - 275])
.domain([0, main_width - 275]);
//Brushing function:
function brushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(brush.empty() ?
originalRange:
brush.extent() );
main_x0.rangeRoundBands( [
main_xZoom(originalRange[0]),
main_xZoom(originalRange[1])
], 0.2);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function (d) {
return "translate(" + main_x0(d.date) + ",0)";
})
.attr("width", function (d) {
return main_x1.rangeBand();
})
.attr("x", function (d) {
return main_x1(d.portfolio);
});
main.select("g.x.axis").call(main_xAxis);
}
Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot):
http://fiddle.jshell.net/CjaD3/1/

Manipulating D3 axis labels

I'm working with D3 for the first time and I'm trying to make a parallel coordinate graph. I basically am using this demo. The only real changes I've had is changing the data and changing the far right axis so it has strings instead of numbers as the labels. I do this by using the following:
if(d === "Dog Breed") {
y[d] = d3.scale.ordinal()
.domain(dogData.map(function(p) { return p[d]; }))
.rangePoints([h, 0]); // quantitative color scale
}
Unfortunately, if the dog's breed is too long, the text gets cut off, making it hard to read the label (one has to move the axis in its entirety to read it, but when they let go of it, it goes right back to where it was initially).
My other change were the following:
var m = [30, 10, 10, 10],
w = screen.width - 150, // Make it 150px less than the screen's width.
h = 500 - m[0] - m[2];
The axis label code remains the same at:
// Add an axis and title.
g.append("svg:g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("svg:text")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(String);
Is there any way to avoid the name-being-clipped-thing? Even shifting the graph itself over in its block about 20px would help, but I don't know where the code for that would be...
The fix was to manipulate var m to have more on the lefthand side in m[3].

d3 bar chart is upside down

I have a simple bar chart drawn in d3, with vertical bars: http://jsfiddle.net/philgyford/LjxaV/2/
However, it's drawing the bars down, with the baseline at the top of the chart.
I've read that to invert this, drawing up from the bottom, I should change the range() on the y-axis. So, change this:
.range([0, chart.style('height')]);
to this:
.range([chart.style('height'), 0]);
However, that looks like it's drawing the inverse of the chart - drawing in the space above each of the bars, and leaving the bars themselves (drawn from the bottom) transparent. What am I doing wrong?
Per the d3 basic bar chart :
http://bl.ocks.org/mbostock/3885304
You are correct in inverting the range.
Additionally, your rectangles should be added like this:
.attr('y', function(d) { return y(d.percent); } )
.attr('height', function(d,i){ return height - y(d.percent); });
The x and y coordinates for svg start in the top left. You want the y to start on the bottom. The code below assumes you're appending to some function along the lines of:
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
To make the bar plot act as you desire, set the y attribute to begin at distance data[i] above the axis:
.attr('y', function(d) { return height - d; })
Then, you must make the distance extend the remaining data[i] to the axis.
.attr('height', function(d) { return d; })
And that's it!
Setting the y attribute seems to work:
.attr('y', function(d){ return (height - parseInt(y(d.percent))); })
jsfiddle here

Categories

Resources