Appending element clone to svg group in d3js - javascript

I am able to append a rectangle to group on drag drop but it is not working properly. If I drag and drop multiple rectangles on to group, the rectangles are dislocated at different location. I am sure there must be some thing wrong.
Demo: http://jsfiddle.net/wqvLLbLa/
code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
var rect = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 50)
.attr("width", 51)
.attr("height", 41)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.style("fill", "white");
function moveRect() {
d3.select(this)
.attr('x', d3.event.x)
.attr('y', d3.event.y);
}
var dragGroup = d3.behavior.drag()
.origin(function () {
var g = this;
return {x: d3.transform(g.getAttribute("transform")).translate[0],
y: d3.transform(g.getAttribute("transform")).translate[1]};
})
.on("drag", function (d, i) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
console.log(translate);
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
});
var group = svgContainer.append("g")
.attr("id", "mygroup")
.call(dragGroup)
.style('cursor', 'move')
.attr("transform", "translate(20, 20)");
group.append("rect")
.attr("x", 250)
.attr("y", 250)
.attr("width", 151)
.attr("height", 141)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style("fill", "white");
var circleDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
})
var rectDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
var groupTransform = d3.transform(group.attr("transform"));
var groupX = groupTransform.translate[0];
var groupY = groupTransform.translate[1];
var rect = group.select("rect");
var rectX = +rect.attr("x");
var rectY = +rect.attr("y");
var rectWidth = +rect.attr("width");
var rectHeight = +rect.attr("height");
if (mouseCoordinates[0] > groupX + rectX
&& mouseCoordinates[0] < groupX + rectX + rectWidth
&& mouseCoordinates[1] > groupY + rectY
&& mouseCoordinates[1] < groupY + rectY + rectHeight) {
//Append new element
var newRect = d3.select("g").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483");
}
else
{
var newRect = d3.select("svg").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483")
.call(
d3.behavior.drag()
.on('drag', moveRect).origin(function () {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
}));
}
});
rect.call(rectDrag);

Responding to the update question - as in the comments.
The reason for the duplicates is because you are appending a new element to the targetG after your bounds check, instead of targetCircle.
The easiest way to fix this would be to simply remove targetCircle after appending the new circle like so
targetCircle.remove();
Fiddle - http://jsfiddle.net/afmLhofL/

Related

Path with rounded corners in Javascript d3js

