Related
since two days, I'm trying to move a line in simple 10x10 Raster, using the mouse.
I solved this without any issues, with complex svg-"Symbols" but the simplest Elements bring me to my borders…
Ok, simple line with a g-tag:
var svg = d3.select("#drawing");
const graph = svg.append("g")
.attr("id","1")
.attr("ponter-events","fill")
.call(d3.drag()
.on("start", dragGraphicStart)
.on("drag", dragGraphic)
.on("end", dragGraphicStop));
graph.html("<line class='coldbrinewater graphic' x1='10' y1='10' x2='50' y2='50' />");
Now of Course, before the first drag, this line doesn't have a Transformation, so I check this before reading a Position:
var xPos, yPos;
function dragGraphicStart() {
var trans = d3.select(this).attr("transform"); //Prüfen ob bereits ein Transform existiert
if (trans == null || trans == "" || trans == "translate(0)") { //kein Transform vorhanden
xPos = 0;
yPos = 0;
}
else { //Transform gefunden, startwerte ermitteln
trans = trans.replace(",", " "); //I don't know why, sometimes I got a comma between x and y-value
trans = trans.substring(trans.indexOf("(") + 1, trans.indexOf(")")).split(" ");
xPos = parseInt( trans[0]);
yPos = parseInt( trans[1] );
}
}
And then I try to move the line:
function dragGraphic(d, i) {
xPos += Math.round(d3.event.dx / raster) * raster;
//xPos += d3.event.dx;
yPos += Math.round(d3.event.dy / raster) * raster;
//yPos += d3.event.dy;
d3.select(this).attr("transform", "translate(" + xPos + " " + yPos + ")");
}
with the commented lines xPos += d3.event.dx (and y) it works fine but not if I like to calculate the raster before.
I have no idea why, but then I can see in the console, that translate-attribute often only has one Parameter or is empty. like this:
transform"translate(30)" or transform=""
I built a (non) working example here:
https://jsfiddle.net/Telefisch/3oecj6wd/
Thank you
I am using d3 to create a diagram to try and speed up a nearest nabour search within a function that plots points on a plane.
Is there a way to add points directly to the diagram so I can add the points within a while loop instead of re-drawing the entire voronoi?
var svg = d3.select("svg")
var distance = function(pa, pb) {
var x = pa[0] - pb[0],
y = pa[1] - pb[1]
return Math.sqrt((x * x) + (y * y))
}
var scatterCircle = function(point, radius, quantity, proximity, margin) {
var x1 = point[0] - radius,
y1 = point[1] - radius,
inner = radius * margin,
array = [
[500, 500]
]
//should be declaring diagram here and addings points below//
while (array.length < quantity) {
//constructing new diagram each loop to test function, needs add to diagram function//
var newpoly = d3.voronoi()(array),
x = x1 + (radius * 2 * Math.random()),
y = y1 + (radius * 2 * Math.random()),
ii = newpoly.find(x, y).index
var d = distance(array[ii], [x, y]),
e = distance([x, y], point)
if (e < inner) {
if (d > proximity) {
array.push([x, y])
}
}
}
return array
}
var test = scatterCircle([500, 500], 500, 1500, 10, 0.9)
var o = 0
while (o < test.length) {
svg.append("circle")
.attr("cx", test[o][0])
.attr("cy", test[o][1])
.attr("r", 1)
o++
}
<script src="https://d3js.org/d3.v4.js"></script>
<svg width="1000" height="1000">
I am no expert in d3.js but I will share what I found out. The implemented algorithm for Voronoi diagrams is Fortune's algorithm. This is the classical algorithm to compute a Voronoi diagram. Inserting a new point is neither part of this algorithm nor of the function set documented for d3.js. But you are correct, inserting one new site does not require to redraw the whole diagram in theory.
You use the Voronoi diagram for NNS (nearest neighbour search). You could also use a 2d-tree to accomplish NNS. There insertion and removal is easier. A quick search revealed two implementations in javascript: kd-tree-javascript and kd-tree-js.
I'm quite new to D3 and been working through trying to figure everything out. I'm trying to configure this example here to update with new data and transition appropriately.
Here is the code pen I have configured (click the submit to update)
http://codepen.io/anon/pen/pbjLRW?editors=1010
From what I can gather, using some variation of .exit() is required for a clean data transition, but after reading some tutorials I'm still finding it difficult to know how it works. I have seen examples where simply removing the containers before calling the draw function works, but in my limited experience it can cause a flicker when changing data so I'm not sure if it's best practice?
Now, I'm not sure why the data is not updating correctly in my codepen, but my main concern is trying to get the transition right. Ideally I would like to know how I could just move the needle when changing data, so It would go from 90 > 40 for example, instead of 90 > 0 > 40.
However, I will definitely settle for figuring out why it doesn't redraw itself in the same location once clicking submit in the linked codepen.
Here is my update function;
function updateGuage() {
d3.selectAll("text").remove()
d3.selectAll('.needle').remove()
chart.remove()
name = "qwerty";
value = "25";
drawGuage();
}
initial draw;
function drawGuage() {
percToDeg = function(perc) {
return perc * 360;
};
percToRad = function(perc) {
return degToRad(percToDeg(perc));
};
degToRad = function(deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-first");
chart.append('path').attr('class', "arc chart-second");
chart.append('path').attr('class', "arc chart-third");
arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
repaintGauge = function() {
perc = 0.5;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc1.startAngle(arcStartRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
next_start += perc / 3;
arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 3);
arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
chart.select(".chart-third").attr('d', arc3);
}
/////////
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function() {
return dataset[0].metric;
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 6) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size", 25)
.style("fill", "#000000");
var trX = 180 - 210 * Math.cos(percToRad(percent / 2));
var trY = 195 - 210 * Math.sin(percToRad(percent / 2));
// (180, 195) are the coordinates of the center of the gauge.
displayValue = function() {
texts.append("text")
.text(function() {
return dataset[0].value;
})
.attr('id', "Value")
.attr('transform', "translate(" + trX + ", " + trY + ")")
.attr("font-size", 18)
.style("fill", '#000000');
}
texts.append("text")
.text(function() {
return 0;
})
.attr('id', 'scale0')
.attr('transform', "translate(" + ((width + margin.left) / 100) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function() {
return gaugeMaxValue / 2;
})
.attr('id', 'scale10')
.attr('transform', "translate(" + ((width + margin.left) / 2.15) + ", " + ((height + margin.top) / 30) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function() {
return gaugeMaxValue;
})
.attr('id', 'scale20')
.attr('transform', "translate(" + ((width + margin.left) / 1.03) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
var Needle = (function() {
//Helper function that returns the `d` value for moving the needle
var recalcPointerPos = function(perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = 0;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
function Needle(el) {
this.el = el;
this.len = width / 2.5;
this.radius = this.len / 8;
}
Needle.prototype.render = function() {
this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));
};
Needle.prototype.moveTo = function(perc) {
var self,
oldValue = this.perc || 0;
this.perc = perc;
self = this;
// Reset pointer position
this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function() {
return function(percentOfPercent) {
var progress = (1 - percentOfPercent) * oldValue;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function() {
return function(percentOfPercent) {
var progress = percentOfPercent * perc;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
};
return Needle;
})();
needle = new Needle(chart);
needle.render();
needle.moveTo(percent);
setTimeout(displayValue, 1350);
}
Any help/advice is much appreciated,
Thanks
What you wanna check out is How selections work written by Mike Bostock. After reading this article, everything around enter, update and exit selections will become clearer.
In a nutshell:
You create a selection of elements with selectAll('li')
You join the selection with a data array through calling data([...])
Now D3 compares what's already in the DOM with the joined data. Each DOM element processed this way has a __data__ property that allows D3 to bind a data item to an element.
After you've joined data, you receive the enter selection by calling enter(). This is every data element that has not yet been bound to the selected DOM elements. Typically you use the enter selection to create new elements, e.g. through append()
By calling exit() you receive the exit selection. These are all already existing DOM elements which no longer have an associated data item after the join. Typically you use the exit selection to remove DOM elements with remove()
The so called update selection is the one thing that's been returned after joining the selection with data(). You will want to store the update selection in a variable, so you have access to it even after calling enter() or exit().
Note the difference between d3v3 and d3v4:
In d3v3, when you've already added elements via the enter selection, the update selection includes those newly created DOM elements as well. It's crucial to know that the update selection changes after you've created new elements.
However, this is no longer true when using d3v4. The change log says
"In addition, selection.append no longer merges entering nodes into the update selection; use selection.merge to combine enter and update after a data join."
It is important to know that there are three different operations you can perform after binding data. Handle additions, deletions and modify things that did not change (or have been added just before).
Here is an example creating and manipulating a simple list: http://jsbin.com/sekuhamico/edit?html,css,js,output
var update = () => {
// bind data to list elements
// think of listWithData as a virtual representation of
// the array of list items you will later see in the
// DOM. d3.js does not handle the mapping from this
// virtual structure to the DOM for you. It is your task
// to define what is to happen with elements that are
// added, removed or updated.
var listWithData = ul.selectAll('li').data(listItems);
// handle additions
// by calling enter() on our virtual list, you get the
// subset of entries which need to be added to the DOM
// as their are not yet present there.
listWithData.enter().append('li').text(i => i.text).on('click', i => toggle(i));
// handle removal
// by calling exit() on our virtual list, you get the
// subset of entries which need to be removed from the
// DOM as they are not longer present in the virtual list.
listWithData.exit().remove();
// update existing
// acting directly on the virtual list will update any
// elements currently present in the DOM. If you would
// execute this line before calling exit(), you would
// also manipulate those items to be removed. If you
// would even call it before calling enter() you would
// miss on updating the newly added element.
listWithData.attr('class', i => i.active ? 'active' : '');
};
Be aware that in reality you probably need to add some sort of id to your items. To ensure the right items are removed and you do not get ordering issues.
Explanation
The update function knows nothing about what, or even if anything has changed. It does not know, nor care, if there are new data elements or if old ones have been removed. But both things could happen. Therefore we handle both cases by calling enter() and exit() respectively. The d3 functions enter() and exit() provides us with the subsets of list elements that should be added or removed. Finally we need to take care of changes in the existing data.
var listItems = [{ text: 1, active: false}, { text: 2, active: true}];
var ul = d3.select('#id').append('ul');
var update = () => {
var listWithData = ul.selectAll('li').data(listItems);
// add new
listWithData.enter().append('li').text(i => i.text).on('click', i => toggle(i));
// remove old
listWithData.exit().remove();
// update existing
listWithData.attr('class', i => i.active ? 'active' : '');
};
update();
$('#add').click(() => {
listItems.push({
text: listItems.length+1,
active: false
});
update();
});
var toggle = (i) => {
i.active = !i.active;
update();
};
li.active {
background-color:lightblue;
}
li {
padding: 5px;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="id"></div>
<button id="add">Add</button>
</body>
</html>
I have this code:
var sets = [
{sets: ['A'], size: 10},
{sets: ['B'], size: 10},
{sets: ['A','B'], size: 5}
];
var chart = venn.VennDiagram();
var div = d3.select("#venn").datum(sets).call(chart);
using excellent venn.js library, my venn diagram is drawn and works perfectly.
using this code:
div.selectAll("g")
.on("mouseover", function (d, i) {
// sort all the areas relative to the current item
venn.sortAreas(div, d);
// Display a tooltip with the current size
tooltip.transition().duration(400).style("opacity", .9);
tooltip.text(d.size + " items");
// highlight the current path
var selection = d3.select(this).transition("tooltip").duration(400);
selection.select("path")
.style("stroke-width", 3)
.style("fill-opacity", d.sets.length == 1 ? .4 : .1)
.style("stroke-opacity", 1)
.style("cursor", "pointer");
})
.on("mousemove", function () {
tooltip.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("click", function (d, i) {
window.location.href = "/somepage"
})
.on("mouseout", function (d, i) {
tooltip.transition().duration(400).style("opacity", 0);
var selection = d3.select(this).transition("tooltip").duration(400);
selection.select("path")
.style("stroke-width", 1)
.style("fill-opacity", d.sets.length == 1 ? .25 : .0)
.style("stroke-opacity", 0);
});
I'm able to add Click, mouseover,... functionality to my venn.
Here is the problem:
Adding functionality to Circles (Sets A or B) works fine.
Adding functionality to Intersection (Set A intersect Set B) works fine.
I need to add some functionality to Except Area (set A except set B)
This question helped a little: 2D Polygon Boolean Operations with D3.js SVG
But I had no luck making this work.
Tried finding out Except area using: clipperjs or Greiner-Hormann polygon clipping algorithm but couldn't make it work.
Update 1:
The code in this question is copied from venn.js sample: http://benfred.github.io/venn.js/examples/intersection_tooltip.html
Other samples:
https://github.com/benfred/venn.js/
Perhaps you can do something like this....
Given 2 overlapping circles,
Find the two intersection points, and
Manually create a path that arcs from IP1 to IP2 along circle A and then from IP2 back to IP1 along circle B.
After that path is created (that covers A excluding B), you can style it however you want and add click events (etc.) to that SVG path element.
FIND INTERSECTION POINTS (IPs)
Circle-circle intersection points
var getIntersectionPoints = function(circleA, circleB){
var x1 = circleA.cx,
y1 = circleA.cy,
r1 = circleA.r,
x2 = circleB.cx,
y2 = circleB.cy,
r2 = circleB.r;
var d = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)),
a = (Math.pow(r1,2)-Math.pow(r2,2)+Math.pow(d,2))/(2*d),
h = Math.sqrt(Math.pow(r1,2)-Math.pow(a,2));
var MPx = x1 + a*(x2-x1)/d,
MPy = y1 + a*(y2-y1)/d,
IP1x = MPx + h*(y2-y1)/d,
IP1y = MPy - h*(x2-x1)/d,
IP2x = MPx - h*(y2-y1)/d,
IP2y = MPy + h*(x2-x1)/d;
return [{x:IP1x,y:IP1y},{x:IP2x,y:IP2y}]
}
MANUALLY CREATE PATH
var getExclusionPath = function(keepCircle, excludeCircle){
IPs = getIntersectionPoints(keepCircle, excludeCircle);
var start = `M ${IPs[0].x},${IPs[0].y}`,
arc1 = `A ${keepCircle.r},${keepCircle.r},0,1,0,${IPs[1].x},${IPs[1].y}`,
arc2 = `A ${excludeCircle.r},${excludeCircle.r},0,0,1,${IPs[0].x},${IPs[0].y}`,
pathStr = start+' '+arc1+' '+arc2;
return pathStr;
}
var height = 900;
width = 1600;
d3.select(".plot-div").append("svg")
.attr("class", "plot-svg")
.attr("width", "100%")
.attr("viewBox", "0 0 1600 900")
var addCirc = function(circ, color){
d3.select(".plot-svg").append("circle")
.attr("cx", circ.cx)
.attr("cy", circ.cy)
.attr("r", circ.r)
.attr("fill", color)
.attr("opacity", "0.5")
}
var getIntersectionPoints = function(circleA, circleB){
var x1 = circleA.cx,
y1 = circleA.cy,
r1 = circleA.r,
x2 = circleB.cx,
y2 = circleB.cy,
r2 = circleB.r;
var d = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)),
a = (Math.pow(r1,2)-Math.pow(r2,2)+Math.pow(d,2))/(2*d),
h = Math.sqrt(Math.pow(r1,2)-Math.pow(a,2));
var MPx = x1 + a*(x2-x1)/d,
MPy = y1 + a*(y2-y1)/d,
IP1x = MPx + h*(y2-y1)/d,
IP1y = MPy - h*(x2-x1)/d,
IP2x = MPx - h*(y2-y1)/d,
IP2y = MPy + h*(x2-x1)/d;
return [{x:IP1x,y:IP1y},{x:IP2x,y:IP2y}]
}
var getExclusionPath = function(keepCircle, excludeCircle){
IPs = getIntersectionPoints(keepCircle, excludeCircle);
var start = `M ${IPs[0].x},${IPs[0].y}`,
arc1 = `A ${keepCircle.r},${keepCircle.r},0,1,0,${IPs[1].x},${IPs[1].y}`,
arc2 = `A ${excludeCircle.r},${excludeCircle.r},0,0,1,${IPs[0].x},${IPs[0].y}`,
pathStr = start+' '+arc1+' '+arc2;
return pathStr;
}
var circleA = {cx: 600, cy: 500, r: 400};
var circleB = {cx: 900, cy: 400, r: 300};
var pathStr = getExclusionPath(circleA, circleB)
addCirc(circleA, "steelblue");
addCirc(circleB, "darkseagreen");
d3.select(".plot-svg").append("text")
.text("Hover over blue circle")
.attr("font-size", 70)
.attr("x", 30)
.attr("y", 70)
d3.select(".plot-svg").append("path")
.attr("class","exlPath")
.attr("d", pathStr)
.attr("stroke","steelblue")
.attr("stroke-width","10")
.attr("fill","white")
.attr("opacity",0)
.plot-div{
width: 50%;
display: block;
margin: auto;
}
.plot-svg {
border-style: solid;
border-width: 1px;
border-color: green;
}
.exlPath:hover {
opacity: 0.7;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class="plot-div">
</div>
If you have more complex overlapping in your Venn diagrams (3+ region overlap) then this obviously gets more complicated, but I think you could still extend this approach for those situations.
Quick (sort of) note on to handle 3 set intersections ie. A∩B\C or A∩B∩C
There are 3 "levels" of overlap between A∩B and circle C...
Completely contained in C | Both AB IPs are in C
Partial overlap; C "cuts through" A∩B | Only one AB IP is in C
A∩B is completely outside C | No AB IPs are in C
Note: This is assuming C is not a subset of or fully contained by A or B -- otherwise, for example, both BC IPs could be contained in A
In total, you'll need 3 points to create the path for the 3 overlapping circles. The first 2 are along C where it "cuts through" A∩B. Those are...
The BC intersection point contained in A
The AC intersection point contained in B
For the 3rd point of the path, it depends if you want (i)A∩B∩C or (ii)A∩B\C...
(i) A∩B∩C: The AB intersection point contained in C
(ii) A∩B\C: The AB intersection point NOT contained in C
With those the points you can draw the path manually with the appropriate arcs.
Bonus -- Get ANY subsection for 2 circles
It's worth noting as well that you can get any subsection by choosing the right large-arc-flag and sweep-flag. Picked intelligently and you'll can get...
Circle A (as path)
Circle B (as path)
A exclude B --in shown example
B exclude A
A union B
A intersect B
... as well as a few more funky ones that won't match anything useful.
Some resources...
W3C site for elliptical curve commands
Good explanation for arc flags
Large-arc-flag: A value of 0 means to use the smaller arc, while a value of 1 means use the larger arc.
Sweep-flag: The sweep-flag determines whether to use an arc (0) or its reflection around the axis (1).
When I was trying out d3 force directed layout, I came across a challenge where
I want to zoom this svg. But Its quite tough for me to integrate.I want to remove the scrollers and put zoom for the graph.
http://nylen.tv/d3-process-map/graph.php
I want something like this which i can zoom,
http://cpettitt.github.io/project/dagre-d3/latest/demo/tcp-state-diagram.html
Below is the code where i integrate the graph in svg,
graph.svg = d3.select('#graph').append('svg')
.attr('width' , graph.width + graph.margin.left + graph.margin.right+500)
.attr('height', graph.height + graph.margin.top + graph.margin.bottom)
.append('g')
.attr('transform', 'translate(' + graph.margin.left + ',' + graph.margin.top + ')');
The Second link has something like this which implements zoom,
var svg = d3.select("svg"),
inner = svg.select("g");
// Set up zoom support
var zoom = d3.behavior.zoom().on("zoom", function() {
);
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.call(zoom
Below is the code I inspected from the link you provided(http://nylen.tv/d3-process-map/graph.php) from a file called script.js, it is not minified :)
obj.positionConstraints.push({
weight : c.weight,
x : c.x * graph.width,
y : c.y * graph.height
});
They are manually calculating the x & y positions as shown above. Their tick function has the following code:
for (var name in graph.data) {
var obj = graph.data[name];
obj.positionConstraints.forEach(function(c) {
var w = c.weight * e.alpha;
if (!isNaN(c.x)) {
obj.x = (c.x * w + obj.x * (1 - w));
}
if (!isNaN(c.y)) {
obj.y = (c.y * w + obj.y * (1 - w));
}
});
}