I've been at this for a few days now and I can't get this chart to obey panning bounds. Initially, the data could be pulled off the page both negatively and positively, but I've been able to stop the negative by following this blog post. I'll paste what I believe is the relevant code here, but the file is way to long to include.
The chart in question is an elevation chart made up of concatenated area objects that are colored according to their gradient.
There's a commented out line that is the one giving trouble. I've put question marks in place of what is supposed to be a max bound. For some reason, I can't find the max bound of the data area.
Here's a Plunkr
// Set up the size of the chart relative to the div
var x = d3.scale.linear().range([0, (width-80)]);
var y = d3.scale.linear().range([height, 0]);
var y1 = d3.scale.linear().range([height, 0]);
// Define the look of the axis
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5);
var yAxisRight = d3.svg.axis().scale(y1).orient("left").ticks(5);
// Areas are segments of the chart filled with color
var area = d3.svg.area()
.x(function(d) { return x(d.distance); })
.y0(height)
.y1(function(d) { return y(d.elevation); });
// Functions for handling zoom events
var gradientZoomListener = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", gradientZoomHandler);
function gradientZoomHandler() {
var t = gradientZoomListener.translate(),
tx = t[0],
ty = t[1];
tx = Math.min(tx, 0);
// tx = Math.max(tx, ??);
gradientZoomListener.translate([tx,ty])
gradientChart.select(".x.axis").call(xAxis);
gradientChart.select(".y.axis").call(yAxis);
gradientChart.selectAll('.area').attr('d', area);
}
The most intuitive way to do it in this case is to check what the scale maps the bounds of the input domain to, rather than checking the pixel values. The idea is that the lower bound of the domain should map to 0 (the lower bound of the output range) or less, and the upper bound to the upper bound of the output range or more.
If the lower bound maps to less than 0, the value is to the left of the graph, i.e. the lowest shown value is more than the bound. If it is larger than 0, there must be a gap between the y axis and the first value. Similarly for the upper bound, if it maps to less than the upper bound of the output range, there must be a gap between it and the end of the graph.
In code, this looks as follows.
if(x(xExtent[0]) > 0) {
tx = 0;
} else if(x(xExtent[1]) < x.range()[1]) {
tx -= x(xExtent[1]) - x.range()[1];
}
The only non-trivial thing is the adjusting of the translation value if there's a gap between the largest value and the end of the graph. The size of this gap is the difference between where the largest input value is projected to and the largest output value. The gap is then subtracted from the current translation value to close it.
Complete example here. Note that I've moved some code around to get access to the values which are only known when the data has been read.
It works the same way for the y axis.
Related
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());
I'm using chart.js but on some and only some of the graphs it creates the y-axis scale goes from 0-100 when a more appropriate scale might be 80-100. This means all the lines are bunched up at the top.
You can see what I mean if you visit mbi.dajubox.com and select '14 days' under waiting times. When the results come up beneath click the first entry (Calderdale And Huddersfield NHS Foundation Trust) and the graph appears. But the lines are bunched at the top.
If I go down to number 15 though (Stockport NHS Foundation Trust) it scales the axis ok.
The code that generates them is the same
var ctx = document.getElementById("myChart_"+provID).getContext("2d");
var myLineChart = new Chart(ctx).Line(data, {bezierCurve: false, multiTooltipTemplate: "<%= datasetLabel %> - <%= value %>"});
Can any one help me out?
This is because in your data you receive null values to display in the chart. Chartjs's min function is just a wrapper for the Math.min which will treat null as 0.
A fix for this can be to override the helper function calculateScaleRange
Just declare this after you have Chart.js (or apply the small change straight to your Chart.js)
Chart.helpers.calculateScaleRange = function (valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
//Set a minimum step of two - a point at the top of the graph, and a point at the base
var minSteps = 2,
maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
skipFitting = (minSteps >= maxSteps);
var maxValue = Chart.helpers.max(valuesArray),
minValue = Chart.helpers.min(valuesArray.map(function(value){
//using map to create a new array where all nulls are mapped to Infinity so they do not pull the result down to 0
return value === null ? Infinity: value;
}));
................ //lots more code that is part of calculateScaleRange
here is a full example http://jsfiddle.net/leighking2/L9kLxpe1/
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?
I want the actual value of each bar displayed on top in the way it's shown here
I am trying this on multi bar chart.
Can't find reference anywhere.
Duplicate of How to display values in Stacked Multi-bar chart - nvd3 Graphs
There is a fix you can implement yourself at https://gist.github.com/topicus/217444acb4204f364e46
EDIT: Copied the code if the github link gets removed:
// You need to apply this once all the animations are already finished. Otherwise labels will be placed wrongly.
d3.selectAll('.nv-multibar .nv-group').each(function(group){
var g = d3.select(this);
// Remove previous labels if there is any
g.selectAll('text').remove();
g.selectAll('.nv-bar').each(function(bar){
var b = d3.select(this);
var barWidth = b.attr('width');
var barHeight = b.attr('height');
g.append('text')
// Transforms shift the origin point then the x and y of the bar
// is altered by this transform. In order to align the labels
// we need to apply this transform to those.
.attr('transform', b.attr('transform'))
.text(function(){
// Two decimals format
return parseFloat(bar.y).toFixed(2);
})
.attr('y', function(){
// Center label vertically
var height = this.getBBox().height;
return parseFloat(b.attr('y')) - 10; // 10 is the label's magin from the bar
})
.attr('x', function(){
// Center label horizontally
var width = this.getBBox().width;
return parseFloat(b.attr('x')) + (parseFloat(barWidth) / 2) - (width / 2);
})
.attr('class', 'bar-values');
});
});
Apparently this doesn't exist yet. There is an issue (https://github.com/novus/nvd3/issues/150) that was closed because this is (apparently) hard to implement.
I am not sure what you have tried so fat, but the example in here is pretty straight forward.
.showValues(true) pretty much does the trick.
Hope it helps.
I've got a script that creates a gradient by shading cells based on their distance from a set of coordinates. What I want to do is make the gradient circular rather than the diamond shape that it currently is. You can see an en example here: http://jsbin.com/uwivev/9/edit
var row = 5, col = 5, total_rows = 20, total_cols = 20;
$('table td').each(function(index, item) {
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// Percentage based on location, always using positive numbers
var percentage_row = Math.abs(current_row-row)/total_rows;
var percentage_col = Math.abs(current_col-col)/total_cols;
// I'm thinking this is what I need to change to achieve the curve I'm after
var percentage = (percentage_col+percentage_row)/2;
$(this).find('div').fadeTo(0,percentage*3);
});
If you can give me hand with the right maths function to get the curve I'm after that would be great! Thanks!
Darren
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// distance away from the bright pixel
var dist = Math.sqrt(Math.pow(current_row - row, 2) + Math.pow(current_col - col, 2))
// do something with dist, you might change this
var percentage = dist / total_cols;
$(this).find('div').fadeTo(0,percentage*3);
You can use the square of the distance formula:
((current_row - row)*(current_row - row) + (current_col - col)*(current_col - col))
then multiply it by whatever scale factor you need.
Here is a circle drawing procudure I wrote many moons ago in Pascal which you can use as pseudo code to understand how to color pixels at the radius from an (X,Y) and work your way in. Multiple shrinking circles should cover the entire area you need. The code also gives you the formula for accessing the radius.
PROCEDURE DrawCircle(X,Y,Radius:Integer);
VAR A,B,Z:LongInt;
BEGIN
Z:=Round(Sqrt(Sqr(LongInt(Radius))/2));
FOR A:=Z TO Radius DO
FOR B:=0 TO Z DO
IF Radius=Round(Sqrt(A*A+B*B)) THEN
BEGIN
PutPixel(X+A,Y+B,8);
PutPixel(X+A,Y-B,9);
PutPixel(X-A,Y+B,10);
PutPixel(X-A,Y-B,11);
PutPixel(X+B,Y+A,12);
PutPixel(X+B,Y-A,13);
PutPixel(X-B,Y+A,14);
PutPixel(X-B,Y-A,15);
END;
END;
NB: "Longint()" is a compiler typecast for larger numeric computations so don't let that worry you.
NB: Inner-most brackets are executed first.