I would like to create a rounded edge for a corner where the user can specify the corner's radius in D3js.
I found a post that has potential solutions, but the examples are in Observable notebook.
I tried converting to plain Javascript. But it didn't work for me.
https://observablehq.com/#carpiediem/svg-paths-with-circular-corners
Any help is much appreciated, thanks.
I think the post you shared may be overly complicated. Assuming you are using d3.line() or d3.area(), I would suggest looking into the different curve interpolators available in D3. Many of them allow an extra parameter to specify, for example, a tension that can be manipulated.
Here it is:
const drag = () => {
function dragstarted(d) {
d3.select(this).raise().attr("stroke", "black");
}
function dragged(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
d3.select('path.angled')
.attr('d', 'M' + points.map(d => `${d.x} ${d.y}`).join(','));
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const manualPathDesc = `
M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
d3.select('path.arced').attr('d', manualPathDesc);
d3.select('rect.anchor.in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3);
d3.select('rect.anchor.out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
d3.select('path.triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`);
d3.select('text.angle').text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
d3.select('text.shortest').text(Math.round(shortestRay));
d3.select('text.maxradius').text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
d3.select('text.toAnchor').text(Math.round(distanceToTangentPoint));
d3.select('text.determinate').text(determinant < 0 ? 'neg.' : 'pos.');
}
function dragended(d) {
d3.select(this).attr("stroke", null);
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
function alongSegment(from, toward, distanceAlong) {
const bearing = Math.atan2(from.y-toward.y, from.x-toward.x);
return {
bearing,
x: from.x - distanceAlong * Math.cos(bearing),
y: from.y - distanceAlong * Math.sin(bearing)
};
}
const chart = () => {
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
const svg = d3.select("svg")
.attr("viewBox", [0, 0, width, height]);
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
const manualPathDesc = `M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
svg.append('rect')
.attr("x", 8)
.attr("y", 10)
.attr("width", 160)
.attr("height", 105)
.attr("fill", '#eee');
svg.append('text')
.attr("class", 'angle')
.attr("x", 35)
.attr("y", 25)
.attr("text-anchor", "end")
.text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
svg.append('text')
.attr("class", 'shortest')
.attr("x", 35)
.attr("y", 45)
.attr("text-anchor", "end")
.text(Math.round(shortestRay));
svg.append('text')
.attr("class", 'maxradius')
.attr("x", 35)
.attr("y", 65)
.attr("text-anchor", "end")
.text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
svg.append('text')
.attr("class", 'toAnchor')
.attr("x", 35)
.attr("y", 85)
.attr("text-anchor", "end")
.text(Math.round(distanceToTangentPoint));
svg.append('text')
.attr("class", 'determinate')
.attr("x", 35)
.attr("y", 105)
.attr("text-anchor", "end")
.text(determinant < 0 ? 'neg.' : 'pos.');
svg.append('text')
.attr("x", 40)
.attr("y", 25)
.attr("text-anchor", "start")
.text('angle between rays');
svg.append('text')
.attr("x", 40)
.attr("y", 45)
.attr("text-anchor", "start")
.text('length of shortest ray');
svg.append('text')
.attr("x", 40)
.attr("y", 65)
.attr("text-anchor", "start")
.text('max radius, to fit');
svg.append('text')
.attr("x", 40)
.attr("y", 85)
.attr("text-anchor", "start")
.text('from vertex to anchors');
svg.append('text')
.attr("x", 40)
.attr("y", 105)
.attr("text-anchor", "start")
.text('determinant value');
svg.append('path')
.attr("class", 'arced')
.datum(points)
.attr("d", manualPathDesc)
.attr("stroke", 'orange')
.attr("stroke-width", 5)
.attr("fill", 'none');
svg.append('path')
.attr("class", 'angled')
.attr("d", 'M' + points.map(d => `${d.x} ${d.y}`).join(', '))
.attr("stroke", '#888')
.attr("fill", 'none');
svg.append('rect')
.attr("class", 'anchor in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#888');
svg.append('rect')
.attr("class", 'anchor out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#ccc');
svg.append('path')
.attr("class", 'triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`)
.attr("stroke", '#ccc')
.attr("fill", 'none');
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 6)
.attr("fill", (d, i) => color(i))
.on("mouseover", function (d) {d3.select(this).style("cursor", "move");})
.on("mouseout", function (d) {})
.call(drag());
return svg.node();
}
const width = 1000;
const height = 600;
const cornerRadius = 50;
const points = d3.range(3).map(i => ({
x: Math.random() * (width - 10 * 2) + 10,
y: Math.random() * (300 - 10 * 2) + 10,
}));
chart();
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="1000" height="600"></svg>

d3 tooltip - appended circle failing to follow values of line graph

