d3.js referencing a single array column - javascript

I have an array of generated values to plot a line function. However, when I call the array, the function only returns single values of the array, rather than the column, and hence draws a straight line (http://tributary.io/inlet/8822590). What is the correct syntax here? Thanks in advance.
// Create data
var v1 = 4.137,
t = 10,
x = [],
y = [];
for (i = 0.1; i < 190; i += 0.1) {
x.push(i);
y.push((Math.pow((v1 / i), 1 / t) - 1) * 100);
}
var data = [x, y];
// Scale data
var xscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[0];
}))
var yscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[1];
}))
var line = d3.svg.line()
.x(function (d) {
return xscale(d[0])
})
.y(function (d) {
return yscale(d[1])
});
var svg = d3.select("svg");
var path = svg.append("path")
.data([data])
.attr("d", line) //this calls the line function with this element's data
.style("fill", "none")
.style("stroke", "#000000")
.attr("transform", "translate(" + [96, 94] + ")")

D3 expects the data for a line to be an array of array where each element determines one line and each element within the inner array the point of the line. You've passed in a single array (for one line) with two elements, so you get two points.
To plot all the points, push the coordinates as separate elements:
for (i = 0.1; i < 190; i += 0.1) {
data.push([i, (Math.pow((v1/i),1/t)-1)*102]);
}
The rest of your code can remain basically unchanged. Complete example here.

Related

Top line obscures bottom line in d3js svg chart

I'm using d3js to draw a chart which plots two data series as two lines.
However, parts of the bottom line (the blue line) are obscured:
Hiding either line by adding display: none in the browser's devel tools shows both lines fully rendered.
The rendered SVG looks like this (sorry for the picture, hard to copy the text):
Each path is created by its own D3 function because the vertical scales are different:
var theLineFcnA = d3.line()
.x(function (d) { return xScaleT(d.t); })
.y(function (d) { return yScaleA(d.v); });
var theLineFcnB = d3.line()
.x(function (d) { return xScaleT(d.t); })
.y(function (d) { return yScaleB(d.v); });
And called like this:
function plotLineA(plotData)
{
if (theSVG === null)
return;
// plot it
var theALine = theSVG.select('.lineChart').select("path.lineA");
theALine.data([plotData]);
theSVG.select("g.x.axis").call(xAxis);
theSVG.select("g.y.axisA").call(yAxisA);
theSVG.select("path.lineA").attr("d", theLineFcnA);
}
(there is a similar function for line B)
Any idea on how to fix this? I've fiddled around with various CSS properties on the line but not sure what else to do.
Many thanks
I suppose you set the width of the bottom curve (which should be the first path laid down) to be thicker than the that of the top curve. Here's an example:
let N = 12;
let n = 5;
let cur = 0;
let pts1 = d3.range(N).map(function (x) {
let step = 2 * d3.randomInt(0, 2)() - 1;
cur = cur + step;
return [x, cur];
});
cur = pts1[n - 1][1];
let pts2 = pts1.slice(0, n);
pts2 = pts2.concat(
d3.range(n, N).map(function (x) {
let step = 2 * d3.randomInt(0, 2)() - 1;
cur = cur + step;
return [x, cur];
})
);
let [ymin, ymax] = d3.extent(pts1.concat(pts2).map((d) => d[1]));
let width = 500;
let w = d3.min([800, width]);
let h = 0.625 * w;
let pad = 20;
let x_scale = d3
.scaleLinear()
.domain([0, N])
.range([pad, w - pad]);
let y_scale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let pts_to_path = d3
.line()
.x((d) => x_scale(d[0]))
.y((d) => y_scale(d[1]));
let svg = d3.select('#container')
.append("svg")
.attr("width", w)
.attr("height", h);
svg
.selectAll("path")
.data([pts1, pts2])
.join("path")
.attr("d", pts_to_path)
.attr("stroke", (_, i) => d3.schemeCategory10[i])
.attr("stroke-width", (_, i) => (i == 0 ? 6 : 2))
.attr("fill", "none")
svg
.append("g")
.attr("transform", `translate(0, ${h / 2})`)
.call(d3.axisBottom(x_scale));
svg
.append("g")
.attr("transform", `translate(${pad})`)
.call(d3.axisLeft(y_scale).ticks(4));
<script src="https://cdn.jsdelivr.net/npm/d3#7"></script>
<script src="https://cdn.jsdelivr.net/npm/#observablehq/plot#0.6"></script>
<div id="container"></div>

