ClipPath hides the original shape - d3 ReactJS - javascript

I'm a newbie to D3 and ReactJS, and trying to clip circle with a straight line, but could not get how it works. I have this HTML code to draw image;
<div >
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" className="clip-path" ref={this.attachCircle.bind(this)}>
</svg>
</div>
here is my function to draw image;
attachCircle = (svgRef:SVGElement) => {
const svg = d3.select(svgRef);
// draw a circle
svg.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "SteelBlue")
}
and it results in this way;
but as soon as I add clipPath to circle;
svg.append("clipPath") // define a clip path
.attr("id", "clip") // give the clipPath an ID
.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "SteelBlue")
it shows nothing on the screen. It is not drawing any circle or anything.
Can anyone explain why is it so?? or What am I missing?

You need to use use
<use clip-path="url(#clip)" xlink:href="#clip" fill="red" />
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath

Related

d3 5+ - Appending circles about a semi-circle or arc

Suppose we wanted to make a list-like visual. Setting the y logic for the circles can be as simple as:
var data = [0,1,2,3,4,5,6,7,8,9];
var yScale = d3.scaleLinear()
.range([height,0])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return yScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
However, suppose we wanted to go for some style points and arrange the circles not in a blocky / tabular arrangement but rather arrange them about a circle or arc. I had this result in mind (only concerned with the outer circles):
While I think d3 does have trigonometric functions, I have never seen them used in pixel coordinates. I'd imagine the pseudo-code to be something like:
var semiCircleScale = d3.?????
.range([250 degrees, 110 degrees])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return semiCircleScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
Question
Is anyone familiar with using circle / arc scales for use with x,y logic for appending shapes? Or is there an easier/less-math-intensive way?
So the idea is to create 2 different path of arc and then calculate the circumference and place the circles along with.
d3.svg.arc()
.append("path")
.attr("d", arc1)
Here is a fiddle link with minimum code to establish the idea
https://jsfiddle.net/Dibyanshu/g03p6sxj/

Mouseout on element grouping fires before leaving bounding area in D3