I have a vertical line graph in d3 and I'm trying to add a tooltip in the form of a line and circle that follows the cursor movement in the vertical. The latter works but the circle fails to follow the path - I've tried different variations but the circle never follows the path, it's currently just following the y-axis of the graph (see attached image for example). I've achieved the same effect for a horizontal plot but when I try to adapt the code for a vertical graph I just can't get the circle to work properly.
I've brought an example together with the code below, still very new to javascript so code is a bit of a mess.
Screenshot of graph with circle (red) failing to follow the path:
function test(test_data) {
// setup params
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
d3.csv(test_data,
function(d){
return { output_time_ref: d.output_time_ref = +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: d.prediction = +d.prediction,
}
},
function(data) {
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.prediction; })])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.output_time_ref; })])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
///////////////////////////////////////////////////
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
///////////////////////////////////////////////////
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
})
}
test_data.csv:
output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0
You can precisely compute x by y using the input data:
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
...
}
See it's working in the snippet:
const csvData = `output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0`;
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
const point = line_graph.append('circle')
.attr('r', 5)
.style('fill', 'red');
const data = d3.csvParse(csvData).map(d => ({
output_time_ref: +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: +d.prediction,
}));
console.log(data);
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, d => d.prediction)])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, d => +d.output_time_ref)])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
line_graph.on('mousemove', () => {
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
// console.log(xPos);
point
.attr('cx', xPos)
.attr('cy', y)
}
// line_graph
// y_test
})
/*
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave);
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="my_dataviz_test" />

create and drag in same click

I would like to create an application like scratch or node-red, with D3.js, by this I mean create some svg elements by clicking on a 'button list' to create an element and then drag them over an area to arrange them.
This idea is working with my code below. I can click to create shapes (svg group). Once created, I can click on them (AGAIN) and drag it over svg area.
But, I want to mimic the behavior of same apps node-red and scratch, by dragging the new svg element with the same click used to create it. Sparing a click, in one word. But I don't know how to start drag behavior programmatically on the element created. Here is my working code.
var svg = d3.select("body").append("svg")
.attr("width", 1500)
.attr("height", 800);
addButton(svg, 'ADD');
function addShape(svg, x, y) {
var dotContainer = svg.append("g")
.attr("class", "dotContainer")
.datum({
x: x,
y: y
})
.attr("transform", function(d) {
return 'translate(' + d.x + ' ' + d.y + ')';
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var text = dotContainer.append("text")
.datum({
x: 20,
y: 20
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.text('Title');
var rectangle = dotContainer.append("rect")
.attr("width", 200)
.attr("height", 100)
.attr("x", 0)
.attr("y", 0)
.attr('style', "opacity:1;fill:#ffffff;fill-opacity:0;stroke:#000000;stroke-width:5;stroke-opacity:1")
.attr("ry", 8);
return dotContainer;
}
function dragstarted(d) {
let xCoord = d3.event.dx - d3.select(this).attr('x')
let yCoord = d3.event.dy - d3.select(this).attr('y')
}
function dragged(d) {
d3.select(this).select("text").text(d.x + ';' + d.y);
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d, i) {
return "translate(" + [d.x, d.y] + ")"
});
}
function dragended(d) {
d3.select(this).attr("transform", function(d, i) {
return "translate(" + [d.x, d.y] + ")"
});
}
function addButton(area, title) {
var group = area.append("g");
group.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 50)
.attr('style', 'fill:rgb(255,0,0);stroke-width:1;stroke:rgb(200,200,200)');
group.append("text")
.attr('x', 20)
.attr('y', 20)
.text(title);
group.on('mousedown', function() {
var grp = addShape(area, 0, 0);
//START DRAG ON grp HERE ???
});
}
<script src="https://d3js.org/d3.v5.min.js"></script>
So, my issue is here that I can't figure out how to call dragstarted() from outside of svg group dotContainer, since dragstarted use this and d, which refers to the svg group. Or use a complete other way to achieve this? I am lost here....
Thanks,
When in doubt, you can always reach back to vanilla JavaScript. In this case, you can dispatch a custom MouseDown event using the d3.event object as the attribute dictionary, essentially cloning the element.
Then, the MouseMove events take over and are processed seamlessly:
var svg = d3.select("body").append("svg")
.attr("width", 1500)
.attr("height", 800);
addButton(svg, 'ADD');
function addShape(svg, x, y) {
var dotContainer = svg.append("g")
.attr("class", "dotContainer")
.datum({
x: x,
y: y
})
.attr("transform", function(d) {
return 'translate(' + d.x + ' ' + d.y + ')';
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var text = dotContainer.append("text")
.datum({
x: 20,
y: 20
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.text('Title');
var rectangle = dotContainer.append("rect")
.attr("width", 200)
.attr("height", 100)
.attr("x", 0)
.attr("y", 0)
.attr('style', "opacity:1;fill:#ffffff;fill-opacity:0;stroke:#000000;stroke-width:5;stroke-opacity:1")
.attr("ry", 8);
return dotContainer;
}
function dragstarted(d) {
let xCoord = d3.event.dx - d3.select(this).attr('x')
let yCoord = d3.event.dy - d3.select(this).attr('y')
}
function dragged(d) {
d3.select(this).select("text").text(d.x + ';' + d.y);
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d, i) {
return "translate(" + [d.x, d.y] + ")"
});
}
function dragended(d) {
d3.select(this).attr("transform", function(d, i) {
return "translate(" + [d.x, d.y] + ")"
});
}
function addButton(area, title) {
var group = area.append("g");
group.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 50)
.attr('style', 'fill:rgb(255,0,0);stroke-width:1;stroke:rgb(200,200,200)');
group.append("text")
.attr('x', 20)
.attr('y', 20)
.text(title);
group.on('mousedown', function() {
var grp = addShape(area, 0, 0);
grp.node().dispatchEvent(new MouseEvent(
"mousedown",
d3.event
));
});
}
<script src="https://d3js.org/d3.v5.js"></script>
Yo could listen for a mousedown on the button used to create the new shape. In the event listener, you create a new shape and create a new mousedown event which you dispatch immediately on the new element. This new mousedown event will trigger the drag behavior, triggering the drag-start listener once and the drag listener continuously until the mouse is raised. This could look like:
select.on("mousedown", function(event,d) {
// create some new shape:
var aNewShape = container.append("shape")
.attr(...)
....
// create a new event and dispatch it on the new shape
var e = document.createEvent("MouseEvents");
e.initMouseEvent("mousedown", true,true,window,0,0,0,event.x,event.y)
aNewShape.node().dispatchEvent(e)
})
Which could look something like:
var svg = d3.select("body")
.append("svg")
.attr("width",400)
.attr("height", 300);
var data = [
{shape: d3.symbolCross, y: 0, cy: 25, cx: 25},
{shape: d3.symbolWye, y: 60, cy: 85, cx: 25 },
{shape: d3.symbolDiamond, y: 120, cy: 145, cx: 25}
]
// Add some buttons:
var g = svg.selectAll("null")
.data(data)
.enter()
.append("g")
.attr("transform",function(d,i) {
return "translate("+[0,d.y]+")";
})
g.append("rect")
.attr("width", 50)
.attr("height", 50)
.attr("fill", "#ddd");
g.append("path")
.attr("d", function(d) { return d3.symbol().type(d.shape).size(100)(); })
.attr("transform","translate(25,25)")
.attr("fill", "#aaa");
// Some sort of drag function
var drag = d3.drag()
.on("drag", function(event,d) {
d.x = event.x;
d.y = event.y;
d3.select(this).attr("transform", "translate("+[d.x,d.y]+")");
})
.on("start", function() {
d3.select(this).transition()
.attr("fill","steelblue")
.duration(1000);
})
// Mouse down event:
g.on("mousedown", function(event,d) {
var shape = svg.append("path")
.datum({type:d.shape,x:d.cx,y:d.cy})
.attr("d", d3.symbol().type(d.shape).size(300)())
.attr("transform", function(d) { return "translate("+[d.x,d.y]+")" })
.attr("fill","black")
.call(drag);
var e = document.createEvent("MouseEvents");
e.initMouseEvent("mousedown", true,true,window,0,0,0,event.x,event.y)
shape.node().dispatchEvent(e);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>

d3.js Tesselations and Image positioning/scaling

//First part is to create tessellation shapes.
http://jsfiddle.net/NYEaX/1526/
this code creates a tessellation shape of rects - and a circle -- but there must be a cleaner and better way of looping through rects first placing a set of cols and rows.
$(document).ready(function() {
function maskMaker(el) {
var backcolor = $(el).data("color");
var backopacity = $(el).data("opacity");
// Set the main elements for the series chart
var svgroot = d3.select($(el)[0]).append("svg");
var mask = svgroot
.append("defs")
.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "1200px")
.attr("height", 500)
.style("fill", "white")
.style("opacity", backopacity);
mask.append("circle")
.attr("cx", 350)
.attr("cy", 250)
.attr("r", 70);
function tesselateShape(mask, shape, x, y, size) {
if (shape == "rect") {
mask.append("g")
.attr("transform", "translate(30) rotate(45 50 50)")
.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", size)
.attr("height", size);
}
}
var size = 200;
var xStart = 500;
var yStart = -500;
var shapes = ["rect", "rect", "rect", "rect", "rect", "rect"];
var batch = 3; //group in 3 and then move into a new col.
var batchCount = 1;
for (i = 0; i < shapes.length; i++) {
tesselateShape(mask, "rect", xStart, yStart, size);
yStart += size;
xStart += size;
if (i % batch) {
var xStart = 500 + (-size * batchCount);
var yStart = -500 + (size * batchCount);
batchCount++;
}
}
/*
//col1
tesselateShape(mask, "rect", 400, -600, 200);
tesselateShape(mask, "rect", 600, -400, 200);
tesselateShape(mask, "rect", 800, -200, 200);
//col2
tesselateShape(mask, "rect", 600, -800, 200);
tesselateShape(mask, "rect", 800, -600, 200);
tesselateShape(mask, "rect", 1000, -400, 200);
*/
var svg = svgroot
.attr("class", "series")
.attr("width", "1200px")
.attr("height", "500px")
.append("g")
.attr("transform", "translate(0,0)")
var rect = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "1200px")
.attr("height", 500)
.attr("mask", "url(#myMask)")
.style("fill", backcolor);
}
//var el = $(".mask"); //selector
$('[data-role="mask"]').each(function(index) {
console.log("test")
maskMaker(this);
});
});
//Second part is to add in a rounded masked image and label, pointer
Would involve a def pattern part
http://jsfiddle.net/NYEaX/652/
[{
"label": "Google Street View",
"link": "https://maps.googleapis.com/maps/api/streetview?size=600x300&location=46.414382,10.013988&heading=151.78&pitch=-0.76"
}]
a json blob like
var defs = patternsSvg.append('svg:defs');
defs.append('svg:pattern')
.attr('id', index+"-"+value.userName.toLowerCase())
.attr('width', 65)
.attr('height', 65)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', 65)
.attr('height', 65);
labels and pointer
//__labels
var labels = mask.append("g")
.attr("class", "labels")
//__ enter
var labels = labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
//__ update
labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.text(function(d) {
return d.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
//__ exit
labels.exit().remove();
//__labels
//__labels
//__pointers
var pointers = mask.append("g")
.attr("class", "pointers")
pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = pointers.selectAll("path.pointer")
.data(data);
//__ enter
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
//__ update
pointers
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
//__ exit
pointers.exit().remove();
//__pointers

How to drag and drop a rectangle

Unable to drag rectangle in d3js. The code looks fine to me. What I am I missing here?
All the drag behaviors like start drag and drag end are implemented but still the drag is not working
Here is my code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
var rect = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 50)
.attr("width", 51)
.attr("height", 41)
.attr("stroke", "#7E7E7E")
.attr("cursor", "move")
var drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", drag)
.on("dragend", dragend);
function dragstart()
{
d3.event.sourceEvent.stopPropagation();
}
function drag()
{
var self = d3.select(this);
//var translate =d3.transform(self.getAttribute("transform")).translate;
var translate = d3.transform(self.attr("transform")).translate;
var x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
self.attr("transform", "translate(" + x + "," + y + ")");
}
function dragend()
{
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 170)
{
//Append new element
svg.append("g").append("rect")
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.attr("width", 51)
.attr("height", 41)
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.classed("initialDragRect", true)
.call(drag);
}
}
rect.call(drag);
Change the translate variable declaration on your code in function drag into this
var translate = d3.transform(self.attr("transform")).translate;
The problem is because the self variable does not have the function getAttribute()

Categories