I have two array objects that hold my d3.svg.symbol types which are circles, squares & triangles. Array #1 has multiple symbols which I plot across the canvas, whereas array #2 only holds three symbols aligned together.
My goal is to be able to click on array #2 to filter out all of the array #1 symbols that i dont want to see. e.g. Clicking a circle in array #2 would only mean circles are shown in array #1.
var array1 = svg.selectAll(a.array1)
.data(json).enter().append("a")
array1.transition().duration(1000)
.attr("transform", function(d,i) {return "translate("+d.x+","+d.y+")" ;})
array1.append('path')
.attr("d", d3.svg.symbol().type(function(d) {return shape [d.Country];}).size(120))
var array2 = svg.selectAll(g.array2)
.data(filt)
.enter().append("g")
.attr("transform", function(d,i) {return "translate("+d.x+","+d.y+")" ;})
array2.append("path")
.attr("d", d3.svg.symbol().type(function(d){return d.shape;}).size(200))
.attr("transform", "translate(-10, -5)")
So my query is how do I specify the click onto array#2 specific types as I have three. Therefore, I would like all to be clickable, but have a different outcome.
So far I have tried this just to try & select specific shapes in array#2
array2.on("click", function(){ alert('success') })
which just alerts when I click any of them, however when this is applied:
array2.on("click", function(){ if (d3.svg.symbol().type('circle') === true) { return alert('success') ;}; })
When I click the circle of array2 it doesnt alert at all.
It would be great if I could get some help - thanks. http://jsfiddle.net/Zc4z9/16/
The event listener gets the current datum and index as arguments, see the documentation. You can also access the DOM element through this. You could use this like follows.
.on("click", function(d) {
if(d.shape == "circle") { alert("success"); }
});
Related
I am trying to replicate this example of a multiline chart with dots. My data is basically the same, where I have an object with name and values in the first level, and then a couple of values in the second level, inside values. The length of the arrays inside values is 40.
Now, one requirement is that all the dots for all the paths are inside the same g group within the DOM. This is giving me a lot of trouble because I can't seem to figure out how to join the circles with the appropriate portion of the nested data.
The last thing I've tried is this:
var symbolsb = d3.select("#plot-b") // plot-b is the graph area group within the svg
.append("g")
.attr("id", "symbols-b");
symbolsb.selectAll("circle")
.data(games, function(d) {console.log(d.values) // games is my data object
return d.values})
.enter()
.append("circle")
.attr("class", "symbolsb")
.attr("cx", function(d,i) {console.log(d)
return x(d.values.date);})
.attr("cy", function(d,i) {return y_count(d.count);})
.attr("r", function(d,i) {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
console.log(parent)
if (i%3 === 1 && included_names.includes(datum[i].name)) {
return 8;}
else {return null;}})
.style("fill", function(d,i) {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
{return color(datum.name);}});
As I (incorrectly) understand the data() function, I thought that by returning d.values, the functions in cx, cy, and r would just see the array(s) that is inside d.values, but when log d to the console within the functions to define cx, cy, etc. I see again the full object games. Again, I though I should only get the values portion of the object.
I have been able to get a plot that looks like the result I want by loading the data and appending a g when defining symbolsb, but this creates a group for each set of circles.
I think the problem comes from my confusion of how nested objects are accessed by the data() function. So any help explaining that would be greatly appreciated.
It would be great if you could provide a live reproduction, for example in an Observable or VizHub notebook.
This line looks suspect
.data(games, function(d) {console.log(d.values) // games is my data object
return d.values})
The second argument to *selection*.data should be a 'key function', a function that returns a unique string identifier for each datum. Here you are giving an object (d.values) which will get stringified to [object Object] for each data point. This also explains why you're seeing the full games object when logging. I think it's safe here to just remove the second argument to .data():
.data(games)
This also doesn't look right
.attr("r", function(d,i) {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
console.log(parent)
if (i%3 === 1 && included_names.includes(datum[i].name)) {
return 8;}
else {
return null;
*emphasized text*}})
I'm not entirely sure what you're trying to do here. If you're trying to access the name of the data point you can just access it on the data point itself using .attr("r", function(d,i) { if (included_names.includes(d.name)) { return 8 } else { return 0} )
I was looking into the following example by Mike Bostock on focus + context zooming. https://bl.ocks.org/mbostock/1667367.
I was wondering if we can link multiple charts with a main graph. Something similar to the attached image. I am quiet new to d3.js so i might have missed something but i am unable to find any links on how to go about it. All of the graphs have equal data points.
Thanks!
It depends obviously on how your data is organized. I imagine you have a data array as follows:
[ {date : ... /*x-coordinate*/
price1 : .../*first y-coordinate*/
price2 : .../*second y-coordinate*/
/* ...and as many as you like*/
},
{date : ...
price1 : ...
price2 : ... }
]
You also have an array containing the names of the fields you want to fetch as y-axis:
fields = ["price1", "price2", ...]
So first thing to do is a way to extract the data for each separate curve. This can be done as follows:
function singleCurveData(fieldId) {
return data.map(function (d) {
return {date: d.date, price: d[fieldId]};
});
}
If your data is organized differently, only this function needs to change. Basically, it receives the id of the graph you want to draw, and outputs the data for this specific graph in a standard fashion.
Now to the drawing section. You need as many focus parts as different fields you want to show, each one containing a single area; and a single context part containing many area2s.
var focus = svg.selectAll(".focus")
.data(fields) //associate one field id to each focus block
.enter()
.append("g")
.attr("class", "focus")
.attr("transform", function(d,i) {return "translate(" + margin.left + "," + (margin.top - i*focusHeight) + ")"});
//the above line takes care of the positioning, you need to know the target height of a focus block.
var context= ...//as before
And now we move on to drawing the curves:
focus.append("path") //append one path per focus element
.datum(function(fieldId) {return singleCurveData(fieldId)}) //only this line changes
.attr("class", "area")
.attr("d", area);
context.selectAll("path")
.data(fields) // add a "path" for each field
.enter()
.append("path") //here the datum for the path is the field Id
.datum(function(fieldId) {return singleCurveData(fieldId)})
//now the datum is the path data for the corresponding field
.attr("class", "area")
.attr("d", area2);
This should be all there is to it. Good luck!
The following toy problem illustrates my issue. I have an array of "locations", say a treasure map. Each item in the array for example monsters or treasure could exist at multiple locations on the map. e.g.
locations = [
{name:'treasure', color: 'blue', coords:[[100,100], [200,300]]},
{name:'monsters', color: 'red', coords:[[100,150], [220,420], [50,50]]}
]
Now I want to plot these using D3. The bad/naive approach (that works - see here for fiddle), would look like this:
for location in locations
for coords in location.coords
svg.append('circle')
.attr('cx', coords[0])
.attr('cy', coords[1])
.attr('r', 8)
.style('fill', location.color)
.datum(location)
However, when I modify the contents of the data, I don't want to have to run this naive code each time. It appears that using data() and enter() is the "correct" way to do it, but I can't figure out how it works with the sub-coordinates. e.g.
svg.selectAll('circle').data(locations).enter().append('circle')
.attr('cx', (d) -> d.coords[0][0])
.attr('cy', (d) -> d.coords[0][1])
.attr('r', 8)
.style('fill', (d) -> d.color)
This works great, but as you can see I am only printing the FIRST coordinate for each location, where I want to print them all. I suspect the only way to do this is to flatten my data array so there are 5 entries in total - 3 monsters and 2 treasure items.
Just wondering if there is a way to handle this better using D3.
For this, you need nested selections. The idea is that instead of appending a single element per data item, you append several. In code, it looks like this:
// append a `g` element for each data item to hold the circles
var groups = svg.selectAll("g.circle").data(locations)
.enter().append("g").attr("class", "circle");
// now select all the circles in each group and append the new ones
groups.selectAll("circle")
// the d in this function references a single data item in locations
.data(function(d) { return d.coords; })
.enter().append("circle")
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; });
It works the same for update and exit selections.
I have two g elements each containing circles. Circles are organized using force.layout. The g elements are transitioning.
You can see here: demo. Reduced code:
var dots = svg.selectAll(".dots")
.data(data_groups)
.enter().append("g")
.attr("class", "dots")
.attr("id", function (d) {
return d.name;
})
...
.each(addCircles);
dots.transition()
.duration(30000)
.ease("linear")
.attr("transform", function (d, i) {
return "translate(" + (150 + i * 100) + ", " + 450 + ")";
});
function addCircles(d) {
d3.select(this).selectAll('circle')
.data(data_circles.filter(function (D) {
return D.name == d.name
}))
.enter()
.append('circle')
.attr("class", "dot")
.attr("id", function (d) {
return d.id;
})
...
.call(forcing);
}
function forcing(E) {
function move_towards(alpha) {
...
}
var force = d3.layout.force()
.nodes(E.data())
.gravity(-0.01)
.charge(-1.9)
.friction(0.9)
.on("tick", function (e) {
...
});
force.start();
}
I need to move circle (for example id=1) from the first g element to the second one using transition.
Any suggestions are appreciated.
It can be done.
What I did was:
1) Use jquery to append the point to the target group
2) Use a transformation (no transition) to move the point back to its original location
3) Transition the point to its new location
The jQuery was used for the appendTo method. It can be removed and replaced with some pure Javascript stuff, but it's quite convenient.
I've got a partially working fiddle here. The green points work right, but something is going wrong with the blue ones. Not sure why.
In my view, transitions work on a single element. If an element changes its position in the DOM tree, from below one g to another, I can't think of a way to make that as one smooth transition because it's basically a binary split: Now there's an element under one g, now it's gone but there's another one somewhere else.
What I'd do in order to achieve what I think you want to do: Group everything under the same ´g´, assign color and translation individually, then change color and translation for that single element you want to change.
But don't take that as a reliable statement that you can't do it the way you originally wanted.
I have a dataset already binded to svg:g via a d.id
var categorized = g1.selectAll("g.node")
.data(dataset, function(d){return d.id})
.classed('filtered', false);
categorized.enter()
.append("g")
.attr("class", "node")
...
I use a function to order it from a data value like this:
var sorted = dataset
.filter(function(d) { return d.notation[3].value >=50 } )
.sort(function(a, b) { return d3.descending(a.notation[3].value,
b.notation[3].value) });
It returns the correct order when I console.log it
var filtered = g1.selectAll("g.node")
.data(sorted, function(d) {return d.id})
.classed('filtered', true);
Still in the right order if I console.log it,
but if I apply a delay it reverses the result order
scored.transition()
.delay(500).duration(1000)
.attr("id", function(d) {
console.log(d.id);
});
but keeps it well sorted if I remove the delay.
My question : am I doing something in a bad way?
I think you're observing that d3.js generally uses the "optimized" for loop that iterates in reverse (see Are loops really faster in reverse? among other references).
Would it work to simply reverse your selection? I'm not sure what you're transitioning such that you need the tween steps to be applied in a certain order.