I'm trying to add a click and mouseout handler to a group of circles and text. The click handler works fine, but the mouse out seems to fire even if I haven't left the circleGroup area (shown below).
Note: there are multiple of these circle groups which are added in a grid.
Here is the SVG as it appears in the browser:
The code to produce the circleGroup, containing an outer green-ish circle, an inner white circle, and a text element, is as follows:
let circleGroup = leftPanelGroup.append('g');
let outerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.attr("r", 15);
let innerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.style('stroke', '#fff')
.attr("r", 7);
let text = circleGroup.append('text')
.style('color', '#fff')
.attr("x", x)
.attr("y", y - 25)
.style('fill', '#fff')
.attr("font-size", 12)
.attr('font-weight', 'bold')
.attr("font-family", "sans-serif")
.attr('id', 'circle-text')
.style("text-anchor", "middle")
.text('Thank you');
...
On click anywhere within the circleGroup, the circleClick should fire. This works fine. The issue is, the circleMouseout function seems to randomly fire even if I haven't yet left the bounding area of the circleGroup:
circleGroup.on('click', circleClick).on('mouseout', circleMouseout);
function circleClick() {
// Do something
}
function circleMouseout() {
// Do something else
}
The output HTML in console shows the area of the <g> svg group element. I'd expect that click anywhere in this highlighted area would fire the click event, and only when I mouse out of this highlighted area would the mouseout event fire. Again, the click works fine, the mouseout does not.
<g>
<circle cx="252.99037499999997" cy="340.938" r="15" style="fill: rgb(108, 160, 123);">
</circle>
<circle cx="252.99037499999997" cy="340.938" r="7" style="fill: rgb(108, 160, 123); stroke: rgb(255, 255, 255);">
</circle>
<text x="252.99037499999997" y="315.938" font-size="12" font-weight="bold" font-family="sans-serif" id="circle-text" style="color: rgb(255, 255, 255); fill: rgb(255, 255, 255); text-anchor: middle;">Thank you
</text>
</g>
The bounding box of a g doesn't affect where a mouse interacts with it.The mouse only interacts with the "interaction area" of an element. This is generally the stroke or fill of rendered elements. So, whenever your mouse leaves the circles or the text, you trigger the mouseout event, not when you leave the bounding box of the parent g.
If you want the bounding box of the parent g to interact with the mouse, then we need to add a new rectangle. We can extract the g's bbox and use this to draw a new rectangle over the bounding box of the g. This rectangle can be given the fill of none to make it invisible, but also given a pointer-events property of all to ensure it interacts with the mouse:
let boundingBox = circleGroup.append('rect')
.each(function() {
var bbox = this.parentNode.getBBox(); // get parent `g` bounding box
d3.select(this)
.attr("width", bbox.width) // size rect based on bounding box.
.attr("height", bbox.height)
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("fill","none")
.attr("pointer-events","all")
})
Now we can assign event listeners to the g, or the rect for that matter, and the entire g bounding box will respond to mouse events:
let circleGroup = d3.select("body")
.append("svg")
.append("g");
let x = 50;
let y = 50;
let color = "steelblue";
let outerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.attr("r", 15);
let innerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.style('stroke', '#fff')
.attr("r", 7);
let text = circleGroup.append('text')
.attr("x", x)
.attr("y", y - 25)
.style('fill', color)
.attr("font-size", 12)
.attr('font-weight', 'bold')
.attr("font-family", "sans-serif")
.attr('id', 'circle-text')
.style("text-anchor", "middle")
.text('Thank you');
let boundingBox = circleGroup.append('rect')
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width)
.attr("height", bbox.height)
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("fill","none")
.attr("pointer-events","all")
})
circleGroup.on("mouseover", function() {
console.log("mouseover");
}).on("mouseout", function() {
console.log("mouseout");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

svg circle too small for mouseevent

I am working on a D3 Graph. My graph has circles with no fill color which look like this:
var circles = svg.selectAll("circle")
.data(x.ticks(6))
.enter().append("circle")
.attr("r", function (d) { return radius(d); })
.style("fill", "none")
.style("stroke", "black")
.style("stroke-dasharray", "3,3")
.style("stroke-width", "1px")
I have added a mouse event to that circle so that whenever someone hovers over it, the circle would get larger width:
.on('mouseenter', function (a, i) {
d3.select(this)
.style("stroke-dasharray", "0")
.style("stroke-width", "3px")
})
However, the width of the circle is too small to be easily touchable without extra effort. What would be a good and efficient solution to make the hit slop bigger so that the mouseevent would trigger with ease?
The best option is control the minimum circle radius(the radius where it is easily touchable).That means, if a circle is not easily touchable when radius < 5px then keep minimum radius as 5px.

Clip path is moving with group of elements when using d3.drag

I'm trying to drag a group of shapes on a clipped path. For the first time, It works fine, but as soon as I started dragging, clipping does not work at all.
Here is my working code;
var svg = d3.select("svg");
// draw a circle
svg.append("clipPath") // define a clip path
.attr("id", "clip") // give the clipPath an ID
.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
var g = svg.append("g")
.datum({x:0, y:0})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.attr("clip-path","url(#clip)")
.call(d3.drag()
.on("start", function(d){
d3.select(this).raise().classed("active", true);
})
.on("drag", function(d){
d3.select(this).attr("transform","translate(" + (d3.event.x) + "," + (d3.event.y) + ")" );
})
.on("end", function(d){
d3.select(this).classed("active", false);
}));
g.append("rect")
.attr("x",100)
.attr("y",80)
.attr("height",100)
.attr("width",200)
g.append("line")
.attr("x1", 100)
.attr("y1", 80)
.attr("x2", 200)
.attr("y2", 80)
.style("stroke", "purple")
.style("stroke-width", 12)
.svgClass{
border:2px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="500" height="300" class="svgClass"></svg>
You can see on dragging, first time clipped shape is moving all the way. No further clipping is there.
To make it easy, I redraw the outer circle again. Check this code;
var svg = d3.select("svg");
// draw a circle
svg.append("clipPath") // define a clip path
.attr("id", "clip") // give the clipPath an ID
.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
// redraw circle to make it easy
svg.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
var g = svg.append("g")
.datum({x:0, y:0})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.attr("clip-path","url(#clip)")
.call(d3.drag()
.on("start", function(d){
d3.select(this).raise().classed("active", true);
})
.on("drag", function(d){
d3.select(this).attr("transform","translate(" + (d3.event.x) + "," + (d3.event.y) + ")" );
})
.on("end", function(d){
d3.select(this).classed("active", false);
}));
g.append("rect")
.attr("x",100)
.attr("y",80)
.attr("height",100)
.attr("width",200)
g.append("line")
.attr("x1", 100)
.attr("y1", 80)
.attr("x2", 200)
.attr("y2", 80)
.style("stroke", "purple")
.style("stroke-width", 12)
.svgClass{
border:2px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="500" height="300" class="svgClass"></svg>
Here You can see clipping is not working at all. I want to bound this dragging within circle and if moves out of clipping boundaries, it should clip it accordingly.
Can anyone help me out with this requirement? Or let me know where I'm
doing it wrong.
The drag callback is transforming the same g element that the clip path has been applied to. This means that the g element's clip path is also being transformed, which is why the clipped shape is moving around as you drag your shape.
The snippet below uses a grey rectangle to show the clip path definition, and a pink rectangle to show the area of the transformed g element. The circle is retaining the original clip shape because the g element's clip path is being translated along with the rest of the element.
<svg width="300" height="300">
<clipPath id="cut">
<rect width="100" height="100" x="100" y="50"></rect>
</clipPath>
<rect x="100" y="50" width="100" height="100" fill="#eee"></rect>
<g clip-path="url(#cut)" transform="translate(50, 50)">
<rect x="100" y="50" width="100" height="100" fill="pink"></rect>
<circle
class="consumption"
cx="100"
cy="100"
r="50">
</circle>
</g>
</svg>
In the snippet below, a clip path is applied to an outer g element (which is not translated and has the same co-ordinates as the original clip path definition), while the transformation is applied to an inner g element.
<svg width="300" height="300">
<clipPath id="cut">
<rect width="100" height="100" x="100" y="50"></rect>
</clipPath>
<rect x="100" y="50" width="100" height="100" fill="#eee"></rect>
<g clip-path="url(#cut)">
<rect x="100" y="50" width="100" height="100" fill="pink"></rect>
<g transform="translate(100, 50)">
<circle
class="consumption"
cx="100"
cy="100"
r="50">
</circle>
</g>
</g>
</svg>
So, as shown in the example you should apply the clip path to an outer g element, while transforming an inner g element.

d3.js compute path booleans between ellipses and circles

I need to split multiple overlapping ellipses in an SVG by all path intersections. The purpose of this is for a venn diagram. Ben Fredrickson's venn diagram gets you part of the way there by computing intersections, but does not compute all possible intersections between an arbitrary number of ellipses. His methods do not compute convex (difference) areas, only intersections, and do not handle ellipses.
I've created a non-proportional, symmetric venn diagram layout in d3 and would like to generate paths for all the possible regions, not just intersections.
If there is not a javascript method available, if someone could help clarify the math that would also be acceptable.
My approach so far looks like this:
find intersection points of ellipse circumfrence paths (how?)
generate arc segments between those points using ellipse radii
join arc segments to new paths
So in the following fiddle, I would need to split these ellipses by every path intersection, generating 18 separate paths.
fiddle here
var width = 450,
height = 400
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:ellipse")
.attr("cx", 100)
.attr("cy", 100)
.attr("rx", 50)
.attr("ry",100)
.style("fill", 'rgba(128,255,128,0.4)')
.style("stroke", "#777")
.style("stroke-width", '1px');
svg.append("svg:ellipse")
.attr("cx", 150)
.attr("cy", 100)
.attr("rx", 50)
.attr("ry",100)
.style("fill", 'rgba(255,128,128,0.4)')
.style("stroke", "#777")
.style("stroke-width", '1px');
svg.append("svg:ellipse")
.attr("cx", 150)
.attr("cy", 100)
.attr("rx", 100)
.attr("ry",50)
.style("fill", 'rgba(128,128,255,0.4)')
.style("stroke", "#777")
.style("stroke-width", '1px');
svg.append("svg:ellipse")
.attr("cx", 190)
.attr("cy", 130)
.attr("rx", 100)
.attr("ry",50)
.style("fill", 'rgba(255,128,255,0.4)')
.style("stroke", "#777")
.style("stroke-width", '1px');

Categories

Resources