Folks-
I've been trying to set the d3.scale.linear.domain x axis value manually, using Mike Bostock's simple bar chart: http://bl.ocks.org/mbostock/raw/2368837/
var x = d3.scale.linear()
.domain([-100, 200]) //this line added
.range([0, width])
I would expect this to yield a an x axis with values -100 to 200. Instead it yields -30 to 10, as before, and the chart doesn't change. Any ideas?
Thanks,
RL
The domain is set again inside the d3.tsv callback:
x.domain(d3.extent(data, function(d) { return d.value; })).nice();
If you want to change it, you need to delete that line.
Related
I am new to D3.js, pardon me if my understanding is wrong.
I have an equation for a straight line in a log-log plot, Log(Y)=Log(C) + Log(X), C is constant and user defined.
Is there a way to draw the straight line in D3 purely from the equation?
Thank you.
No this isn't possible exactly as you'd like in D3. D3 is less about mathmatical calculation & visualization compared to other tools (R, MatLab) and is more about binding data sets to DOM and handling animation between data sets.
That being said, if you calculate the X and Y values for the equation then you can plot those values easily. I've seen D3 used like this, with input boxes for C and then plotting across a range.
Following your comment here's an example:
const C = 1;
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 1000]); // pixels
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 1000]);
const line = d3.line()
.x(d => xScale(d))
.y(d => yScale(Math.log(C) + Math.log(d)));
const values = [0, 50, 100];
d3.selectAll("path")
.datum(values)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("d", line);
Note that the key to pumping in the equation is defining how to generate the y value given the x in the line generator, covered by this line:
.y(d => yScale(Math.log(C) + Math.log(d)))
I am using d3 v4 for ploting the graph. And currently the tick text on the x-axis is coming below the axis. and I want that text on above the axis.
//Set the Xaxis scale Range
let x = scaleLinear().rangeRound([0, width]);
let x_axis = axisBottom(x);
x.domain(extent(graphData, function (d) {
return d.weeks;
}));
g.append("g").attr("transform", "translate(0," + height + ")").call(axisBottom(x).ticks(5)).attr("transform", "translate(0, 120)");
so can you help me how to put the tick text above the x-axis.
If you want the ticks on top of the axis, you should use axisTop, instead of axisBottom.
The names are pretty easy to understand and the API is very clear:
d3.axisTop(scale): In this orientation, ticks are drawn above the horizontal domain path.
d3.axisBottom(scale): In this orientation, ticks are drawn below
the horizontal domain path. (emphases mine)
Here is a demo, the first axis uses axisTop, and the second one, below, uses axisBottom:
var svg = d3.select("svg");
var x = d3.scaleLinear().range([20, 280]);
var xAxisTop = d3.axisTop(x)(svg.append("g").attr("transform", "translate(0,50)"))
var xAxisBottom = d3.axisBottom(x)(svg.append("g").attr("transform", "translate(0,100)"))
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
In my d3 line chart, I only want ticks for the plotted data. This proves to be a issue with time stamps though as I get:
d3.js:7651 Error: <g> attribute transform: Expected number, "translate(NaN,0)"..
I thought to convert the strings to numbers in the tickValues array but I can not since it's got a colon. Any ideas?
// Hard coded data
scope.data = [
{date: '12:00', glucoseLevel: 400},
{date: '15:00', glucoseLevel: 200},
{date: '18:00', glucoseLevel: 300},
{date: '23:00', glucoseLevel: 400}
];
var parseDate = d3.timeParse('%I:%M');
scope.data.forEach(function(d) {
d.date = parseDate(d.date);
d.glucoseLevel = +d.glucoseLevel;
});
var x = d3.scaleTime()
.range([0, width]);
var xAxis = d3.axisBottom(x)
.tickValues(['12:00', '15:00', '18:00', '23:00']);
// Add the X Axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
You are specifying X values as times, so you must also specify the X-axis tick values as times.
As you already have the X values in the correct format, you can just write
var xAxis = d3.axisBottom(x)
.tickValues(scope.data.map(function (d) { return d.date; }));
.tickValues() isn't for setting the tick labels, it's for setting where on the axis the ticks appear. If you want the tick labels formatted in some way, specify a formatter using tickFormat, for example:
var xAxis = d3.axisBottom(x)
.tickValues(scope.data.map(function (d) { return d.date; }))
.tickFormat(d3.timeFormat("%H:%M"));
I've used the format string %H:%M instead of %I:%M as %I is hours in the range 01-12 whereas %H uses the 24-hour clock. For consistency I'd recommend changing your time parsing function to d3.timeParse('%H:%M'), although parsing a time with the hours greater than 12 using %I seems to work.
Finally, you'll also need to set the domain of your scale object x, for example:
var x = d3.scaleTime()
.domain([parseDate('12:00'), parseDate('23:00')])
.range([0, width]);
The two values passed to domain are the minimum and maximum X values to use for the axis. I've used the minimum and maximum values of your data, but I could have chosen a different time range (e.g. 00:00 to 24:00) as long as it contained all of your data points.
Im running a simple bar chart using d3.scale.linear() and hardcoding he domain range for this example
I can see in firebug that when aplying attr of width to my div, w_bar appears to be NaN.
Why is that?
var w_bar = d3.scale.linear()
.domain([0, 107525233]) //harcoded
.range(["0px", "290px"]);
var theList = d3.select("#list").selectAll("div")
.data(myJSON);
theList.enter().append("div")
.text(function (d) { return d.v; })
.transition()
.duration(1000)
.attr("width", w_bar); // Why NaN?
theList.exit()
.remove();
Here's the jsfiddle:
http://jsfiddle.net/NAhD9/5/
Well, w_bar is Not a Number, and the width attribute needs to be a number. Hence, NaN.
If you want your width attribute to scale based on the v attribute in your myJSON object, you should say
.attr("width",function (d) {return w_bar(d.v)}).
This is how scales work in d3, they are a function which takes in an argument (some value within the domain of the scale) and returns that value transformed to fit into the range of your scale.
Updated jsFiddle here.
You need to use a function to tell d3 how you want your data to interact with to scale to create an array:
.attr("width", function(d){ return w_bar(d.v); })
This will take all the v attributes from objects making up the myJSON array, scale them with w_bar, and set the width of their corresponding rectangles equal to that value.
I am trying to create a linear color scale for a heatmap. I want to color scale to go through a large set of specific colors, where the first color corresponds to the min of the data and the last color should be given to the max of the data.
I know that I can do this by also giving the domain 17 values in between the min and max, but I do not know how to do this dynamically if the user is able to change the dataset (and thus change the coloring of the heatmap)
In essence I would like to following, but I know it does not work
var colorScale = d3.scale.linear()
.range(["#6363FF", "#6373FF", "#63A3FF", "#63E3FF", "#63FFFB", "#63FFCB",
"#63FF9B", "#63FF6B", "#7BFF63", "#BBFF63", "#DBFF63", "#FBFF63",
"#FFD363", "#FFB363", "#FF8363", "#FF7363", "#FF6364"])
.domain([d3.min(dataset, function(d) {return d;}),
d3.max(dataset, function(d) {return d;})]);
Can anybody please tell me what I need to put into 'domain' to make it work?
EDIT:
I did find something that does what I want. Using R I calculated 256 colors in between the 17 from above with the designer.colors functions and put this into the range. This does give the feeling of a continous color scale
var colorScale = d3.scale.linear()
.range(["#6363FF", "#6364FF", "#6364FF", "#6365FF",
"... several other lines with color codes ..."
"#FF6764", "#FF6564", "#FF6464", "#FF6364"])
.domain(d3.range(1,257));
var quantize = d3.scale.quantile()
.range(d3.range(1,257))
.domain([d3.min(dataset, function(d) {return d;}),
d3.max(dataset, function(d) {return d;})]);
Now I can use the color in this fashion
colorScale(quantize(dataset))
But I'm wondering if this can also be done in less lines of code?
You want to split the problem up. First define a scale for your heatmap that maps 0-1 to your colours. Then define a second (dynamic) scale that maps your dataset to 0-1. You can then combine the scales to paint your shapes.
var colours = ["#6363FF", "#6373FF", "#63A3FF", "#63E3FF", "#63FFFB", "#63FFCB",
"#63FF9B", "#63FF6B", "#7BFF63", "#BBFF63", "#DBFF63", "#FBFF63",
"#FFD363", "#FFB363", "#FF8363", "#FF7363", "#FF6364"];
var heatmapColour = d3.scale.linear()
.domain(d3.range(0, 1, 1.0 / (colours.length - 1)))
.range(colours);
// dynamic bit...
var c = d3.scale.linear().domain(d3.extent(dataset)).range([0,1]);
// use the heatmap to fill in a canvas or whatever you want to do...
canvas.append("svg:rect")
.data(dataset)
.enter()
// snip...
.style("fill", function(d) {
return heatmapColour(c(d));
Plus you can use the d3.extent function to get the min and max of the dataset in one go.
Use a Quantitative Scale plus Color Brewer
// pick any number [3-9]
var numColors = 9;
var heatmapColour = d3.scale.quantize()
.domain(d3.extent(dataset))
.range(colorbrewer.Reds[numColors]);
// use the heatmap to fill in a canvas or whatever you want to do...
canvas.append("svg:rect")
.data(dataset)
.enter()
// snip...
.style("fill", function(d) {return heatmapColour(d);})
Use threshold scales. Here is a quick example:
coffee> d3 = require 'd3'
coffee> color = d3.scale.threshold().domain([5,30,100]).range(["red","orange","green"]);
coffee> color 6
'orange'
coffee> color 3
'red'
coffee> color 33
'green'