I am doing nesting in D3 and in a nested element, I need to reach data object on its parent.
Right now I am doing
d3.select(this).node().parentNode.__data__;
Is there a better way?
d3.select(this).node() is the same as just this in the context of a function passed to a D3 selection. You could rework it like this d3.select(this.parentNode).datum() and get the correct value back without having to use the ugly double-underscore property.
The other method I'm familiar with is to use .each() on the parent, and then deal with children within the closure:
d3.selectAll('.parent').each(function(parentDatum) {
d3.select(this).selectAll('.child').each(function(childDatum) {
// do stuff with parentDatum and childDatum here
});
});
I found this post when I had exactly the same issue. My solution, was to change the selection to pass on the data that I will need in the child, which I think might be a valid approach if you can live with the data redundancy in the node.
[
{
title: "Google",
link: "www.google.com",
languagePath : "en,fr,it"
},
...
]
To help explain, I've used this in the context of a table, with two columns. The first column has the title and the second column has an a for each of the accept-language items.
So I did a sub selections of the split of languagePath and for each enter call, I would create a a with the text being the language.
So at this point I also need the link, so that the a elements look like:
EN
FR
But the link is not part of the data passed to this child, so when I did the selection instead of doing:
var langLinks = tableRow
.append("td")
.selectAll("span")
.data(function(d) {
return d.languagePath.split(",")
})
I did
var langLinks = tableRow
.append("td")
.selectAll("span")
.data(function(d) {
return d.languagePath.split(",").map(function(item) {
return {
lang : item,
link : d.link
})
})
And so, when I'm processing data on the enter() of this selection I do have the parent's data.
Hope this is a valid alternative for some of you, it did help me.
Related
I'm a bit new to d3.js and I guess my question is not about any detailed code or data.
here it is:
function selectNodesWithProperty(container, key, value){
var nodes = d3.selectAll(container).select(function(d, i) {
if (value == d[key]) {
console.log(this); // this works correctly, the element is the one I want.
this.attr('fill', 'red')
}
});
}
selectNodesWithProperty(".selector", "id", "Blue");
I have two questions:
Is my way the proper way to select the element by data?
Is there any way that I can use d3's attr for the element?
Thanks,
The most obvious solution is to use selection.filter() to get a subset of the selection containing all the nodes for which the filter is true. You can then use .attr() on the selection returned by .filter() to apply changes to the attribute values of the new subset only.
function selectNodesWithProperty(container, key, value){
d3.selectAll(container)
.filter(d => value === d[key])
.attr('fill', 'red')
}
http://jsfiddle.net/brunoperel/z6qfttmr/
I'm trying to create elements on-the-fly, depending on the link which has been clicked on :
d3.select('#processes').selectAll('.processLaunch').data(helperProcessesData).enter()
.append('a')
.classed('processLaunch', true)
.attr('href', 'javascript:void(0)')
.text(function(d) { return d.text; })
.on('click', function(d) {
var currentProcess=d.name;
var filteredData = helperStepsData.filter(function(d) { return d.process === currentProcess; });
var helperStepsForProcess = d3.select('div#helperContainer').selectAll('.helperStep')
.data(filteredData);
console.log('Filtered data : '+JSON.stringify(filteredData));
console.log('Linked data : '+JSON.stringify(helperStepsForProcess.data()));
helperStepsForProcess.enter()
.append('div').text(function(d) { return 'Step '+d.step+' : '+d.title; });
helperStepsForProcess.exit().remove();
});
There are (at least !) two things that I don't understand here :
Each time I click on a link, related texts are added to the page, but obsolete ones are not removed even though I called .exit().remove() on the selection. Why ?
When I do a console.log of the data that is about to be bound to the selection's elements, it returns an array of objects, which is fine. But when, in the line after this, I retrieve the data which has been bound to the elements, I get an array of undefined objects. Why don't I get an array of objects as well ?
I think you just forgot to add the respective class on the steps:
helperStepsForProcess.enter()
.append('div')
.attr('class', 'helperStep');
helperStepsForProcess.text(function(d) {
return 'Step '+d.step+' : '+d.title; });
Adding this in your fiddle return the expected result.
I hope this helps!
Update: https://jsfiddle.net/chroth/z6qfttmr/2/
i am trying to display top 5 country based on revenue
i tried feeding in chart its not working
.group(countryGrp.top(10))
but when i tried console.log(countryGrp.top(10)) i can see the values though
var country = data.dimension(function(d){return d.PName});
var countryGrp = lead.group().reduceSum(function(d) {
return d.Amount;
});
var leadBarChart = dc.barChart("#country-chart")
leadBarChart.width(500).height(450).transitionDuration(750)
.margins({
top : 20,
right : 10,
bottom : 80,
left : 50
})
.dimension(country)
.group(countryGrp.top(10))
.ordinalColors([ "#1F77B4" ]).elasticY(true).centerBar(true)
.title(function(d) {
return d.Amount;
}).gap(6).xUnits(function() {
return 2;
})
.x(d3.scale.linear())
.renderHorizontalGridLines(true).xAxis().ticks(5)
.tickFormat(d3.format("d"));
i am getting this error uncaught TypeError: undefined is not a function can any one help me out here. Thanks in advance
So group.top(N) doesn't return a group object; it returns an array. You need to supply a group for the group argument.
Unfortunately the bar chart does not currently support capping but what you can do is prefilter the data as explained here:
https://github.com/dc-js/dc.js/wiki/FAQ#filter-the-data-before-its-charted
Basically you will create a "fake group" which has an .all() function which returns only the items you want.
I am planning to fix .data() so that it works for bar charts:
https://github.com/dc-js/dc.js/issues/584
(and it would also be nice to support capping) but for now I think you are stuck with the "fake group" workaround.
Please comment here if you can't get this to work, or add you example to the wiki linked above if you do get it to work!
You'll want to use something like the following:
function getTops(source_group) {
return {
all: function () {
return source_group.top(5);
}
};
}
var fakeGroup = getTops(groupToFindTopsFor);
You can then use .group(fakeGroup) to properly chart your data.
Merry Christmas, everyone!
I want to do something when the source element or target element of a joint.dia.Link is changed. Firstly I tried to put the code in the callback function of 'change:source' and 'change:target' events. However, it turns out that those callback functions are called as soon as the link's position changes, instead of being called when the source element or target element changes. Then I tried to put the code in the LinkView.pointup() function, by adding a tag, which is set in the callback function of 'change:source' and 'change:target' events, to indicate the changed element. The resulted code looks like this:
link.on('change:source', function(){this.src_changed = true;});
link.on('change:target', function(){this.dest_changed = true;});
joint.shapes.custom.ModelLink = joint.dia.Link.extend({
defaults: joint.util.deepSupplement({
type: 'custom.ModelLink',
}, joint.dia.Link.prototype.defaults)
});
joint.shapes.custom.ModelLinkView = joint.dia.LinkView.extend({
pointerdown: function () {
joint.dia.LinkView.prototype.pointerdown.apply(this, arguments);
},
pointermove: function () {
joint.dia.LinkView.prototype.pointermove.apply(this, arguments);
},
pointerup: function (evt, x, y) {
var link = this.model;
if(link.src_changed) { // do something}
if(link.dest_changed) {// do something}
joint.dia.LinkView.prototype.pointerup.apply(this, arguments);
}
});
However, I found src_changed and dest_changed are both set to true sometimes when I am just dragging one end of the link. Why does this happen? How can I fix this? Or any new approach to do some response to the change of source element or target element?
Besides, After I reset the events of joint.shapes.uml.State using model.set('events', events), the text doesnot change on the graph? How can I refresh graph to show the changed state element?
Thanks very much!
The change:source and change:target events are indeed triggered also when the position of the arrowheads changes. In general, the source and target of a link can either be a point (an object with x and y properties) or an element (and in the near future also a link) - an object with id property pointing to the linked element. If you're only interested in source/target being an element, you can just check in your handlers for the change:source and change:target events whether the source/target of the link contains the id property:
if (this.get('source').id) { /*... do something ...*/ }
I want to dynamically add some preconfigured HTML-Elements in use of a 'click'-event with mootools.
So I can make it work with my basic knowledge, although it isn´t very nifty. I coded this so far...
This is my preconfigured element, with some text, a classname and some event, cause i wanna have events already added, when it´s inserted into my container:
var label = new Element('label', {
'text': 'Label',
'class': 'label',
'events': {
'click': function(el){
alert('click');
}
}
});
Here is my function, which adds the label-Element:
function addText(){
$('fb-buildit').addEvent('click', function(){
row.adopt(label, textinput, deletebtn);
$('the-form').adopt(row.clone());
row.empty();
/*
label.clone().inject($('the-form'));
textinput.inject($('the-form'));
deletebtn.inject($('the-form'));
*/
});
}
The second part which uses inject also works, but there, my click-Event, which fires the "alert('click')" works too. The method with adopt doesn´t add any event to my label Object, when its inserted in the dom.
Can anyone help me with this. I just wanna know why adobt ignores my "events" settings and inject doesn´t.
Thanks in advance.
(sorry for my english ^^)
you go label.clone().inject but row.adopt(label) and not row.adopt(label.clone()) -
either way. .clone() does not cloneEvents for you - you need to do that manually.
var myclone = label.clone();
myclone.cloneEvents(label);
row.adopt(label);
this is how it will work
as for why that is, events are stored in the Element storage - which is unique per element. mootools assigns a uid to each element, eg, label = 1, label.clone() -> 2, label.clone() -> 3 etc.
this goes to Storage[1] = { events: ... } and so forth. cloning an element makes for a new element.uid so events don't work unless you implicitly use .cloneEvents()
you are sometimes not doing .clone() which works because it takes the ORIGINAL element along with its storage and events.
suggestion consider looking into event delegation. you could do
formElement.addEvent("click:relay(label.myLabel)", function(e, el) {
alert("hi from "+ el.uid);
});
this means no matter how many new elements you add, as long as they are of type label and class myLabel and children of formElement, the click will always work as the event bubbles to the parent.