const filters = new (function () {
const fBlur = d3.selectAll('feGaussianBlur:first-child');
const pBlur = d3.select('feComponentTransfer + feGaussianBlur');
const fFunc = d3.select('feFuncA[type=linear]');
const fMatr = d3.select('feColorMatrix');
let fComp = d3.selectAll('feComposite');
let active;
const ids = {
fcd: '#component-discrete',
fcl: '#component-linear',
fmx: '#colormatrix'
};
const xmls = new XMLSerializer();
const pre = d3.select('pre');
this.activate = function (a) {
active = ids[a];
this.showSource();
};
this.showSource = function () {
const source = xmls.serializeToString(d3.select(active).node())
.replace(/>\s*</g, '><')
.replace(/<fe/g, '\n<fe')
.replace(/(<\/\w+>)(<\/\w+>)/g, '$1\n$2')
.replace(/(<feFuncA|<feMergeNode)/g, ' $1')
.trim()
pre.text(source);
};
this.blur = function (deviation) {
fBlur.attr('stdDeviation', deviation);
this.showSource();
};
this.postBlur = function (deviation) {
pBlur.attr('stdDeviation', deviation);
this.showSource();
};
this.contrast = function (slope, intercept) {
fFunc.attr('slope', slope)
.attr('intercept', intercept);
fMatr.attr('values', [
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0",
slope,
intercept
].join(' '));
this.showSource();
};
this.combinate = function (operator) {
fComp.remove();
switch (operator) {
case 'none':
return this.showSource();
case 'merge':
fComp = d3.selectAll('filter')
.append('feMerge');
fComp.append('feMergeNode')
.attr('in', "cutoff");
fComp.append('feMergeNode')
.attr('in', "SourceGraphic");
return this.showSource();
case 'blend':
fComp = d3.selectAll('filter')
.append('feBlend');
break;
default:
fComp = d3.selectAll('filter')
.append('feComposite')
.attr('operator', operator);
break;
}
fComp.attr('in', "SourceGraphic")
.attr('in2', "cutoff");
this.showSource();
};
})();
const tweaks = new (function () {
this.blur = d3.select('#blur-select');
const check = d3.select('#blur-select input');
this.linear = d3.select('#linear-select');
const selects = d3.selectAll('#linear-select input');
const slope = selects.filter('#slope');
const intercept = selects.filter('#intercept');
const visSlope = d3.select('#visSlope');
this.activate = function (active) {
this.blur.classed('inactive', active !== 'fcd');
check.property('disabled', active !== 'fcd');
this.linear.classed('inactive', active === 'fcd');
selects.property('disabled', active === 'fcd');
};
this.limit = function () {
const min = 1 - slope.property('value');
intercept.attr('min', min);
if (min > intercept.property('value')) {
intercept.property('value', min);
}
return [
slope.property('value'),
intercept.property('value')
];
};
this.visualize = function (slope, intercept) {
visSlope.attr('x1', -intercept * 100 / slope)
.attr('x2', (1 - intercept) * 100 / slope);
};
})();
const primitives = d3.selectAll('.primitive');
const examples = d3.selectAll('.example');
const goo = d3.selectAll('.goo');
d3.selectAll('input[name=variant]').on('change', function () {
if (!this.checked) return;
examples.classed('hide', true)
.filter(this.id).classed('hide', false);
}).dispatch('change');
d3.select('#color').on('change', function () {
primitives.classed('bw', this.checked);
}).dispatch('change');
d3.select('#filter-select').on('change', function () {
goo.attr('class', 'goo ' + this.value);
filters.activate(this.value);
tweaks.activate(this.value);
}).dispatch('change');
d3.select('#deviation').on('input change', function () {
d3.select('#devNum').text(this.value);
filters.blur(this.value);
}).dispatch('change');
tweaks.blur.on('change', function () {
filters.postBlur(this.checked ? 1 : 0);
}).dispatch('change');
tweaks.linear.on('change', function () {
const params = tweaks.limit();
tweaks.visualize.apply(tweaks, params);
filters.contrast.apply(filters, params);
}).dispatch('change');
d3.select('#combine-select').on('change', function (e) {
filters.combinate(this.value);
}).dispatch('change');
function tweenFactory (forth) {
return function () {
let interpolator;
const node = this,
from = forth ? 0 : 500,
to = forth ? 500 : 0;
if (this instanceof SVGElement) {
interpolator = d3.interpolateTransformSvg(
'translate(0, ' + from + ')',
'translate(0, ' + to + ')'
);
return function (t) {
node.setAttribute("transform", interpolator(t));
};
} else {
interpolator = d3.interpolateTransformCss(
'translate(0px, ' + from + 'px)',
'translate(0px, ' + to + 'px)'
);
return function (t) {
node.style.transform = interpolator(t);
};
}
};
}
d3.selectAll('.move').transition()
.on('start', function repeat () {
d3.active(this)
.duration(2000)
.ease(d3.easeSinInOut)
.tween('forth', tweenFactory(true))
.transition()
.duration(2000)
.ease(d3.easeSinInOut)
.tween('back', tweenFactory(false))
.transition()
.on('start', repeat);
});
body {
font-family: sans-serif;
line-height: 2em;
}
.inactive {
opacity: 0.5;
}
input[type=number] {
width: 3em;
}
#vis {
fill:none;
stroke:black;
margin-left:10px;
vertical-align: middle;
}
pre {
padding: 10px;
border: 1px solid black;
min-height: 10em;
overflow-x: auto;
clear: both;
}
.floating {
float: left;
margin-right: 30px;
}
.floating.example {
width: 250px;
height: 400px;
}
div.goo {
position: relative;
width: 500px;
height: 800px;
transform: scale(0.5);
transform-origin: 0% 0%;
}
div.goo div {
position: absolute;
width: 200px;
height: 200px;
border-radius: 100%;
}
div.goo .move {
width: 160px;
height: 160px;
border-radius: 0;
}
.hide {
display: none;
}
.bw {
fill: black;
background: black !important;
}
.fcd {
filter: url(#component-discrete);
}
.fcl {
filter: url(#component-linear);
}
.fmx {
filter: url(#colormatrix);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" class="floating" height="0" width="0">
<filter id="colormatrix" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
<feGaussianBlur stdDeviation="20" />
<feColorMatrix mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9" result="cutoff" />
<feComposite operator="atop" in="SourceGraphic" in2="cutoff" />
</filter>
<filter id="component-linear" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
<feGaussianBlur stdDeviation="20" />
<feComponentTransfer result="cutoff">
<feFuncA type="linear" slope="19" intercept="-9" />
</feComponentTransfer>
<feComposite operator="atop" in="SourceG forraphic" in2="cutoff" />
</filter>
<filter id="component-discrete" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
<feGaussianBlur stdDeviation="20" />
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1" />
</feComponentTransfer>
<feGaussianBlur id="post-blur" stdDeviation="1" result="cutoff" />
<feComposite operator="atop" in="SourceGraphic" in2="cutoff" />
</filter>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="floating example" viewBox="0 0 500 800">
<g class="goo fcd">
<circle class="primitive" r="100" cx="150" cy="400" fill="#ff0000" />
<circle class="primitive" r="100" cx="350" cy="400" fill="#00ff00" />
<rect class="primitive move" x="170" y="70" width="160" height="160" fill="#0000ff" />
</g>
</svg>
<div class="floating example hide">
<div class="goo fcd">
<div class="primitive" style="background:#ff0000;left:50px;top:300px"></div>
<div class="primitive" style="background:#00ff00;right:50px;top:300px"></div>
<div class="primitive move" style="background:#0000ff;left:170px;top:70px"></div>
</div>
</div>
<div id="controls">
<p>Filtered Objects
<input name="variant" id="svg" type="radio" checked="checked"></input><label>SVG primitives</label>
<input name="variant" id="div" type="radio"></input><label>HTML divs</label></p>
<p><input id="color" type="checkbox"></input>
<label>Set black and white</label></p>
<p><label>Blur value</label><br/>
<input id="deviation" type="range" min="5" max="50" step="5" value="20"></input>
<span id="devNum">20</span></p>
<p><label>Cutoff Filter</label><br/>
<select id="filter-select">
<option value="fcd" selected="selected">Component/discrete</option>
<option value="fcl">Component/linear</option>
<option value="fmx">ColorMatrix</option>
</select></p>
<p id="blur-select"><input type="checkbox" checked="checked"></input>
<label>Post-Blur for discrete</label></p>
<p id="linear-select"><label>slope</label> <input id="slope" type="number" min="2" step="1" value="19"></input>
<label>intercept</label> <input id="intercept" type="number" min="-18" max="0" step="1" value="-9"></input> for linear
<svg id="vis" width="100" height="20">
<rect x="0.5" y="0.5" width="99" height="19" />
<line x1="50" y1="20" x2="50" y2="0" style="stroke:red;opacity:0.7" />
<line id="visSlope" x1="50" y1="20" x2="50" y2="0" />
</svg></p>
<p><label>Combining Filter</label><br/>
<select id="combine-select">
<option value="atop" selected="selected">Composite atop</option>
<option value="over">Composite over</option>
<option value="blend">Blend normal</option>
<option value="merge">Merge</option>
<option value="none">none</option>
</select></p>
</div>
<pre></pre>