I'm making an interactive line chart and I'm having troubles capturing the data from a complex data structure
I passed an array of "paths" and it works:
path = [[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
[{x:int,y:int},{x:int,y:int},{x:int,y:int}]]
but this is the data structure that I'm using:
data:[{
id: 0,
image:[int values],
path:[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
pixel:[int values]
},
id: 1,
image:[int values],
path:[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
pixel:[int values]
}]
And I make a line chart with a standard valueline function:
var valueline = d3.line()
.x(function(d) { return xScale(d.x); })
.y(function(d) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
chart.selectAll(".line-group")
.data(path)
.enter()
.append("g")
.attr("class", "line-group")
.append("path")
.attr("d", valueline)
I'd like to pass the entire data array to d3js and that the function "valueline", captures x and y from the "path" key of each image in the data array.
Any ideas? thanks!
chart.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.append("path")
.attr("d", d => valueline(d.path))
I ended up using rioV8 solution.
I also found another workaround from https://amber.rbind.io/blog/2017/05/02/d3nest/:
chart.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.selectAll(".lineChart")
.data(function(d) { return [d.path] })
.enter()
.append("path")
.attr("class", "lineChart")
.attr("d",valueline)
Related
I'm trying to convert the line graph to bar, I'm having trouble understanding where I'm making the error. I'm using d3.js v4.
This line of code displays code that works for line area:
var area = d3.area()
.x(function(d) { return time_scale(d['timestamp'])+6.5; })
.y0(-80+width -max_y)
.y1(function(d) { return measure_scale(+d[field]); })
.curve(d3.curveStep)
;
// append a SVG path that corresponds to the line chart
d3.select('#chart').append("path")
.datum(neigh_data)
.attr("class", "area")
.attr("d", area)
.attr('transform', 'translate(' + margin + ', -15)')
;
This line of code does not work for bars (it looks like the height of the rectangle is not correct, and I'm not using the color fill):
d3.select('#chart').selectAll(".bar").append("path")
.datum(neigh_data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return time_scale(d['timestamp']); })
.attr("y", function(d,i) { return measure_scale(+d[field]); })
.attr("width", time_scale.bandwidth())
.attr("height", function (d,i) { return height - measure_scale(+d[field]); })
;
I'm really messing around with something.
I would expect the following code to plot two different lines on the same svg but it only returns two empty path>
<body>
<svg width="960" height="500"></svg>
<script>
data = [
{ Name: "line1", color: "blue", Points: [{x:0, y:5 }, {x:25, y:7 }, {x:50, y:13}] },
{ Name: "line2", color: "green", Points: [{x:0, y:10}, {x:25, y:30}, {x:50, y:60}] }
];
var line = d3.line()
.x(function(d, i) {return d.x})
.y(function(d, i) {return d.y})
var lines = d3.select("svg")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", line);
</script>
</body>
I cannot find multiple line charts example that have a data structure similar to mine.
The issue is that you are passing the whole object from your array of data to the line() function, which is expecting an array of points. One alternative is to change the calling function to pass in only the Points array, something like this (untested):
.enter()
.append("path")
.attr("d", function(d) { return line(d.Points); })
In fact you need to access the Points field of each element of the data array:
Within:
var lines = d3.select("svg")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", line);
replace
.data(data)
by
.data(data.map( function(d) { return d.Points }))
Please look at Hexbin and scatterplots: http://imgur.com/a/2oR68
Why in Hexbinplot, points donot touch each other whereas in scatterplot it clearly touches the close points ?
I expected my hexbin plot comes up like this: https://bl.ocks.org/mbostock/4248145 but it didnot.
I am using d3.hexbin plugin.
The only code that is differing from Hexbin plot to scatter plot (I am dealing with same dataset) apart from little bit of scaling is:
For Hexbin:
var color = d3.scale.linear()
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.extent([[0,0],[size - padding , padding]])
.radius();
hexbin.x(function(d,i){return x(subdata[0][i]);})
hexbin.y(function(d,i){return y(subdata[0][i]);})
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", w)
.attr("height", size);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(datum))
.enter()
.append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("fill", function(d) { return color(d.length); });
For scatterplot:
svg.selectAll("circle")
.data(datum)
.enter()
.append("circle")
.style("fill", "steelblue")
.attr("cx", function (d, i) {
return x(subdata[0][i]);
})
.attr("cy", function (d,i) {
return y(subdata[0][i]);
})
.attr("r", 3)
Where am i doing wrong ?
Edit1: Included some fraction of code under Hexbin
If you set...
.attr("d", hexbin.hexagon(5))
//radius value here ------^
..., the hexagons will touch only if you set the same value in the hexabin generator:
var hexbin = d3.hexbin()
.radius(5)//same value here
.extent([[0, 0], [width, height]]);
According to your result, I believe that was not the case. Thus, the solution can be simply removing that value:
.attr("d", hexbin.hexagon())
//no radius here --------^
I can use d3 to draw a pie chart or a graph, I can even draw a pie chart within each node of a graph as shown here.
Is it possible to create a reusable function that generate the pie chart and attach its result to the each node? That way the pie chart code could be reused, for instance in a gallery of charts.
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node");
// draw pie chart
node.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
From the above code, I tried the following code which doesn't work
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drawPie(function(d) { return d.proportions; }));
function drawPie(d) {
this.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
}
Your original idea is much closer than the one recommended in the other answer, you just need to understand how selection.call works.
This is not tested but the general principle is like...
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drawPie);
function drawPie(selection) {
this.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
}
In reference to your first attempt, if you stop and think about this line...
.call(drawPie(function(d) { return d.proportions; }));
...it's actually trying to call null because that's what is returned by drawPie. It's equivalent to...
.call(null);
Based on the recommendations, here is the modified code which still require some improvements. An error message report that "row 93 undefined is not an object evaluating d.proportions"
graph = { "nodes":[
{"proportions": [{"group": 1, "value": 1},
{"group": 2, "value": 2},
{"group": 3, "value": 3}]},
{"proportions": [{"group": 1, "value": 2},
{"group": 2, "value": 2},
{"group": 3, "value": 2}]}],
"links":[{"source": 0, "target": 1, "length": 500, "width": 1}]
}
var width = 960,
height = 500,
radius = 25,
color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(10);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(4 * radius)
.size([width, height]);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append(function(d) {return createPie(d);}) // .append(createPie) --- shorter version
.attr("class", "node");
// node.selectAll("path")
// .data(function(d, i) {return pie(d.proportions); })
// .enter()
// .append("svg:path")
// .attr("d", arc)
// .attr("fill", function(d, i) { return color(d.data.group); });;
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});
});
function createPie(d) {
console.log(d);
var pie = d3.select(document.createElement('svg:g'));
pie.selectAll('path')
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });
return pie.node();
}
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append(function(d){return createPie(d);}) // .append(createPie) --- shorter version
.attr("class", "node");
function createPie(data) {
var pie = d3.select(document.createElement('svg:g'));
pie.selectAll('path')
...;
return pie.node();
}
UPDATE:
function createPie(d) {
console.log(d);
var p = d3.select(document.createElement('svg:g'));
p.selectAll('path')
.data(pie(d.proportions))
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });
return p.node();
}
the previous variable pie needs to be refactored because it overwrites the one in parent scope.
and the data call needs to be fixed as well
So I'm using code based off of this..
http://bl.ocks.org/mbostock/3884955
Essentially, what I'm trying to do is at every data point I want to add a circle. Any help would be much appreciated seeing that I have no idea where to start.
This is my code so far: It worked when I was using single lines.
var circlegroup = focus.append("g")
circlegroup.attr("clip-path", "url(#clip)")
circlegroup.selectAll('.dot')
.data(data)
.enter().append("circle")
.attr('class', 'dot')
.attr("cx",function(d){ return x(d.date);})
.attr("cy", function(d){ return y(d.price);})
.attr("r", function(d){ return 4;})
.on('mouseover', function(d){ d3.select(this).attr('r', 8)})
.on('mouseout', function(d){ d3.select(this).attr('r', 4)});
You need nested selections for this. Assuming that data is a two-dimensional array, you would do something like this.
var groups = svg.selectAll("g").data(data).enter().append("g");
groups.data(function(d) { return d; })
.enter()
.append("circle")
// set attributes