D3 stops plotting points when data source is modified

I am plotting points on a UK map using D3 off a live data stream. When the data points exceed 10,000 the browser becomes sluggish and the animation is no longer smooth. So I modify the dataPoints array to keep only the last 5000 points.
However when I modify the dataPoints the first time using splice() D3 stops rendering any new points. The old points gradually disappear (due to a transition) but there are no new points. I am not sure what I am doing wrong here.
I have simulated the problem by loading data of a CSV as well storing it in memory and plotting them at a rate of 1 point every 100ms. Once the number of dots goes above 10 I splice to retain the last 5 points. I see the same behaviour. Can someone review the code and let me know what I am doing wrong?
Setup and the plotting function:
var width = 960,
height = 1160;
var dataPoints = []
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([40, 70])
.scale(5000)
.translate([width / 2, height / 2]);
function renderPoints() {
var points = svg.selectAll("circle")
.data(dataPoints)
points.enter()
.append("circle")
.attr("cx", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[0];
})
.attr("cy", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[1];
})
.attr("r", "4px")
.attr("fill", "blue")
.attr("fill-opacity", ".4")
.transition()
.delay(5000)
.attr("r", "0px")
}
/* JavaScript goes here. */
d3.json("uk.json", function(error, uk) {
if (error) return console.error(error);
console.log(uk);
var subunits = topojson.feature(uk, uk.objects.subunits);
var path = d3.geo.path()
.projection(projection);
svg.selectAll(".subunit")
.data(subunits.features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id })
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a!== b && a.id !== 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary")
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a=== b && a.id === 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary IRL")
svg.selectAll(".place-label")
.attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
.style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start": "end"; });
svg.selectAll(".subunit-label")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.name; })
// function applyProjection(d) {
// console.log(d);
// prj = projection(d)
// console.log(prj);
// return prj;
// }
lon = -4.6
lat = 55.45
dataPoints.push([lon,lat])
renderPoints()
});
Function to cleanup old points
var cleanupDataPoints = function() {
num_of_elements = dataPoints.length
console.log("Pre:" + num_of_elements)
if(num_of_elements > 10) {
dataPoints = dataPoints.splice(-5, 5)
}
console.log("Post:" + dataPoints.length)
}
Loading data from CSV and plotting at a throttled rate
var bufferedData = null
var ptr = 0
var renderNext = function() {
d = bufferedData[ptr]
console.log(d)
dataPoints.push(d)
ptr++;
renderPoints()
cleanupDataPoints()
if(ptr < bufferedData.length)
setTimeout(renderNext, 100)
}
d3.csv('test.csv', function (error, data) {
bufferedData = data
console.log(data)
setTimeout(renderNext, 100)
})
In the lines
points = svg.selectAll("circle")
.data(dataPoints)
points.enter() (...)
d3 maps each element in dataPoints (indexed from 0 to 5000) to the circle elements (of which there should be 5000 eventually). So from its point of view, there is no enter'ing data: there are enough circles to hold all your points.
To make sure that the same data point is mapped to the same html element after it changed index in its array, you need to use an id field of some sort attached to each of your data point, and tell d3 to use this id to map the data to elements, instead of their index.
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.id})
If the coordinates are a good identifier for your point, you can directly use:
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.longitude+" "+d.latitude})
See https://github.com/mbostock/d3/wiki/Selections#data for more details.

Dynamically generating multiple d3 svg graphs

