I am working with D3.js and actually I am trying to select all elements with a certain class except one on the particular event "mouseover".
I tried different kind of solutions, but no one worked or just partially.
This is my first solution:
.on("mouseover",
function(d) {
d3.select(this)
.style("stroke-width", "4px");
var selectedMethod = document.getElementById(d.name + "-line");
var notSelectedMethods = $(".method").not(selectedMethod);
d3.selectAll(notSelectedMethods)
.style("opacity", 0.2);
var selectedLegend = document.getElementById(d.name + "-legend");
var notSelectedLegends = $(".legend").not(selectedLegend);
d3.selectAll(notSelectedLegends)
.style("opacity", 0.2);
}
)
Debugging I can see that notSelectedMethods store all nodes ignoring the not() function. This is true for the first part, because with the second one of the snippet work.
Looking around I found this one, so I tried what that they said (focusing on the first part, the line selection), but no one work.
d3.selectAll(".method")
.style("opacity",
function() {
return (this === selectedMethod) ? 1.0 : 0.2;
}
);
Or
var selectedMethod = this;
d3.selectAll(".method")
.filter(
function(n, i) {
return (this !== selectedMethod);
}
)
.style("opacity", 0.2);
Or
d3.selectAll(".method:not(." + this.id + ")")
.style("opacity", 0.2);
How can I solve this issue?
UPDATE:
#TomB and #altocumulus point me in the right direction. With some bit changes, code are now working.
var selectedMethod = d.name;
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I did not mention data structure of the d element, that's was my bad.
This snipper do the job. I think I cannot do better, am I right?
UPDATE 2:
I cheered too soon. I tried to replicate previously on mouseover solution on legend to change lines and legend (same logic as before)
.on("mouseover",
function(d) {
// Change opacity of legend's elements
var selectedLegend = d.name;
d3.selectAll(".legend")
.style("opacity",
function(e) {
return (e.name === selectedLegend) ? 1.0 : 0.2;
}
);
// Change width of selected line
var selectedMethod = d.name;
d3.selectAll(".method")
.style("stroke-width",
function(e) {
return (e.name === selectedMethod) ? "4.5px" : "1.5px";
}
);
// Change opacity of no-selected lines
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I do not know why, snippet where I change width do not work (width does not change).
You have to compare the id like that :
.on("mouseover", function(d) {
d3.select(this).style("stroke-width", "4px");
var selectedLine = d.name + "-line";
d3.selectAll(".method")
.style("opacity", function(e) {
return (e.id === selectedLine) ? 1.0 : 0.2;
});
})
All items with "method" class will have an opacity of 1 except the one with id === d.name + "-line"
You can't compare JQuery items and D3 items like that, have a look at http://collaboradev.com/2014/03/18/d3-and-jquery-interoperability/
#TomB and #altocumulus point me in the right direction. With some bit changes, code are now working.
var selectedMethod = d.name;
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I did not mention data structure of the d element, that's was my bad.
Related
I have two elements I need to render and a context of the big picture I am trying to achieve (a complete dashboard).
One is a chart that renders fine.
$scope.riskChart = new dc.pieChart('#risk-chart');
$scope.riskChart
.width(width)
.height(height)
.radius(Math.round(height/2.0))
.innerRadius(Math.round(height/4.0))
.dimension($scope.quarter)
.group($scope.quarterGroup)
.transitionDuration(250);
The other is a triangle, to be used for a more complex shape
$scope.openChart = d3.select("#risk-chart svg g")
.enter()
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);
On invocation of render functions, the dc.js render function is recognized and the chart is seen, but the d3.js render() function is not recognized.
How do I add this shape to my dc.js canvas (an svg element).
$scope.riskChart.render(); <--------------Works!
$scope.openChart.render(); <--------------Doesn't work (d3.js)!
How do I make this work?
EDIT:
I modified dc.js to include my custom chart, it is a work in progress.
dc.starChart = function(parent, fill) {
var _chart = {};
var _count = null, _category = null;
var _width, _height;
var _root = null, _svg = null, _g = null;
var _region;
var _minHeight = 20;
var _dispatch = d3.dispatch('jump');
_chart.count = function(count) {
if(!arguments.length)
return _count;
_count = count;
return _chart;
};
_chart.category = function(category) {
if(!arguments.length)
return _category
_category = category;
return _chart;
};
function count() {
return _count;
}
function category() {
return _category;
}
function y(height) {
return isNaN(height) ? 3 : _y(0) - _y(height);
}
_chart.redraw = function(fill) {
var color = fill;
var triangle = d3.symbol('triangle-up');
this._g.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", triangle)
.attr("transform", function(d) { return "translate(" + 25 + "," + 25 + ")"; })
.style("fill", fill);
return _chart;
};
_chart.render = function() {
_g = _svg
.append('g');
_svg.on('click', function() {
if(_x)
_dispatch.jump(_x.invert(d3.mouse(this)[0]));
});
if (_root.select('svg'))
_chart.redraw();
else{
resetSvg();
generateSvg();
}
return _chart;
};
_chart.on = function(event, callback) {
_dispatch.on(event, callback);
return _chart;
};
_chart.width = function(w) {
if(!arguments.length)
return this._width;
this._width = w;
return _chart;
};
_chart.height = function(h) {
if(!arguments.length)
return this._height;
this._height = h;
return _chart;
};
_chart.select = function(s) {
return this._root.select(s);
};
_chart.selectAll = function(s) {
return this._root.selectAll(s);
};
function resetSvg() {
if (_root.select('svg'))
_chart.select('svg').remove();
generateSvg();
}
function generateSvg() {
this._svg = _root.append('svg')
.attr({width: _chart.width(),
height: _chart.height()});
}
_root = d3.select(parent);
return _chart;
}
I think I confused matters by talking about how to create a new chart, when really you just want to add a symbol to an existing chart.
In order to add things to an existing chart, the easiest thing to do is put an event handler on its pretransition or renderlet event. The pretransition event fires immediately once a chart is rendered or redrawn; the renderlet event fires after its animated transitions are complete.
Adapting your code to D3v4/5 and sticking it in a pretransition handler might look like this:
yearRingChart.on('pretransition', chart => {
let tri = chart.select('svg g') // 1
.selectAll('path.triangle') // 2
.data([0]); // 1
tri = tri.enter()
.append('path')
.attr('class', 'triangle')
.merge(tri);
tri
.attr("d", d3.symbol().type(d3.symbolTriangle).size(200))
.style("fill", 'darkgreen'); // 5
})
Some notes:
Use chart.select to select items within the chart. It's no different from using D3 directly, but it's a little safer. We select the containing <g> here, which is where we want to add the triangle.
Whether or not the triangle is already there, select it.
.data([0]) is a trick to add an element once, only if it doesn't exist - any array of size 1 will do
If there is no triangle, append one and merge it into the selection. Now tri will contain exactly one old or new triangle.
Define any attributes on the triangle, here using d3.symbol to define a triangle of area 200.
Example fiddle.
Because the triangle is not bound to any data array, .enter() should not be called.
Try this way:
$scope.openChart = d3.select("#risk-chart svg g")
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);
i'm using a rowchart to show the total of sales by item of a salesman.
Already tried a composite chart unsuccessfully like many posts from the google, but none of the examples uses a rowchart.
I need to do like the image, creating the red lines to represent the sale value target for each item, but i dont know how, can you guys help me? Thanks!
Actually this is my code to plot the rowchart
spenderRowChart = dc.rowChart("#chart-row-spenders");
spenderRowChart
.width(450).height(200)
.dimension(itemDim)
.group(totalItemGroup)
.elasticX(true);
Obviously you need a source for the target data, which could be a global map, or a field in your data.
I've created an example which pulls the data from a global, but it would also take from the data if your group reduction provides a field called target.
Then, it adds a new path element to each row. Conveniently the rows are already SVG g group elements, so anything put in there will already be offset to the top left corner of the row rect.
The only coordinate we are missing is the height of the rect, which we can get by reading it from one of the existing bars:
var height = chart.select('g.row rect').attr('height');
Then we select the gs and use the general update pattern to add a path.target to each one if it doesn't have one. We'll make it red, make it visible only if we have data for that row, and start it at X 0 so that it will animate from the left like the row rects do:
var target = chart.selectAll('g.row')
.selectAll('path.target').data(function(d) { return [d]; });
target = target.enter().append('path')
.attr('class', 'target')
.attr('stroke', 'red')
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M0,0 v' + height;
}).merge(target);
The final .merge(target) merges this selection into the main selection.
Now we can now animate all target lines into position:
target.transition().duration(chart.transitionDuration())
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M' + (chart.x()(d.value.target || _targets[d.key] || 0)+0.5) + ',0 v' + height;
});
The example doesn't show it, but this will also allow the targets to move dynamically if they change or the scale changes. Likewise targets may also become visible or invisible if data is added/removed.
thank you, due the long time to have an answer i've developed a solution already, but, really thank you and its so nice beacause its pretty much the same ideia, so i think its nice to share the code here too.
The difference its in my code i use other logic to clear the strokes and use the filter value of some other chart to make it dynamic.
.renderlet(function(chart) {
dc.events.trigger(function() {
filter1 = yearRingChart.filters();
filter2 = spenderRowChart.filters();
});
})
.on('pretransition', function(chart) {
if (aux_path.length > 0){
for (i = 0; i < aux_path.length; i++){
aux_path[i].remove();
}
};
aux_data = JSON.parse(JSON.stringify(data2));
aux_data = aux_data.filter(venda => filter1.indexOf(venda.Nome) > -1);
meta_subgrupo = [];
aux_data.forEach(function(o) {
var existing = meta_subgrupo.filter(function(i) { return i.SubGrupo === o.SubGrupo })[0];
if (!existing)
meta_subgrupo.push(o);
else
existing.Meta += o.Meta;
});
if (filter1.length > 0) {
for (i = 0; (i < Object.keys(subGrupos).length); i++){
var x_vert = meta_subgrupo[i].Meta;
var extra_data = [
{x: chart.x()(x_vert), y: 0},
{x: chart.x()(x_vert), y: chart.effectiveHeight()}
];
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveLinear);
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.extra').data([extra_data]);
path = path.enter()
.append('path')
.attr('class', 'oeExtra')
.attr('stroke', subGruposColors[i].Color)
.attr('id', 'ids')
.attr("stroke-width", 2)
.style("stroke-dasharray", ("10,3"))
.merge(path)
path.attr('d', line);
aux_path.push(path);
}
}
})
And that's how it looks
In the example below, I see that when a user selects a country, the globe rotates and centers to that country. How can I code it such that when a country is clicked, the globe centers to that country?
D3 Globe
I have tried to do add an on click handler before the mouseover event, but it doesn't seem to work. Any ideas?
.on("click", function(d) {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
})
First create a click to the path like this:
.on("click", function(d){rotateMe(d);})
Change code for the clicked data. Get the id of the clicked path and get its country data.
var rotateMe = function(d) {
var rotate = projection.rotate(),
focusedCountry = country(countries, d.id),//get the clicked country's details
p = d3.geo.centroid(focusedCountry);
console.log(focusedCountry, "hello")
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
};
working code here
In d3 I'm animating elements based on what they are.
.on('mouseover', function (d, i) {
d3.selectAll('rect')
.attr('y', function() {
// affects all <rect> elements
// I have tried
if (source.length > (i + 1)) {
console.log(i);
}
}
d3.select(this)
.attr('y' function() {
// affects only the mouseover element
}
// since above didn't work, maybe some other selection method will?
d3.select(AllThingsAfterthis)
.attr('y' function() {
// some other values
}
})
I'm using an on.('mouseover') event to determine two states:
d3.selectAll('rect')
d3.select(this)
What I'd like to know is how to select only elements after this in the grouping.
rect 1 -> .selectAll
rect 2 -> .selectAll
rect 3 -> this
rect 4 -> how do I target this?
rect 5 -> or this specifically?
You can do this with .nextSibling:
.on("mouseover", function() {
var node = this;
while(node = node.nextSibling) {
d3.select(node)...;
}
})
Complete demo here.
Is there any way to transition between text values for a particular element - e.g. fading between them or something similar? I've found no relevant references to this, but D3 seems built for this kind of thing. This jsfiddle may assist: http://jsfiddle.net/geotheory/2wJr7/
<p>Intro text - click mouse on this text</p>
<script>
var i = 0;
var data = ['text 1','text 2','text 3','text 4','text 5','text 6','text 7','text 8'];
var change = d3.select('p').on("mousedown", function(){transition();});
function transition() {
change.transition().duration(1000).text(data[i]);
i = i + 1;
}
</script>
You can do this by setting the opacity to 0 and then to 1 in two connected transitions:
change.transition().duration(500)
.style("opacity", 0)
.transition().duration(500)
.style("opacity", 1)
.text(data[i]);
Complete jsfiddle here.
Like this?
var data = ['text 1','text 2','text 3','text 4','text 5','text 6','text 7','text 8'];
var change = d3.select('p').on("mousedown", function(){transition();});
function transition() {
for (i=0; i < data.length; i++)
change
.transition()
.delay(i * 1000)
.duration(500)
.text(data[i]);
}
This is more beautiful than changing opacity
function transition() {
d3.select('text').transition()
.duration(500)
.style("font-size","1px")
.transition()
.duration(500)
.text(data[i])
.style("font-size","16px");
i = i + 1;
}