I have a force directed graph made in D3.
I have the ability to drag a box over nodes and change their attribute to 'selected'.
What I wish to do now is move all these selected nodes at once. Here is my drag function'
function dragstart(d, i)
{
force.stop(); //-stop the force layout as soon as you move nodes
}
function dragmove(d, i) //-change coordinates of nodes and lines ???
{
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick();
}
function dragend(d, i) //-when you stop dragging the node
{
d.fixed = true; //-D3 giving the node a fixed attribute
d3.select(this).classed("fixed", true); //-changing nodes CSS class
tick(); //-update positions
}
How do I apply this so that I move all the selected nodes at once ?
I'm assuming that you meant that you changed the class of the nodes to 'selected' and not their attribute. I'm a beginner in D3js, but here's what I think should happen:
d3.behaviour.drag()
.on("dragstart", function() {
d3.selectAll('.selected').each(dragstart(d,i))
})
.on("drag", function() {
d3.selectAll('.selected').each(dragmove(d,i))
})
.on("dragend", function() {
d3.selectAll('.selected').each(dragend(d,i))
})
Depending on what your function tick() does, this may work for you.
Related
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
Is there any way to completely remove/unbind the d3.zoom from canvas?
I wanted to enable only the zoom functionality when the zoom is enabled (via separate button setting)
and reclaim the mouse events (mouse down, up etc) when it is removed.
here is how I add the d3 zoom to canvas
///zoom-start
var d3Zoom = d3.zoom().scaleExtent([1, 10]).on("zoom", zoom),
d3Canvas = d3.select("canvas").call(d3Zoom).on("dblclick.zoom", null),
d3Ctx = d3Canvas.node().getContext("2d"),
d3width = d3Canvas.property("width"),
d3height = d3Canvas.property("height");
function zoom() {
}
Any help would be appreciated.
Thanks in advance
According to the API:
Internally, the zoom behavior uses selection.on to bind the necessary event listeners for zooming. The listeners use the name .zoom, so you can subsequently unbind the zoom behavior as follows:
selection.on(".zoom", null);
And, to enable it again, you just need:
selection.call(zoom);
Here is a demo (the buttons are self explanatory):
var svg = d3.select("svg");
var g = svg.append("g");
var zoom = d3.zoom().on("zoom", function() {
g.attr("transform", d3.event.transform)
});
svg.call(zoom);
g.append("circle")
.attr("cx", 150)
.attr("cy", 75)
.attr("r", 50)
.style("fill", "teal");
d3.select("#zoom").on("click", function() {
svg.call(zoom);
console.log("zoom enabled")
});
d3.select("#nozoom").on("click", function() {
svg.on(".zoom", null)
console.log("zoom disabled")
});
svg {
border: 1px solid gray;
}
.as-console-wrapper { max-height: 9% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="zoom">Enable zoom</button>
<button id="nozoom">Disable zoom</button>
<br>
<svg></svg>
PS: I'm using SVG, not canvas, but the principle is the same.
I have two datasets in my chart and I'm switching between them by clicking to the paragraphs (I'm drawing the buttons there later). The datasets have the different dimensions so I want to replace one another in the tooltip according to the chosen dataset. I can adjust the tooltip once
.on("mouseover", function(d, i) {
var tickDate = d3.select(d3.selectAll(".axis .tick text")[0][i]).data()[0];
console.log (tickDate);
var formatDate = RU.timeFormat("%B %Y");
var tooltipDate = formatDate(tickDate);
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3. select(this). attr("x")) + ((barWidth - barPadding) / 2);
var yPosition = parseFloat(d3. select(this). attr("y")) / 2 + h / 2;
//Update the tooltip position and value
d3.select("#tooltip" )
.style("left" , xPosition + "px")
.style("top" , yPosition + "px")
.select("#value")
.text(d + " DIMENSION1");
d3.select("#tooltip" )
.select("#label")
.text(tooltipDate);
//Show the tooltip
d3.select("#tooltip" ).
classed("hidden" , false);
d3.select(this)
.attr("fill", "orange");
})
but I don't manage to refresh it after the switching. There is the text "DIMENSION1" in both cases now, my purpose is the text "DIMENSION2" appearance after switching and return "DIMENSION1" after the choosing of the initial dataset.
Here is my fiddle: https://jsfiddle.net/anton9ov/dw1xp1ux/
Several problems here :
Avoid code duplication by creating a function transition(dataset, dimension) called for the transitions
You don't update the mouseover event when you change you dataset. So call your mouseover function each time you update the data
svg.selectAll("rect").on("mouseover", function(d, i) {
// Your mouseover function
});
See this fiddle https://jsfiddle.net/bfz97hdw/
(I also fixed the color issue)
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.
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.