Related
I'm writing code for the first time with SVG. I created a small program in javascript. The rectangle does not start perfectly from the base of the area, remains a strip of light blue.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
#graphicArea {
width: 1400px;
background: #a7def2;
}
</style>
</head>
<body>
<div id="outer-wrapper">
<div id="graphicArea"> </div>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var width = 1400;
var height = 600;
var graphic;
var gocceAlSec = 7;
graphic = d3.select("#graphicArea").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "graphic")
.attr("overflow", "hidden");
var dataset = [0];
graphic.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", 600)
.attr("width", 1400)
.attr("height", 0)
.style("fill", "blue")
.transition()
.duration(50000)
.attr("height", 600)
.attr("y", 0);
</script>
</body>
You're setting the background colour to the <div>, and because of that you'll have to deal with default margins, paddings, computed height etc...
A way simpler approach is setting the background colour to the SVG:
graphic = d3.select("#graphicArea").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "graphic")
.style("background", "#a7def2")
Here is your code with that change:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="outer-wrapper">
<div id="graphicArea"> </div>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var width = 1400;
var height = 600;
var graphic;
var gocceAlSec = 7;
graphic = d3.select("#graphicArea").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "graphic")
.style("background", "#a7def2")
.attr("overflow", "hidden");
var dataset = [0];
graphic.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", 600)
.attr("width", 1400)
.attr("height", 0)
.style("fill", "blue")
.transition()
.duration(50000)
.attr("height", 600)
.attr("y", 0);
function makeRain() {
for (var i = 0; i < gocceAlSec; i++) {
startX = Math.random() * width,
startY = Math.random() * 100 - 100,
endX = startX;
endY = height + 200;
graphic.insert("circle")
.attr("cx", startX)
.attr("cy", startY)
.attr("r", 2)
.style("fill", "blue")
.transition()
.duration(2000)
.attr("cx", endX + 100)
.attr("cy", endY)
.remove();
};
}
d3.timer(makeRain, 100);
</script>
</body>
If you want to stick with the <div> style you can try some changes, like max-heigh: 600px;.
PS: Since this is your first D3/SVG code (by the way, kudos), here is a tip: you don't need an enter selection for the rect, not only because it's only one but mainly because the datum is meaningless. Just append the element to the container.
I would like to prevent the mouseout action of an element when the user click on this element. For an example see this JSFiddle (the circle disappear even if I click on the label).
Is there an easy way to achieve my objective with d3.js? Thank you!
The JSFiddle example code:
var svg = d3.select("#content")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
function mouseover(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
function mouseout(p){
d3.selectAll("circle.someClass").classed("hide", true);
}
function click(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
If you plan to only have one element that controls the circle, use a "flag". Messing with conditionally registering/unregistering events is not a good idea.
Check this updated version of your fiddle:
https://jsfiddle.net/cze9rqf7/
var svg = d3.select("#content")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var isClicked = false;
function mouseover(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
function mouseout(p){
if(!isClicked) {
d3.selectAll("circle.someClass").classed("hide", true);
}
}
function click(p){
isClicked = !isClicked;
d3.selectAll("circle.someClass").classed("hide", false);
}
EDITS For Comments
If you need to "remember" state per element, instead of using a global, you should be using data-binding on those elements:
var text = g.append("text")
.datum({isClicked: false})
.text("Click me")
...
function mouseout(p){
// p is the data-bound object
if(!p.isClicked) {
var className = d3.select(this).attr("class");
d3.selectAll("circle."+className).classed("hide", true);
}
}
function click(p){
// on click edit the data-bound object
p.isClicked = !p.isClicked;
var className = d3.select(this).attr("class");
d3.selectAll("circle."+className).classed("hide", false);
}
Updated fiddle here.
Here is an answer without any "flag":
Given that you have many labels, one option is removing the mouseout for the clicked element:
d3.select(this).on("mouseout", null);
Here is your updated fiddle: https://jsfiddle.net/gerardofurtado/38p18pLt/
And the same code in the Stack snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.classed("someClass", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var text2 = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 150)
.classed("someClass2", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var circle2 = g.append("circle")
.style("fill", "Green")
.attr("cx", 250)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass2", true);
function mouseover(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
function mouseout(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", true);
}
function click(p) {
d3.select(this).on("mouseout", null);
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
.hide {
display: none;
}
text {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You can also toggle the click, making the mouseout work again:
if(d3.select(this)[0][0].__onmouseout){
d3.select(this).on("mouseout", null);
} else {
d3.select(this).on("mouseout", mouseout);
}
Here is the fiddle with the "toggle" function: https://jsfiddle.net/gerardofurtado/4zb9gL9r/1/
And the same code in the Stack snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.classed("someClass", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var text2 = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 150)
.classed("someClass2", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var circle2 = g.append("circle")
.style("fill", "Green")
.attr("cx", 250)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass2", true);
function mouseover(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
function mouseout(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", true);
}
function click(p) {
if (d3.select(this)[0][0].__onmouseout) {
d3.select(this).on("mouseout", null);
} else {
d3.select(this).on("mouseout", mouseout);
}
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
.hide {
display: none;
}
text {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I have nodes (that are rectangles) with a right click menu, I am trying to change the color on the node depending on the outcome of the onclick menu but the nodes don't change colors. I have different ways/attempts in the code to see what I may be doing wrong. There are alot of forums with onclick doubleclick, but not with oncontextmenu. Any help would be great and thanks
var node = svg.selectAll("g.node")
.data(json.nodes)
.enter().append("g")
.attr("r", 12)
.attr("class", "node")
.attr("class", "gLink")
.call(node_drag)
.on('contextmenu', function(d,i) {
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(actions).enter()
.append('li')
.on('click' , function(d) {
if (d=="Force Succeed"){
alert(d);
d3.select(".node").style("fill", "#000");
}
else if (d=="Start Node"){
alert(d);
d3.select(this).style("fill", "#000000");
}
else if (d=="Start Tree"){
alert(d);
d3.select(this).style("fill", "#000");
}
else {
alert(d);
d3.select(this).style("fill", "#000");
}
})
.text(function(d) { return d; });
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
});
node.append("svg:rect")
.attr("x", function(d) { return -1 * (d.name.length * 10) / 2 })
.attr("y", -15)
.attr("rx", 5)
.attr("ry", 5)
.attr("width", function(d) { return d.name.length * 10; })
.attr("height", 20)
.style("fill", "#FFF")
.style("stroke", "#6666FF");
Just keep a reference to the object that was right-clicked for the context menu:
.on('contextmenu', function(d, i) {
var self = d3.select(this); //<-- keep this reference
d3.selectAll('.context-menu').data([1])
.enter()
...
.on('click', function(d) {
if (d == "Force Succeed") {
self.style("fill", "green"); //<-- use it later
...
Here's a functional example, extrapolated from your code snippet:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
var json = {};
json.nodes = [
0, 1, 2, 3
];
var actions = ["Force Succeed", "Start Node", "Start Tree"];
var node = svg.selectAll("g.node")
.data(json.nodes)
.enter().append("circle")
.attr("r", 12)
.attr("cx", function(d){
return d * 30 + 15;
})
.attr("cy", 15)
.on('contextmenu', function(d, i) {
var self = d3.select(this);
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(actions).enter()
.append('li')
.on('click', function(d) {
if (d == "Force Succeed") {
self.style("fill", "green");
} else if (d == "Start Node") {
self.style("fill", "red");
} else if (d == "Start Tree") {
self.style("fill", "blue");
} else {
self.style("fill", "#000");
}
})
.text(function(d) {
return d;
});
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block')
.style('position', 'absolute');
d3.event.preventDefault();
});
node.append("svg:rect")
.attr("x", function(d) {
return -1 * (3 * 10) / 2
})
.attr("y", -15)
.attr("rx", 5)
.attr("ry", 5)
.attr("width", function(d) {
return 3 * 10;
})
.attr("height", 20)
.style("fill", "#FFF")
.style("stroke", "#6666FF");
</script>
</body>
</html>
I am new in d3.js.I have created three circles. I want circle should be drag and drop.we can also create connection between circles using line. here is my code:
var spaceCircles = [30, 70, 110];
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
var circles = svgContainer.selectAll("circle")
.data(spaceCircles)
.enter()
.append("circle");
var circleAttributes = circles
.attr("cx", function (d) { return d; })
.attr("cy", function (d) { return d; })
.attr("r", 20 )
.style("fill", function(d) {
var returnColor;
if (d === 30) { returnColor = "green";
} else if (d === 70) { returnColor = "purple";
} else if (d === 110) { returnColor = "red"; }
return returnColor;
});
In order to achieve this, you must first attach a mousedown() function
circles.on('mousedown', function(d) {
//detect if mouse is on a circle i.e if(d.type === "circle")
})
Then a mousemove() function where you perform your drag action. Finally a mouseup() function where you add the link between the related nodes.
Check this example: http://bl.ocks.org/rkirsling/5001347
I have two overlapping svg.g groups with different onclick events. I periodically blend the groups in or out of the visualization using the opacity attribute. Currently, only the onclick event of the group that is rendered on top is called, but I would like to call the event for the group that is currently visible. Alternatively, I could always call both events and use a conditional statement inside the called function which depended on the opacity attribute.
Here is an example
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<div id="body"></div>
<script type="text/javascript">
var canvas_w = 1280 - 80,
canvas_h = 800 - 180;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var visible_group = svg.append("g")
.attr("opacity", 1)
.on("click", function(d){console.log("Click")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var invisible_group = svg.append("g")
.attr("opacity", 0)
.on("click", function(d){console.log("Invisiclick")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
</script>
</body>
</html>
This code will render a blue rectangle, the visible group. The group with the red rectangle is hidden. If you click on the blue rectangle, "Invisiclick" will be printed to the console, the onclick event of the hidden group. I would like to print "Click" to the console, or alternatively, both "Invisiclick" and "Click".
How can I do this?
Opacity does make the elements translucent, it doesn't make them disappear. Just as you can tap a piece of glass, you can click an element with opacity:0.
Now, there are two options, based on whether the shapes are different in both views. If they aren't (say, you're drawing a world map, the countries stay the same, just the color changes), it might be easiest to listen to the topmost layer and then run an if-statement which part to execute. Like this
var state = "blue";
var clickHandler = function() {
if(state === "blue") {
console.log("Blue clicked");
} else {
console.log("Red clicked");
}
}
var toggleState = function() {
state = (state === "blue") ? "red" : "blue";
}
var updateDisplay = function() {
blueGroup
.transition()
.duration(400)
.attr("opacity", state === "blue" ? 1 : 0);
redGroup
.transition()
.duration(400)
.attr("opacity", state === "red" ? 1 : 0);
}
var canvas_w = 1280 - 80,
canvas_h = 120;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var blueGroup = svg.append("g")
.append("rect")
.attr("opacity", 1)
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var redGroup = svg.append("g")
.on("click", clickHandler)
.append("rect")
.attr("opacity", 0)
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
d3.select("button").on("click", function() {
toggleState();
updateDisplay();
});
<script src="https://samizdat.cz/tools/d3/3.5.3.min.js" charset="utf-8"></script>
<div id="body"></div>
<button>change!</button>
If on the other hand the shapes change, you will need to first make the elements translucent with opacity:0 and then make them disappear with display:none (otherwise, they will flash out instantly). An alternative is pointer-events, but only if you don't need to support old browsers.
The transition would then look like this:
var state = "blue";
var toggleState = function() {
state = (state === "blue") ? "red" : "blue";
}
var updateDisplay = function() {
blueGroup
.style("display", state === "blue" ? "block" : "none")
.transition()
.duration(400)
.attr("opacity", state === "blue" ? 1 : 0)
.each("end", function() {
blueGroup.style("display", state === "blue" ? "block" : "none");
});
redGroup
.style("display", state === "red" ? "block" : "none")
.transition()
.duration(400)
.attr("opacity", state === "red" ? 1 : 0)
.each("end", function() {
redGroup.style("display", state === "red" ? "block" : "none");
});
}
var canvas_w = 1280 - 80,
canvas_h = 120;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var blueGroup = svg.append("g")
.on("click", function() {
console.log("Blue clicked");
})
.append("rect")
.attr("opacity", 1)
.attr("x", 0)
.attr("y", 0)
.attr("width", 150)
.attr("height", 100)
.style("fill", "blue");
var redGroup = svg.append("g")
.on("click", function() {
console.log("Red clicked");
})
.append("rect")
.attr("opacity", 0)
.style("display", "none")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 120)
.style("fill", "red");
d3.select("button").on("click", function() {
toggleState();
updateDisplay();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="body"></div>
<button>change!</button>
Note that on each transition, we now have to handle both opacity and display, and in correct order. Also note that now we have listeners on both rects.
The example would be quite a bit simpler if it could be used with .enter() and .exit() selections, as you could make away with the .on("end") and instead use .remove() on the exiting transitions.
Update: practically identical to display:none is also visibility: hidden.
If you use the style visibility rather than the attribute opacity to set the groups as hidden or visible, you can also use the style pointer-events to restrict events to visible elements.
var canvas_w = 1280 - 80,
canvas_h = 800 - 180;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var visible_group = svg.append("g")
.style("visibility", "visible")
.style("pointer-events", "visible")
.on("click", function(d){console.log("Click")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var invisible_group = svg.append("g")
.style("visibility", "hidden")
.style("pointer-events", "visible")
.on("click", function(d){console.log("Invisiclick")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
</script>
This example will print "Click" to the console when you click the blue rectangle.