I've got an array of objects called graphData (size varies). Each element contains all the information required to create a d3 graph, and I am able to successfully draw the graphs if I access graphData elements by hardcoding (i.e. graphdata[0], graphdata[1] etc).
The problem comes when I attempt to use a for loop to generate one graph for each of the elements. Looked around stackoverflow and the web, but the solutions are all about generating a fixed number of multiple graphs, not generating multiple graphs dynamically.
Below is my working code for generating one graph. What is the recommended way to generate x number of graphs automatically?
var graphData = data.graph;
var RADIUS = 15;
var edgeData = graphData[0].edges;
var nodeData = graphData[0].nodes;
var stageNum = graphData[0].stage;
var xScale = d3.scale.linear()
.domain([d3.min(edgeData, function (d) {
return d.start[0];
}),
d3.max(edgeData, function (d) {
return d.start[0];
})])
.range([50, w - 100]);
var yScale = d3.scale.linear()
.domain([d3.min(edgeData, function (d) {
return d.start[1];
}),
d3.max(edgeData, function (d) {
return d.start[1];
})])
.range([50, h - 100]);
var rScale = d3.scale.linear()
.domain([0, d3.max(edgeData, function (d) {
return d.start[1];
})])
.range([14, 17]);
// already have divs with classes stage1, stage2... created.
var svg = d3.select(".stage" + stageNum).append("svg")
.attr({"width": w, "height": h})
.style("border", "1px solid black");
var elemEdge = svg.selectAll("line")
.data(edgeData)
.enter();
var edges = elemEdge.append("line")
.attr("x1", function (d) {
return xScale(d.start[0]);
})
.attr("y1", function (d) {
return yScale(d.start[1]);
})
.attr("x2", function (d) {
return xScale(d.end[0]);
})
.attr("y2", function (d) {
return yScale(d.end[1]);
})
.attr("stroke-width", 2)
.attr("stroke", "black");
var elemNode = svg.selectAll("circle")
.data(nodeData)
.enter();
var nodes = elemNode.append("circle")
.attr("cx", function (d) {
return xScale(parseInt(d.x));
})
.attr("cy", function (d) {
return yScale(parseInt(d.y));
})
.attr({"r": rScale(RADIUS)})
.style("fill", "yellow")
.style("stroke", "black");
Mike Bostock recommends implementing charts as reusable closures with methods. This would be an ideal implementation in your case as you want to have
multiple graphs with different data
potential reloading with new data (hopefully this is what you mean by dynamic?)
In broad strokes, what you want to do is wrap your code above into a function in very much the same way Mike describes in the post above, and then have data be an attribute of your closure. So here is some badly hacked code:
// your implementation here
var chart = function(){...}
var graphData = d3.json('my/graphdata.json', function(error, data){
// now you have your data
});
// let's say you have a div called graphs
var myGraphs = d3.select('.graphs')
.data(graphData)
.enter()
.append('g')
//now you have g elements for each of your datums in the graphData array
//we use the saved selection above and call the chart function on each of the elements in the selection
myGraphs.call(chart);
//note that internally in your `chart` closure, you have to take in a selection
//object and process it(data is already bound to each of your selections from above):
function chart(selection) {
selection.each(function(data) {
//...
Here is some more good reading on the topic.
Well you can try the following approach.
var graphData = data.graph;
//forEach will return each element for the callback, you can then make use of the e1 to draw the graph.
graphData.forEach(function(e1){
//graph code goes here.
});
providing this as your source array
//it's just a single circle in 3, 4
var stuff = [3, 4];
var source = [ [stuff, stuff], [stuff] ];
a bit of Array stuff
Array.prototype.max = function() {
return Math.max.apply(null, this);
};
Array.prototype.min = function() {
return Math.min.apply(null, this);
};
setup:
var dim = [];
source.forEach(function(elem){
elem.forEach(function(circle){
dim.push(circle.min());
dim.push(circle.max());
});
});
var min = dim.min();
var max = dim.max();
var x = d3.scale.linear()
.domain([min, max])
.scale([yourscale]);
var y = d3.scale.linear()
.domain([min, max])
.scale([yourscale]);
d3.select('body').selectAll('div')
.data(source) //first step: a div with an svg foreach array in your array
.enter()
.append('div')
.append('svg')
.selectAll('circle') //second step: a circle in the svg for each item in your array
.data(function(d){
return d; //returns one of the [stuff] arrays
}).enter()
.append('circle')
.attr('r', 5)
.attr('cx', function(d){
return x(d[0]);
})
.attr('cy', function(d){
return y(d[1]);
})
.style('fill','blue');

Linear cx scaling with javascript objects in D3

I am attempting to plot a simple dataset consisting of an array of javascript objects. Here is the array in JSON format.
[{"key":"FITC","count":24},{"key":"PERCP","count":16},{"key":"PERCP-CY5.5","count":16},{"key":"APC-H7","count":1},{"key":"APC","count":23},{"key":"APC-CY7","count":15},{"key":"ALEXA700","count":4},{"key":"E660","count":1},{"key":"ALEXA647","count":17},{"key":"PE-CY5","count":4},{"key":"PE","count":38},{"key":"PE-CY7","count":18}]
Each object simply contains a String: "key", and a Integer: "count".
Now, I am plotting these in D3 as follows.
function key(d) {
return d.key;
}
function count(d) {
return parseInt(d.count);
}
var w = 1000,
h = 300,
//x = d3.scale.ordinal()
//.domain([count(lookup)]).rangePoints([0,w],1);
//y = d3.scale.ordinal()
//.domain(count(lookup)).rangePoints([0,h],2);
var svg = d3.select(".chart").append("svg")
.attr("width", w)
.attr("height", h);
var abs = svg.selectAll(".little")
.data(lookup)
.enter().append("circle")
.attr("cx", function(d,i){return ((i + 0.5)/lookup.length) * w;})
.attr("cy", h/2).attr("r", function(d){ return d.count * 1.5})
Here is what this looks like thus far.
What I am concerned about is how I am mapping my "cx" coordinates. Shouldn't the x() scaling function take care of this automatically, as opposed to scaling as I currently handle it? I've also tried .attr("cx", function(d,i){return x(i)}).
What I eventually want to do is label these circles with their appropriate "keys". Any help would be much appreciated.
Update:
I should mention that the following worked fine when I was dealing with an array of only the counts, as opposed to an array of objects:
x = d3.scale.ordinal().domain(nums).rangePoints([0, w], 1),
y = d3.scale.ordinal().domain(nums).rangePoints([0, h], 2);
Your code is doing what you want...I just added the text part. Here is the FIDDLE.
var txt = svg.selectAll(".txt")
.data(lookup)
.enter().append("text")
.attr("x", function (d, i) {
return ((i + 0.5) / lookup.length) * w;
})
.attr("y", h / 2)
.text(function(d) {return d.key;});
I commented out the scales, they were not being used...as already noted by you.

Plotting components of three-dimensional array in d3

I'm trying to figure out if I can work with my current data structure to create a scatterplot in d3. What I have is a three-dimensional array, thetas, with dimensions i, j, and k. Dimension j expands with user input. Dimensions i and k are fixed at i=2 and k=3. For a given i, I want to plot the first two entries of k as the x and y values for a scatterplot for all j. I can't figure out how to do this. Currently, to plot i = 1, I have
xx = d3.scale.linear()
.domain([0,1])
.range([margins.yylMarginFactor * width, (1 - margins.yyrMarginFactor) * width]);
yy = d3.scale.linear()
.domain([0,1])
.range([(1 - margins.xxbMarginFactor) * height, margins.xxtMarginFactor * height]);
var tmp = chart.selectAll(".vis3points").data(thetas[0][0], function (d, i) { return i; } );
tmp
.enter().insert("svg:circle")
.attr("class", "vis3points")
.attr("cx", function (d, i) { return xx(d); })
.attr("cy", yy(d))
.attr("r", 3);
tmp.transition()
.duration(10)
.attr("cx", function (d, i) { return xx(d); })
.attr("cy", yy(d))
tmp.exit()
.transition().duration(10).attr("r",0).remove();
Is this the right direction? What structure should I be using?

Categories

Resources