I want to set up a d3 quadtree using data which does not have columns labelled x and y, but with some other labels. Reading the quadtree docs I thought I could do it like this:
var data = [{"a":6,"b":99.0},{"a":12,"b":227.0},{"a":2,"b":43.0},{"a":23,"b":32.0}];
var xname = "a";
var yname = "b";
var quadtree = d3.geom.quadtree(data)
.x(function(d) {return d[xname]; })
.y(function(d) {return d[yname]; });
But when I run this code I get an error:
TypeError: d3.geom.quadtree(data).x is not a function. (In 'd3.geom.quadtree(data).x(function(d) {return d[xname]; })', 'd3.geom.quadtree(data).x' is undefined)
How can I set up the quadtree to use different x and y labels? (Obviously I could change the data to use "x" and "y" but there are reasons I don't want to do that.)
Don't create your quadtree passing it data
e.g :
var data = [{"a":6,"b":99.0},{"a":12,"b":227.0},{"a":2,"b":43.0},{"a":23,"b":32.0}];
var xname = "a";
var yname = "b";
var quadtree = d3.geom.quadtree(/* nothing here */)
.x(function(d) {return d[xname]; })
.y(function(d) {return d[yname]; });
data binding must append later
d3.selectAll('circle').data(quadtree(data))
.append('circle');
Related
Using D3, I'm trying to implement a word wrap plugin for each quotation displayed from myData. The call function on the last line is that.
The problem is that it only works for the first quote. It makes the all other quotes not appear. I'm not sure how to structure the call to happen while enter() would typically renders thing.
var quote=svg.selectAll("text.quote").data(myData);
quote.exit().remove()
quote.enter().append("text")
quote
.attr("class","quote")
.attr("x", function (d,i){ return xScale(i);})
.attr("y", function(d){ return yScale(d.y);})
.text(function(d, i){return d.quote;})
.call(d3.util.wrap(125))
You want selection.each() rather than selection.call(). Selection.call will will invoke a function just once, while .each will invoke it for each datum:
selection.each(function) <>
Invokes the specified function for each selected element, in order,
being passed the current datum (d), the current index (i), and the
current group (nodes), with this as the current DOM element
(nodes[i]). This method can be used to invoke arbitrary code for each
selected element...
Compare to:
selection.call(function[, arguments…]) <>
Invokes the specified function exactly once, passing in this selection
along with any optional arguments.
(API Documentation (v4, but both methods exist in v3))
See the following snippet for a comparison of both:
var data = [10,20,30,40];
var selection = d3.select("body").selectAll(null)
.data(data)
.enter()
.append("p")
.each(function(d) {
console.log("each: " + d); // d is datum
})
.call(function(d) {
console.log("call: ")
console.log(d.data()); // d is selection
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
To call this function once on each item, you could use .call within the .each. I've used a g to place the text, so that the tspans this utility creates are positioned correctly (otherwise they overlap). The following snippet otherwise uses your code (the word wrap utility is on top as I could not find a cdn for it quickly enough):
d3.util = d3.util || {};
d3.util.wrap = function(_wrapW){
return function(d, i){
var that = this;
function tspanify(){
var lineH = this.node().getBBox().height;
this.text('')
.selectAll('tspan')
.data(lineArray)
.enter().append('tspan')
.attr({
x: 0,
y: function(d, i){ return (i + 1) * lineH; }
})
.text(function(d, i){ return d.join(' '); })
}
function checkW(_text){
var textTmp = that
.style({visibility: 'hidden'})
.text(_text);
var textW = textTmp.node().getBBox().width;
that.style({visibility: 'visible'}).text(text);
return textW;
}
var text = this.text();
var parentNode = this.node().parentNode;
var textSplitted = text.split(' ');
var lineArray = [[]];
var count = 0;
textSplitted.forEach(function(d, i){
if(checkW(lineArray[count].concat(d).join(' '), parentNode) >= _wrapW){
count++;
lineArray[count] = [];
}
lineArray[count].push(d)
});
this.call(tspanify)
}
};
var wrap = d3.util.wrap(11);
var svg = d3.select("body")
.append("svg")
.attr("height",400)
.attr("width",400);
var myData = ["text 1","text 2","text 3"]
var quote = svg.selectAll("text.quote").data(myData);
quote.enter().append("g")
quote.attr("class","quote")
.attr("transform", function (d,i){ return "translate(20," + (i * 40 + 20) + ")" })
.append("text")
.text(function(d, i){return d})
.each(function() {
d3.select(this).call(d3.util.wrap(11));
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm a newbie in javascript but a few weeks back just delved into d3.js trying
to create a spatio-temporal visualisation.
What I want to achieve is something like this (https://jsfiddle.net/dmatekenya/mz5fxd44/) based on code shown below:
var w1 = ["Dunstan","Mercy","Lara","Khama"];
var w2 =["August 1,2013","August 2,2013","August 3,2013"]
var text = d3.select("body")
.attr("fill","red")
.text("Dunstan")
.attr("font-size", "50px");
var j = 0;
var transition = function() {
return text.transition().duration(500).tween("text", function() {
var i = d3.interpolateString(w1[j], w1[j + 1]);
return function(t) {
this.textContent = i(t);
};
}).each('end', function() {
j += 1;
if (!(j > w1.length)) return transition();
});
};
transition();
However, instead I want to use date string ( like w2 in the code snippet above). When I do this d3 interpolates the numbers embedded in the string as well and the output isn't what I'm looking for. I need help on how I can somehow create a custom interpolator which can interpolate date string while ignoring numbers in them. I know from d3 documentation (https://github.com/mbostock/d3/wiki/Transitions#tween) thats its possible to push a custom interpolator into the d3 array of interpolators but I have tried and still cannot get it to work.
Kindly need your help.
Maybe you need d3.scaleQuantize().
For instance:
var color = d3.scaleQuantize()
.domain([0, 1])
.range(["brown", "steelblue"]);
color(0.49); // "brown"
color(0.51); // "steelblue"
Working off the example here, I'm using my own dataset in a csv to attempt to display a simple line graph. However, no matter how I create the 'groups' for the lineChart, it won't show any actual line on the graph, nor any y values on the axis. I'm parsing the time correctly because the brush works as expected on the graph and the x axis has the proper time labels.
d3.csv('weatherLogMod.csv', function(data) {
var parseDate = d3.time.format("%Y/%m/%d %H:%M");
data.forEach(function(d) {
d.absHumidity = ((6.112 * Math.exp((17.67 * +d.Temperature)/(+d.Temperature + 243.5)) * 2.1674 * +d.Humidity.replace(/[",%]/g, ''))/(273.15 + +d.Temperature));
d.date = parseDate.parse(d.Time);
d.warmth = +d.Temperature;
});
var instance = crossfilter(data);
var dateDim = instance.dimension(function(d) { return d.Time; });
var tempGroup = dateDim.group().reduceSum(function(d) {return d.warmth;});
var minDate = dateDim.bottom(1)[0].date;
var maxDate = dateDim.top(1)[0].date;
var tempChart = dc.lineChart("#chart-line-hitsperday");
tempChart
.width(500).height(200)
.dimension(dateDim)
.group(tempGroup)
.x(d3.time.scale().domain([minDate,maxDate]));
dc.renderAll();
});
I can't find what I'm doing wrong; in the console, doing tempGroup.all() gives me an array full of temperature-date value:keys, so it seems like it should be working?
Here's a screenshot of my problem.
After some trial and error I figured it out;
var dateDim = instance.dimension(function(d) { return d.Time; });
should be:
var dateDim = instance.dimension(function(d) { return d.date; });
and it works fine.
I am creating a scattered Graph using NVD3 using the code they have provided in their limited documentation. I have created a Scatter graph function that loops over a JSON and pushes the values to the data array.
Now I have 2 values for x axis in my Json , x and run Number. What i want is that the graph should be plotted for the value "x" (which have equal gaps) but it should display values of Run Number on the x axis (which have unequal gaps). We want to do this to make the graph more symmetric, as it is not important for us to display the gaps in graph accurately.
What i did was create a new Array xAxisValue and push the Run Numbers onto it while we loop the JSON to get values. values for x are pushed onto the data array ,
Then using
chart.xAxis.axisLabel('Run No.').tickFormat(d3.format('0d')).tickValues(
xAxisValue);
I set the Tick Values to the xAxisValue (run Number) and then pass the data variable to the draw chart function
d3.select('#chart svg').datum(myData).call(chart);
But this does not seem to work. My Axis is blank and hovering over a value displays the tool tip displays values of x instead of run number.
Because we are dynamically updating the graph i have separated the add Graph and upgrade graph function
Here is the code
function addGraph() {
var jsons = [];
chart = nv.models.scatterChart().showDistX(true).showDistY(true)
.transitionDuration(350).color(d3.scale.category10().range());
chart.tooltipContent(function(key) {
return '<h3>' + key + '</h3>';
});
chart.scatter.onlyCircles(false);
var myData = scatterData(2, 11, jsons);
d3.select('#chart svg').datum(myData).call(chart);
// add zoom handler
nv.utils.windowResize(chart.update);
return chart;
}
Upgrade Graph Function
function upgradeGraph() {
minValue = 1000000, maxValue = 0, minValueY = 100000000, maxValueY = 0;
var jsons = [];
d3.select('svg').text('');
if ($("#check2").is(':checked')) {
jsons.push("charge_ONTk_Barrel_L2_mpv.json");
}
if ($("#check1").is(':checked')) {
jsons.push("charge_ONTk_Barrel_L1_mpv.json");
}
if ($("#check3").is(':checked')) {
jsons.push("charge_ONTk_Barrel_L3_mpv.json");
}
var myData = scatterData(2, 11, jsons);
chart.xAxis.axisLabel('Run No.').tickFormat(d3.format('0d')).tickValues(
xAxisValue);
chart.yAxis.axisLabel('S/N (mpv)').tickFormat(d3.format('.04f'));
for (var i = 0; i < xAxisValue.length; i++) {
console.log("Run Number: " + xAxisValue[i]);
}
console.log("Min Y: " + minValueY + " Max Y " + maxValueY);
chart.forceX([ minValue - 2, maxValue + 2 ]);
chart.forceY([ minValueY - 3, maxValueY + 3 ]);
d3.select('#chart svg').datum(myData).call(chart);
// add zoom
addZoom({
xAxis : chart.xAxis,
yAxis : chart.yAxis,
yDomain : chart.yDomain,
xDomain : chart.xDomain,
redraw : function() {
chart.update();
},
svg : chart.svg
});
nv.utils.windowResize(chart.update);
return chart;
}
And the ScatterData Function
function scatterData(groups, points, jsons) {
var data = [];
data.push({
key : 'Error',
values : [],
color : '#FBEC5D'
});
data.push({
key : 'Bin Content ',
values : [],
color : '#0D4F8B'
});
for (var i = 0; i < jsons.length; i++) {
xAxisValue = [];
var jsonURL = jsons[i];
var xmlhttp = new XMLHttpRequest();
var url = "alljsons/" + jsons[i];
var parameters = location.search;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var myArr = JSON.parse(xmlhttp.responseText);
var jsonName = jsonURL.split(".");
var temp = jsonName[0];
var value = myArr[temp];
// $(".title" + loop).html(temp);
for ( var i in value) {
if (value[i].run > maxValue) {
maxValue = value[i].x;
}
if (value[i].run < minValue) {
minValue = value[i].x;
}
if (value[i].y > maxValueY) {
maxValueY = value[i].y;
}
if (value[i].y < minValueY) {
minValueY = value[i].y;
}
xAxisValue.push(value[i].run);
data[1].values.push({
x : value[i].x,
y : value[i].y,
size : 6 // Configure the size of each scatter point
,
shape : "circle"
});
var err = value[i].y - value[i].yErr;
if (err < 0) {
err = 0;
console.log("error: " + err);
}
data[0].values.push({
x : value[i].x,
y : err,
size : 6 // Configure the size of each scatter point
,
shape : "triangle-down"
});
}
}
};
xmlhttp.open("GET", url, false);
xmlhttp.send();
}
return data;
}
Here is the Output i am getting
If I understand your question correctly:
For the x-axis ticks, I would use D3's axis.tickformat function. You could create a function mapXToRunNumber(x) that takes an x value and returns a run number (you seem to be close to having this already). Then, you would use: chart.xAxis.tickFormat(mapXtoRunNumber);
For the tooltip to also show the same value as the x-axis, you would use the nvD3 function chart.interactiveLayer.tooltip.headerFormatter(mapXToRunNumber).
How can I get the value of the previous element of the dataset passed to .data() in d3?
I know in a callback I cant do something like
function(d,i) { console.log(d, i); })
for example, to print the data element and its index to the console. But how can i reference the previous element?
Like d[i-1] or something?
You can get the value of previous element like this.
var texts = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x",function(d){ return d.x; })
.attr("y",function(d){ return d.y; })
.text(function(d){ return d.name; });
texts.attr(...,function(d,i){
......
var prevData = texts.data()[i-1]; //check whether i>0 before this code
.......
});
Here is a small example JSFiddle , Mouse over the texts to see the functionality.
There is no built in way to do it, but you can achieve it in all kinds of ways, including
Scope:
var data = ['a','b','c','d']
d3.select('.parent').selectAll('.child')
.data(data)
.enter()
.append('div')
.attr('class', 'child')
.text(function(d,i) {
return "previous letter is " + data[i-1];
});
Linking (works even if they're Strings, as in this example):
var data = ['a','b','c','d']
for(var i = 0; i < data.length; i++) { data[i].previous = data[i-1]; }
d3.select('.parent').selectAll('.child')
.data(data)
...
.text(function(d,i) {
return "previous letter is " + d.previous
});
Via the parent node (Experimental):
var data = ['a','b','c','d']
d3.select('.parent').selectAll('.child')
.data(data)
...
.text(function(d,i) {
var parentData = d3.select(this.parentNode).selectAll('.child').data();
// NOTE: parentData === data is false, but parentData still deep equals data
return "previous letter is " + parentData[i-1];
});
Related to the last example, you can even try to find the sibling DOM node immediately preceding this node. Something like
...
.text(function(d,i) {
var previousChild = d3.select(this.parentNode).select('.child:nth-child(' + i + ')')
return "previous letter is " + previousChild.datum();
})
but the last two can fail in all kinds of ways, like if the DOM nodes aren't ordered the same as data, or if there are other unrelated DOM nodes within the parent.