d3 mapping numbers with scale - d3.js

If I set scales so points are in scale on the svg with:
d3.scale.linear().domain(xDomain).range([0, width]);
How can I get the unscaled number of the mouse x position?
Eg.
xPositions = [1,7,10]
7 is # x = ~300 on an svg of width 500
How would I map 300 to 7 based on arbitrary data?

You can use the scale.invert function for the inverse mapping:
var xScale = d3.scale.linear().domain(xDomain).range([0, width]);
var xDomainPos = [1, 7, 10];
var xRangePos = xDomainPos.map(function (d) { return xScale(d); });
var xNewDomainPos = xRangePos.map(function (d) { return xScale.invert(d); });

Related

Violin plot in d3

I need to build a violin point with discrete data points in d3.
Example:
I am not sure how to align the center for each value on X axis. The default behavior will overlay all the points with same X and Y value, however I would like the points to be offset while being center aligned e.g. 5.1 has 3 values in control group and 4.5 has 2 values, all center aligned. It is easy to do so for either right or left aligned by doing a transformation of each point by a specified amount. However, the center alignment seems to be quite hacky.
A hacky way would be to manually transform the X value by maintaining a couple of arrays to see whether this is the first, even or odd number of element and place it according my specifying the value. Is there a proper way to handle this?
The only example of violin plot in d3 I found was here - which implements a probability distribution rather than the discrete values which I require.
"A hacky way would be to manually transform the X value by maintaining a couple of arrays" - that's pretty much the way most d3 layouts work :-) . Discretise your data set by the y value (weight), keeping a total of the data points in each discrete group and a group index for each datum. Then use those to calculate offsets x-ways and the rounded y-value.
See https://jsfiddle.net/n444k759/4/
// below code assumes a svg and g group element are present (they are in the jsfiddle)
var yscale = d3.scale.linear().domain([0,10]).range([0,390]);
var xscale = d3.scale.linear().domain([0,2]).range ([0,390])
var color = d3.scale.ordinal().domain([0,1]).range(["red", "blue"]);
var data = [];
for (var n = 0; n <100; n++) {
data.push({weight: Math.random() * 10.0, category: Math.floor (Math.random() * 2.0)});
}
var groups = {};
var circleR = 5;
var discreteTo = (circleR * 2) / (yscale.range()[1] / yscale.domain()[1]);
data.forEach (function(datum) {
var g = Math.floor (datum.weight / discreteTo);
var cat = datum.category;
var ref = cat+"-"+g;
if (!groups[ref]) { groups[ref] = 0; }
datum.groupIndex = groups[ref];
datum.discy = yscale (g * discreteTo); // discrete
groups[ref]++;
});
data.forEach (function(datum) {
var cat = datum.category;
var g = Math.floor (datum.weight / discreteTo);
var ref = cat+"-"+g;
datum.offset = datum.groupIndex - ((groups[ref] - 1) / 2);
});
d3.select("svg g").selectAll("circle").data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return 50 + xscale(d.category) + (d.offset * (circleR * 2)); })
.attr("r", circleR)
.attr("cy", function(d) { return 10 + d.discy; })
.style ("fill", function(d) { return color(d.category); })
;
The above example discretes into groups according to the size of the display and the size of the circle to display. You might want to discrete by a given interval and then work out the size of circle from that.
Edit: Updated to show how to differentiate when category is different as in your screenshot above

Logarithmic time scale

How to make a logarithmic datetime scale in D3?
a simple time scale is like this:
d3.time.scale()
.domain([new Date(2014, 0, 1), new Date()])
.range([0, 500])
and a simple log scale is like:
d3.scale.log()
.domain([new Date(2014, 0, 1), new Date()])
.rangeRound([0, 500])
.base(10)
Tried to chain their syntax in a various ways with no effect.
Chart will position users by last login date. Range will be about one year. If we space data linearly, most users will collide during last days/hours. With logarithm we can zoom last hours.
Solution could be by interactive zoom or several charts. But goal here is to make single static chart with nonlinear overview of year.
One alternative could be to convert datetime to "days from now", a number. It would work for data. But then I wouldn't know how to label axis ticks like "01-01-2014"...
Something like the below seems to fool d3js into thinking it has a real scale object. It should make a good starting point:
var xt = d3.scaleUtc()
.domain([start, now])
.range([1, width])
var xp = d3.scalePow()
.exponent(2)
.domain([1, width])
.range([0, width])
// Fool d3js into thinking that it is looking at a scale object.
function x_copy() {
var x = function(t) { return xp(xt(t)) }
x.domain = xt.domain
x.range = xp.range
x.copy = x_copy
x.tickFormat = xt.tickFormat
x.ticks = xt.ticks
return x
}
x = x_copy()
var xAxis = d3.axisBottom(x)
Create two scales and use one after the other. First use the time scale and than the log or pow scale.
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale()
.range([0,width]);
var xLog = d3.scale.pow().exponent(4)
.domain([1,width])
.range([0,width]);
than I'm using .forEach to get the linear points:
x.domain(d3.extent(data, function(d) { return parseDate(d.start); }));
data.forEach(function(d) {
d.start = x(parseDate(d.start));
});
when I'm drawing the objects I add the log scale:
.attr('cx', function (d) { return xLog(d.start)})

