I have two overlapping charts. One is a barchart and one is a linechart.
Both have in each data a date (the same date - same count of results) But if I calculate the attr x for each chart I get different results.
var x = d3.time.scale().range([0, width]);
width = 860.
So I debugged very deep in d3js-code and recognized the following:
If I call x (x(d.value) and step into it I came to the following code:
function d3_uninterpolateNumber(a, b) {
b = b - (a = +a) ? 1 / (b - a) : 0;
return function(x) {
return (x - a) * b;
};
}
I saw that x and a have the same value (x is a date (Tue Jul 2 00:00:00 UTC+0200 2013) and a are ticks (1372716000000 --> the ticks of the first date in my data) in case of 2nd of July.
But b has different values. So I have no idea what b is.
In case of barchart it is 3.7947783849423196e-10 and in case of LineChart it's 3.7947783849423196e-10
So the result of x(d.value) is different, but it shouldn't be.
Has anybody an idea?
I try to get a fiddle tomorrow, but maybe somebody has the answer without js-fiddle.
thx in advance
©a-x-i
problem solved.
To render the barchart and linechart I have two different functions. In each function I have called the x.domain.
In LineChart only:
x.domain([d3.min(data, function (d) { return d.xValue; }), d3.max(data, function (d) { return d.xValue; })]);
But in BarChart:
var xMax = d3.max(data, function (d) { return d.xValue; });
xMax = new Date(xMax.toString()); // otherwise data[<last>].xValue is changed as well!
if (ChartHandler.SelectedAggregationPeriod == ChartHandler.AggregationPeriod.month) {
xMax.setTime(xMax.getTime() + 12 * 60 * 60 * 1000); //+12h
}
var xMin = d3.min(data, function (d) { return d.xValue; });
x.domain([xMin, xMax]);
that's it....
©a-x-i
Related
I have created a bar chart in dc.js:
d3.csv("input.csv", function (error, data) {
data.forEach(function (x) {
x.x = +x.x;
});
var ndx = crossfilter(data),
dim1 = ndx.dimension(function (d) {
return Math.floor(parseFloat(d['x']) * 10) / 10;
});
var group1 = dim1.group(),
chart = dc.barChart("#barchart");
chart
.width(500)
.height(200)
.dimension(dim1)
.group(group1)
.x(d3.scale.linear().domain([0,3]))
.elasticY(true)
.barPadding([0.4])
chart.xAxis().tickFormat(function(d) {return d});
chart.yAxis().ticks(10);
});
But the number of bars is low. I want to increase the number of bars displayed to 12.
How can I choose the number of bars?
These lines determine the number of bins:
dim1 = ndx.dimension(function (d) {
return Math.floor(parseFloat(d['x']) * 10) / 10;
});
var group1 = dim1.group(),
What you are doing here is rounding down to the previous tenth (0.1). Thus any rows with x equal to 1.12, 1.16, 1.19 will be counted in the 1.1 bin, etc.
If you want an exact number of bins, you'll have to determine the range of x and divide that by the number of bins you want. If you don't need anything that exact, you could just fiddle with the number 10 there until you get what you want.
For example, changing it to
dim1 = ndx.dimension(function (d) {
return Math.floor(parseFloat(d['x']) * 20) / 20;
});
var group1 = dim1.group(),
will give you twice as many bins, because it will round down to the previous twentieth (0.05).
So 1.12 would round to 1.10; and 1.16, 1.19 would round to 1.15, etc.
BTW, parseFloat is unnecessary, because you have already converted x to number with x.x = +x.x
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
I'm trying to make a generic cross filter that can take in a csv and build a dashboard. Here are working examples:
https://ubershmekel.github.io/gfilter/?dl=https://ubershmekel.github.io/csvData/spent.csv
https://ubershmekel.github.io/gfilter/?dl=https://ubershmekel.github.io/csvData/Sacramentorealestatetransactions.csv
But for some reason the flight data is slow and unresponsive. Compare these 2 which analyze the same data:
https://ubershmekel.github.io/gfilter/?dl=https://ubershmekel.github.io/csvData/flights-3m.csv
https://github.com/square/crossfilter
I think it's because the histogram binning is too detailed but I can't find a good way to tweak that in the api reference. #gordonwoodhull mentioned:
If the binning is wrong you really want to look at the way you've set up crossfilter - dc.js just uses what it is given.
How do I tweak the binning of crossfilter? I've tried messing with the xUnits, dimension and group rounding to no avail.
This is the problem code I suspect is slow/wrong:
var dim = ndx.dimension(function (d) { return d[propName]; });
if (isNumeric(data[0][propName])) {
var theChart = dc.barChart("#" + chartId);
var countGroup = dim.group().reduceCount();
var minMax = d3.extent(data, function (d) { return +d[propName] });
var min = +minMax[0];
var max = +minMax[1];
theChart
.width(gfilter.width).height(gfilter.height)
.dimension(dim)
.group(countGroup)
.x(d3.scale.linear().domain([min, max]))
.elasticY(true);
theChart.yAxis().ticks(2);
You can adjust binning by passing a function that adjusts values to the group() method. For example, this group would create integer bins:
var countGroup = dim.group(function (v) { return Math.floor(v); });
And this one would create bins of 20 units a piece:
var countGroup = dim.group(function(d) { return Math.floor(d / 20) * 20 });
Factoring out a variable for bin size:
var bin = 20; // or any integer
var countGroup = dim.group(function(d) { return Math.floor(d / bin) * bin });
If you use binning, you'll also likely want your bars to be of a width matching your bin size. To do so, add a call to xUnits() on your bar chart. xUnits() sets the number of points on the axis:
.xUnits(function(start, end, xDomain) { return (end - start) / bin; })
See the documentation for crossfilter dimension group(), dc.js xUnits()
You can check out the results at:
https://ubershmekel.github.io/gfilter/?dl=testData/Sacramentorealestatetransactions.csv
This worked for me. I had to avoid 3 pitfalls: the group() function needed to round to the bar locations, xUnits needed the amount of bars, and making the domain (x axis) show the max value.
var numericValue = function (d) {
if (d[propName] === "")
return NaN;
else
return +d[propName];
};
var dimNumeric = ndx.dimension(numericValue);
var minMax = d3.extent(data, numericValue);
var min = minMax[0];
var max = minMax[1];
var barChart = dc.barChart("#" + chartId);
// avoid very thin lines and a barcode-like histogram
var barCount = 30;
var span = max - min;
lastBarSize = span / barCount;
var roundToHistogramBar = function (d) {
if (isNaN(d) || d === "")
d = NaN;
if (d == max)
// This fix avoids the max value always being in its own bin (max).
// I should figure out how to make the grouping equation better and avoid this hack.
d = max - lastBarSize;
var res = min + span * Math.floor(barCount * (d - min) / span) / barCount;
return res;
};
var countGroup = dimNumeric.group(roundToHistogramBar);
barChart.xUnits(function () { return barCount; });
barChart
.width(gfilter.width).height(gfilter.height)
.dimension(dimNumeric)
.group(countGroup)
.x(d3.scale.linear().domain([min - lastBarSize, max + lastBarSize]).rangeRound([0, 500]))
.elasticY(true);
barChart.yAxis().ticks(2);
Is there any way to find inversion of ordinal scale?
I am using string value on x axis which is using ordinal scale and i on mouse move i want to find inversion with x axis to find which string is there at mouse position?
Is there any way to find this?
var barLabels = dataset.map(function(datum) {
return datum.image;
});
console.log(barLabels);
var imageScale = d3.scale.ordinal()
.domain(barLabels)
.rangeRoundBands([0, w], 0.1);
// divides bands equally among total width, with 10% spacing.
console.log("imageScale....................");
console.log(imageScale.domain());
.
.
var xPos = d3.mouse(this)[0];
xScale.invert(xPos);
I actually think it doesn't make sense that there isn't an invert method for ordinal scales, but you can figure it out using the ordinal.range() method, which will give you back the start values for each bar, and the ordinal.rangeBand() method for their width.
Example here:
http://fiddle.jshell.net/dMpbh/2/
The relevant code is
.on("click", function(d,i) {
var xPos = d3.mouse(this)[0];
console.log("Clicked at " + xPos);
//console.log(imageScale.invert(xPos));
var leftEdges = imageScale.range();
var width = imageScale.rangeBand();
var j;
for(j=0; xPos > (leftEdges[j] + width); j++) {}
//do nothing, just increment j until case fails
console.log("Clicked on " + imageScale.domain()[j]);
});
I found a shorter implementation here in this rejected pull request which worked perfectly.
var ypos = domain[d3.bisect(range, xpos) - 1];
where domain and range are scale domain and range:
var domain = x.domain(),
range = x.range();
I have in the past reversed the domain and range when this is needed
> var a = d3.scale.linear().domain([0,100]).range([0, w]);
> var b = d3.scale.linear().domain([0,w]).range([0, 100]);
> b(a(5));
5
However with ordinal the answer is not as simple. I have checked the documentation & code and it does not seem to be a simple way. I would start by mapping the items from the domain and working out the start and stop point. Here is a start.
imageScale.domain().map(function(d){
return {
'item':d,
'start':imageScale(d)
};
})
Consider posting your question as a feature request at https://github.com/mbostock/d3/issues?state=open in case
There is sufficient demand for such feature
That I haven't overlooked anything or that there is something more hidden below the documentation that would help in this case
If you just want to know which mouse position corresponds to which data, then d3 is already doing that for you.
.on("click", function(d,i) {
console.log("Clicked on " + d);
});
I have updated the Fiddle from #AmeliaBR http://fiddle.jshell.net/dMpbh/17/
I recently found myself in the same situation as OP.
I needed to get the inverse of a categorical scale for a slider. The slider has 3 discrete values and looks and behaves like a three-way toggle switch. It changes the blending mode on some SVG elements. I created an inverse scale with scaleQuantize() as follows:
var modeArray = ["normal", "multiply", "screen"];
var modeScale = d3.scalePoint()
.domain(modeArray)
.range([0, 120]);
var inverseModeScale = d3.scaleQuantize()
.domain(modeScale.range())
.range(modeScale.domain());
I feed this inverseModeScale the mouse x-position (d3.mouse(this)[0]) on drag:
.call( d3.drag()
.on("start.interrupt", function() { modeSlider.interrupt(); })
.on("start drag", function() { inverseModeScale(d3.mouse(this)[0]); })
)
It returns the element from modeArray that is closest to the mouse's x-position. Even if that value is out of bounds (-400 or 940), it returns the correct element.
Answer may seem a bit specific to sliders but posting anyway because it's valid (I think) and this question is in the top results for " d3 invert ordinal " on Google.
Note: This answer uses d3 v4.
I understand why Mike Bostock may be reluctant to include invert on ordinal scales since you can't return a singular true value. However, here is my version of it.
The function takes a position and returns the surrounding datums. Maybe I'll follow up with a binary search version later :-)
function ordinalInvert(pos, scale) {
var previous = null
var domain = scale.domain()
for(idx in domain) {
if(scale(datum[idx]) > pos) {
return [previous, datum[idx]];
}
previous = datum[idx];
}
return [previous, null];
}
I solved it by constructing a second linear scale with the same domain and range, and then calling invert on that.
var scale = d3.scale.ordinal()
.domain(domain)
.range(range);
var continousScale = d3.scale.linear()
.domain(domain)
.range(range)
var data = _.map(range, function(i) {
return continousScale.invert(i);
});
You can easily get the object's index/data in callback
.on("click", function(d,i) {
console.log("Clicked on index = " + i);
console.log("Clicked on data = " + d);
// d == imageScale.domain()[1]
});
d is the invert value itself.
You don't need to use obj.domain()[index] .
I am looking for a simple way to gradually change the value of a number displayed as svg text with d3.
var quality = [0.06, 14];
// qSVG is just the main svg element
qSVG.selectAll(".txt")
.data(quality)
.enter()
.append("text")
.attr("class", "txt")
.text(0)
.transition()
.duration(1750)
.text(function(d){
return d;
});
Since text in this case is a number i hope there is an easy way to just increment it to the end of the transition.
Maybe someone of you has an idea.
Cheers
It seems d3JS already provides a suitable function called "tween"
Here is the important part of the code example.
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d),
prec = (d + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round;
};
});
http://jsfiddle.net/c5YVX/280/
You can increment them over a given time interval from any start to any end value regardless their number precision.
Its implemented for SVG text but of course works the same for standard html text.
If you only need the plain tween function for rounded numbers, it gets a bit more leightweight.
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d),
return function(t) {
this.textContent = Math.round(i(t));
};
});