manymanymore
manymanymore

Reputation: 3107

Change a shadow opacity partially in svg using filters and d3.js

How could I change a shadow opacity partially in svg using filters and d3.js?

Here is what I would like my shadow to look like: enter image description here

I mean the shadow which is dropped by the rectangle in which written Entity Name.

Here is what I was able to achieve so far:

var svg = d3.select("#drawRegion")
                .append("svg")
                .attr("width", "100%")
                .attr("height", "100%");
                
                
var defs = svg.append("defs");

var filter = defs.append("filter")
                 		.attr("id","coolShadow");

filter.append("feMorphology")
  .attr("in", "SourceGraphic")
  .attr("result", "upperLayer")
  .attr("operator", "dilate")
  .attr("radius", "2 2");

filter.append("feMorphology")
  .attr("in", "SourceAlpha")
  .attr("result", "enlargedAlpha")
  .attr("operator", "dilate")
  .attr("radius", "3 5");

filter.append("feGaussianBlur")
  .attr("in", "enlargedAlpha")
  .attr("result", "bluredAlpha")
  .attr("stdDeviation", "5");
  
filter.append("feOffset")
  .attr("in", "bluredAlpha")
  .attr("result", "lowerLayer")
  .attr("dy", "3");


var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
  .attr("in","lowerLayer");
feMerge.append("feMergeNode")
  .attr("in","upperLayer");

svg.append("rect")
    .attr("rx", 2)
    .attr("ry", 2)
	.attr("x", "20%")
  .attr("y", "20%")
  .attr("width", "60%")
  .attr("height", "60%")
  .attr("fill", "white")
  .style("filter", "url(#coolShadow)");
  
  
  
  
  
  
  
  
#drawRegion {
    width: 100%;
    height: 100%;
    display: inline-block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    position: relative;
}
<div id="drawRegion">

</div>
<script src="https://d3js.org/d3.v5.min.js"></script>

The shadow is almost ready as you can see. The only thing I can not figure out how to achieve - is the increased opacity (right now it is too increased and I would like to lower it, but at the same time preserver the transparencies on the left, top and right borders).

I tried decreasing the overall opacity, but it did not help: the left, top and right borders transparencies decreased as well, while I would like to make the lower border part more transparent and preserve other borders as they are.

I would be grateful for any corrections which would allow me to tackle the issue, or to the entirely different solution.

Upvotes: 4

Views: 733

Answers (2)

Michael Mullany
Michael Mullany

Reputation: 31705

The best way to achieve what you want to do is to dial back the y radius on the second feMorphology and the y on the feOffset. There are ways to actually reduce opacity selectively, but they'll cause discontinuities in your shadow - which you probably don't want.

(Also you needed to expand your filter region - which I do for you here).

I wonder, though, why you're using a feMorphology to expand the source rectangle? If you want a larger rectangle with slightly rounded corners, you can just draw that directly in SVG - it's going to be more performant. feMorphology is a slow operation.

var svg = d3.select("#drawRegion")
                .append("svg")
                .attr("width", "100%")
                .attr("height", "100%");
                
                
var defs = svg.append("defs");

var filter = defs.append("filter")
                 		.attr("id","coolShadow")
                 		.attr("y","-20%")
                 		.attr("height","140%");

filter.append("feMorphology")
  .attr("in", "SourceGraphic")
  .attr("result", "upperLayer")
  .attr("operator", "dilate")
  .attr("radius", "2 2");

filter.append("feMorphology")
  .attr("in", "SourceAlpha")
  .attr("result", "enlargedAlpha")
  .attr("operator", "dilate")
  .attr("radius", "3 3");

filter.append("feGaussianBlur")
  .attr("in", "enlargedAlpha")
  .attr("result", "bluredAlpha")
  .attr("stdDeviation", "5");
  
filter.append("feOffset")
  .attr("in", "bluredAlpha")
  .attr("result", "lowerLayer")
  .attr("dy", "2");

var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
  .attr("in","lowerLayer");
feMerge.append("feMergeNode")
  .attr("in","upperLayer");

svg.append("rect")
    .attr("rx", 2)
    .attr("ry", 2)
	.attr("x", "20%")
  .attr("y", "20%")
  .attr("width", "60%")
  .attr("height", "60%")
  .attr("fill", "white")
  .style("filter", "url(#coolShadow)");
  
  
  
  
  
  
  
  
#drawRegion {
    width: 100%;
    height: 100%;
    display: inline-block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    position: relative;
}
<div id="drawRegion">

</div>
<script src="https://d3js.org/d3.v5.min.js"></script>

Upvotes: 1

Xavier Guihot
Xavier Guihot

Reputation: 61656

You can both play on the opacity and dy attributes.

The opacity will globally make the shadow brighter or darker (value between 0 and 1).

The dy attribute (same goes for dx) moves the shadow vertically ("moves the light source").

If dy is 0, then the shadow is centered on the shape (the light source is just above the shape). If (as in your case), dy is positive, then the shadow will be translated to the bottom and thus will be more pronounced bellow the shadow's shape.

Here is an example with opacity at 0.3:

.style("opacity", 0.3)

and dy at 0 (you can play with it to adjust it to your style):

.attr("dy", "0")

var svg = d3.select("#drawRegion")
                .append("svg")
                .attr("width", "100%")
                .attr("height", "100%");
                
                
var defs = svg.append("defs");

var filter = defs.append("filter")
                 		.attr("id","coolShadow")
                    .attr("x", "-100%").attr("y", "-100%") //
                    .attr("width", "300%").attr("height", "300%"); //

filter.append("feMorphology")
  .attr("in", "SourceGraphic")
  .attr("result", "upperLayer")
  .attr("operator", "dilate")
  .attr("radius", "2 2");

filter.append("feMorphology")
  .attr("in", "SourceAlpha")
  .attr("result", "enlargedAlpha")
  .attr("operator", "dilate")
  .attr("radius", "3 5");

filter.append("feGaussianBlur")
  .attr("in", "enlargedAlpha")
  .attr("result", "bluredAlpha")
  .attr("stdDeviation", "4");
  
filter.append("feOffset")
  .attr("in", "bluredAlpha")
  .attr("result", "lowerLayer")
  .attr("dy", "0"); //


var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
  .attr("in","lowerLayer");
feMerge.append("feMergeNode")
  .attr("in","upperLayer");

svg.append("rect")
    .attr("rx", 2)
    .attr("ry", 2)
	.attr("x", "20%")
  .attr("y", "20%")
  .attr("width", "60%")
  .attr("height", "60%")
  .attr("fill", "white")
  .style("filter", "url(#coolShadow)")
  .style("opacity", 0.3); //
#drawRegion {
    width: 100%;
    height: 100%;
    display: inline-block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    position: relative;
}
<div id="drawRegion">

</div>
<script src="https://d3js.org/d3.v5.min.js"></script>

Notice how I also modified the width/height/position of the applied shadow in order to avoid it being truncated:

var filter = defs.append("filter")
               .attr("id", "coolShadow")
               .attr("x", "-100%").attr("y", "-100%")
               .attr("width", "300%").attr("height", "300%");

Upvotes: 1

Related Questions