Creating unique scales for parallel coordinates using d3 (multivariate)

I'm trying to find a way to assign different scale max/min to different columns of my data using parallel coordinates. I've adopted code from http://bl.ocks.org/jasondavies/1341281 but do not want to use d3.extent. I've tried different methods (for loops, if statements) but d3 doesn't seem to like that.
Any ideas for how I should approach this?
d3.json("HW3/scores.json", function(error, scores){
x.domain(dimensions = d3.keys(scores[0]).filter(function(d){
return d != "Country" && (y[d] = d3.scale.linear()
.domain(d3.extent(scores, function(p) { return +p[d]; }))
.range([height, 0]));
}));
I'm trying to change the domains of 6 scales (pulled from scores.json, all numbers in column format) to be unique to the data type:
percent (columns 1 and 6) should be from 1 - 100,
score (columns 3 and 4) should be from 200 - max of both score
columns,
hours (columns 2 and 5) should be from 0 - max of both hour columns.
Please and Thank You for ANY help you can provide to a d3 n00b.
You can simply remove the code that creates the scales from the filter function and create the y scales manually according to your specification:
x.domain(dimensions = d3.keys(data[0]).filter(function(d) {
return d != "name";
}));
y['percent 1'] = d3.scale.linear()
.domain([1, 100])
.range([height, 0]));
y['hours 1'] = d3.scale.linear()
.domain([0, d3.max(data, function(p) { return Math.max(+p['hours 1'], +p['hours 2']); }])
.range([height, 0]));
// etc

How can I get the D3.js axis ticks and positions as an array?

I usually place my axis ticks on the svg using this:
d3.svg.axis().scale(xScale(width)).ticks(4)
Is it possible to get these tick values and their svg coordinates so I can use a custom axis outside the svg using d3.svg.axis() ?
Yes, xScale.ticks(4) should give you the actual tick points as values, and you can pipe those back through your xScale to the the X position. You can also just pull the tick points back from the generated elements after you apply the axis to an actual element:
var svg = d3.select("svg");
var scale = d3.scale.linear()
.range([20, 280])
.domain([0, 100])
var axis = d3.svg.axis().scale(scale).orient("bottom").ticks(9);
// grab the "scale" used by the axis, call .ticks()
// passing the value we have for .ticks()
console.log("all the points", axis.scale().ticks(axis.ticks()[0]));
// note, we actually select 11 points not 9, "closest guess"
// paint the axis and then find its ticks
svg.call(axis).selectAll(".tick").each(function(data) {
var tick = d3.select(this);
// pull the transform data out of the tick
var transform = d3.transform(tick.attr("transform")).translate;
// passed in "data" is the value of the tick, transform[0] holds the X value
console.log("each tick", data, transform);
});
jsbin
In d3 v4 I ended up just parsing the rendered x values from the tick nodes
function parseX(transformText) {
let m = transformText.match(/translate\(([0-9\.]*)/);
let x = m[1];
if (x) {
return parseFloat(x);
}
}

how do you draw linear line in scatter plot with d3.js

I am looking to implement ggplot2 type of graphs using d3.js library for interactivey purpose. I love ggplot2 but users are interested in interactive graphs. I've been exploring d3.js library and there seems to be lots of different graph capability but I really did not see any statistical graphs like linear line, forecast etc. Given a scatter plot, is it possible to also add linear line to the graph.
I have this sample script that draws scatter plot. How would I add linear line to this graph in d3.js?
// data that you want to plot, I've used separate arrays for x and y values
var xdata = [5, 10, 15, 20],
ydata = [3, 17, 4, 6];
// size and margins for the chart
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 960 - margin.left - margin.right
, height = 500 - margin.top - margin.bottom;
// x and y scales, I've used linear here but there are other options
// the scales translate data values to pixel values for you
var x = d3.scale.linear()
.domain([0, d3.max(xdata)]) // the range of the values to plot
.range([ 0, width ]); // the pixel range of the x-axis
var y = d3.scale.linear()
.domain([0, d3.max(ydata)])
.range([ height, 0 ]);
// the chart object, includes all margins
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
// the main object where the chart and axis will be drawn
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
// draw the graph object
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(ydata) // using the values in the ydata array
.enter().append("svg:circle") // create a new circle for each value
.attr("cy", function (d) { return y(d); } ) // translate y value to a pixel
.attr("cx", function (d,i) { return x(xdata[i]); } ) // translate x value
.attr("r", 10) // radius of circle
.style("opacity", 0.6); // opacity of circle
To add a line to your plot, all that you need to do is to append some line SVGs to your main SVG (chart) or to the group that contains your SVG elements (main).
Your code would look something like the following:
chart.append('line')
.attr('x1',x(10))
.attr('x2',x(20))
.attr('y1',y(5))
.attr('y2',y(10))
This would draw a line from (10,5) to (20,10). You could similarly create a data set for your lines and append a whole bunch of them.
One thing you might be interested in is the SVG path element. This is more common for lines than drawing one straight segment at a time. The documentation is here.
On another note you may find it easier to work with data in d3 if you create it all as one object. For example, if your data was in the following form:
data = [{x: 5, y:3}, {x: 10, y:17}, {x: 15, y:4}, {x: 20, y:6}]
You could use:
g.selectAll("scatter-dots")
.data(ydata) // using the values in the ydata array
.enter().append("svg:circle") // create a new circle for each value
.attr("cy", function (d) { return y(d.y); } ) //set y
.attr("cx", function (d,i) { return x(d.x); } ) //set x
This would eliminate potentially messy indexing if your data gets more complex.
UPDATE: Here is the relevant block: https://bl.ocks.org/HarryStevens/be559bed98d662f69e68fc8a7e0ad097
I wrote this function to calculate a linear regression from data, formatted as JSON.
It takes 5 parameters:
1) Your data
2) The column name of the data plotted on your x-axis
3) The column name of the data plotted on your y-axis
4) The minimum value of your x-axis
5) The minimum value of your y-axis
I got the formula for calculating a linear regression from http://classroom.synonym.com/calculate-trendline-2709.html
function calcLinear(data, x, y, minX, minY){
/////////
//SLOPE//
/////////
// Let n = the number of data points
var n = data.length;
var pts = [];
data.forEach(function(d,i){
var obj = {};
obj.x = d[x];
obj.y = d[y];
obj.mult = obj.x*obj.y;
pts.push(obj);
});
// Let a equal n times the summation of all x-values multiplied by their corresponding y-values
// Let b equal the sum of all x-values times the sum of all y-values
// Let c equal n times the sum of all squared x-values
// Let d equal the squared sum of all x-values
var sum = 0;
var xSum = 0;
var ySum = 0;
var sumSq = 0;
pts.forEach(function(pt){
sum = sum + pt.mult;
xSum = xSum + pt.x;
ySum = ySum + pt.y;
sumSq = sumSq + (pt.x * pt.x);
});
var a = sum * n;
var b = xSum * ySum;
var c = sumSq * n;
var d = xSum * xSum;
// Plug the values that you calculated for a, b, c, and d into the following equation to calculate the slope
// m = (a - b) / (c - d)
var m = (a - b) / (c - d);
/////////////
//INTERCEPT//
/////////////
// Let e equal the sum of all y-values
var e = ySum;
// Let f equal the slope times the sum of all x-values
var f = m * xSum;
// Plug the values you have calculated for e and f into the following equation for the y-intercept
// y-intercept = b = (e - f) / n = (14.5 - 10.5) / 3 = 1.3
var b = (e - f) / n;
// return an object of two points
// each point is an object with an x and y coordinate
return {
ptA : {
x: minX,
y: m * minX + b
},
ptB : {
y: minY,
x: (minY - b) / m
}
}
}

Resources