Last active
August 29, 2015 14:05
-
-
Save erohinaelena/60e42e8cb99f3cd7500d to your computer and use it in GitHub Desktop.
Filter comparison
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta charset="utf-8"> | |
<link href="main.css" rel="stylesheet"> | |
</head> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src = "shadow.js"></script> | |
<script src="main.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #ccc; | |
shape-rendering: crispEdges; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var margin = {top: 50, right: 20, bottom: 100, left: 40}, | |
width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom | |
var pointsNumber = 45 | |
var ε = 1e-6 | |
var Δ = 0.1, | |
minΔ = ε, | |
maxΔ = 0.5 | |
var values = [] | |
var x = d3.scale.linear() | |
.domain([0, pointsNumber]) | |
.range([0, width]) | |
var y = d3.scale.linear() | |
.domain([0, 1]) | |
.range([height, 0]) | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
var area = svg | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") | |
area.append("g") | |
.attr("class", "axis") | |
.call(yAxis) | |
var areaGroup = area.append('g') | |
var sdtLines = area.append('g') | |
var ffpLines = area.append('g') | |
var sdtGroup = area.append('g') | |
var ffpGroup = area.append('g') | |
var deltaText = area.append('text') | |
.text('Δ = ' + (+Δ.toFixed(3))) | |
.attr('x', 0) | |
.attr('y', height + 20) | |
var SDTtext = area.append('text') | |
.attr('x', 0) | |
.attr('y', height + 50) | |
SDTtext.append('tspan') | |
.text('SDT: ') | |
.attr('fill', '#a00') | |
SDTtext = SDTtext.append('tspan') | |
.text('0 points saved, average error is 0') | |
var FFPtext = area.append('text') | |
.attr('x', 0) | |
.attr('y', height + 80) | |
FFPtext.append('tspan') | |
.text('FFP: ') | |
.attr('fill', '#0a0') | |
FFPtext = FFPtext.append('tspan') | |
.text('0 points saved, average error is 0') | |
var shadow = d3.svg.shadow() | |
.color('#fff') | |
.multiplication(2) | |
.deviation(3) | |
var slider = area.append('g') | |
var filterId = shadow(slider) | |
var sliderScale = d3.scale.linear() | |
.domain([0.5 - maxΔ, 0.5, 0.5 + maxΔ]) | |
.range([maxΔ, minΔ, maxΔ]) | |
.clamp(true) | |
var drag = d3.behavior.drag() | |
.on("drag", update) | |
var upperPoint = slider.append('circle') | |
.attr('cy', y(0.5 + Δ)) | |
var lowerPoint = slider.append('circle') | |
.attr('cy', y(0.5 - Δ)) | |
slider.selectAll('circle') | |
.attr('cx', 0) | |
.attr('r', 5) | |
.attr('fill', '#fff') | |
.attr('stroke', '#000') | |
.attr('filter',"url(#" + filterId + ")") | |
.call(drag) | |
function update() { | |
Δ = sliderScale(y.invert(d3.event.y)) | |
upperPoint.attr('cy', y(0.5 + Δ)) | |
lowerPoint.attr('cy',y(0.5 - Δ)) | |
deltaText.text('Δ = ' + (+Δ.toFixed(3))) | |
draw() | |
} | |
function filterDataSDT(d, Δ){ | |
d = d.slice() | |
var start = 0, | |
result =[], | |
trendBoundaries = [Infinity, -Infinity], | |
currentPointBoundaries = [], | |
prevTrendBoundaries = [], | |
newY = d[0], | |
lower = 0, | |
upper = 1, | |
pushPoint = function () { result.push({ x: start, y: newY }) } | |
pushPoint() | |
for (var i = 0; i < d.length; i++) { | |
currentPointBoundaries = [ | |
(d[i] - d[start] + Δ) / (i - start), | |
(d[i] - d[start] - Δ) / (i - start) | |
] | |
prevTrendBoundaries = trendBoundaries.slice() | |
if (trendBoundaries[upper] < currentPointBoundaries[upper] + ε) { | |
trendBoundaries[upper] = currentPointBoundaries[upper] | |
} | |
if (currentPointBoundaries[lower] < trendBoundaries[lower] + ε) { | |
trendBoundaries[lower] = currentPointBoundaries[lower] | |
} | |
if (trendBoundaries[lower] < trendBoundaries[upper] + ε) { | |
trendBoundaries = [Infinity, -Infinity] | |
i-- | |
newY = d3.sum(prevTrendBoundaries) * (i - start) / 2 + d[start] | |
start = i | |
d[start] = newY | |
pushPoint() | |
} | |
} | |
i-- | |
if (d.length > 1) newY = d3.sum(trendBoundaries) * (i - start) / 2 + d[start] | |
start = i | |
pushPoint() | |
return result | |
} | |
function filterDataFFP(d, Δ) { | |
d = d.slice() | |
var start = 0, | |
result = [], | |
trendBoundaries = [Infinity, -Infinity], | |
currentPointBoundaries = [], | |
currentPointTrend, | |
farthestFeasible = 0, | |
lower = 0, | |
upper = 1, | |
pushPoint = function () { result.push({ x: farthestFeasible, y: d[farthestFeasible] }) } | |
pushPoint() | |
while (farthestFeasible < d.length - 1) { | |
for (var i = start; i < d.length; i++) { | |
currentPointBoundaries = [ | |
(d[i] - d[start] + Δ) / (i - start), | |
(d[i] - d[start] - Δ) / (i - start) | |
] | |
if (trendBoundaries[upper] < currentPointBoundaries[upper] + ε) { | |
trendBoundaries[upper] = currentPointBoundaries[upper] | |
} | |
if (currentPointBoundaries[lower] < trendBoundaries[lower] + ε) { | |
trendBoundaries[lower] = currentPointBoundaries[lower] | |
} | |
currentPointTrend = (d[i] - d[start]) / (i - start) | |
if (trendBoundaries[upper] < currentPointTrend + ε && currentPointTrend < trendBoundaries[lower] + ε) { | |
farthestFeasible = i | |
} else if (trendBoundaries[lower] < trendBoundaries[upper] + ε) { | |
trendBoundaries = [Infinity, -Infinity] | |
i = start = farthestFeasible | |
pushPoint() | |
} | |
} | |
trendBoundaries = [Infinity, -Infinity] | |
start = farthestFeasible | |
pushPoint() | |
} | |
return result | |
} | |
function calculateErr(data, filteredData) { | |
var filteredX = filteredData.map(function(d) { | |
return d.x | |
}) | |
var filteredY = filteredData.map(function(d) { | |
return d.y | |
}) | |
var filteredDataScale = d3.scale.linear() | |
.domain(filteredX) | |
.range(filteredY) | |
var averageError = d3.mean(data, function(d, i) { | |
return Math.abs(d - filteredDataScale(i)) | |
}) | |
return +(averageError).toFixed(4) | |
} | |
function drawArrow(values, filteredData){ | |
var lines = filteredData.map(function(d) { | |
console.log(values[d.x]) | |
return [{x: x(d.x), y: y(values[d.x])}, {x: x(d.x), y: y(d.y)}] | |
}) | |
var arrowData = [] | |
lines.forEach(function(d) { | |
arrowData.push(d) | |
var sign = (d[0].y == d[1].y)? 0:(d[0].y - d[1].y)/Math.abs(d[0].y - d[1].y) | |
arrowData.push([{x: d[0].x - 2, y: d[1].y + 6 * sign}, d[1]]) | |
arrowData.push([{x: d[0].x + 2, y: d[1].y + 6 * sign}, d[1]]) | |
}) | |
var arrows = areaGroup.selectAll('line') | |
.data(arrowData, function (d) { return d[0].x + " " + d[0].y + " " + d[1].x + " " + d[1].y}) | |
arrows.exit().remove() | |
arrows.enter().append('line') | |
.attr('stroke', '#a00') | |
.attr('opacity', 0.5) | |
.attr('x1', function(d) { return d[0].x }) | |
.attr('y1', function(d) { return d[0].y }) | |
.attr('x2', function(d) { return d[1].x }) | |
.attr('y2', function(d) { return d[1].y }) | |
} | |
var i = 0 | |
var currentValue = 0.5 | |
var limit = d3.scale.linear().clamp(true) | |
function draw() { | |
var circles = areaGroup.selectAll('circle') | |
.data(values) | |
circles.exit().remove() | |
circles | |
.enter().append('circle') | |
.attr('cx', function(d, i) { return x(i) }) | |
.attr('cy', function(d) { return y(d) }) | |
.attr('fill', '#ddd') | |
.attr('r', 2) | |
var dataFFP = filterDataFFP(values, Δ) | |
var dataSDT = filterDataSDT(values, Δ) | |
function filteredPointID(d) { return d.x + " " + d.y } | |
var filteredSDT = sdtGroup.selectAll('circle') | |
.data(dataSDT, filteredPointID) | |
filteredSDT.exit().remove() | |
filteredSDT | |
.enter().append('circle') | |
.attr('cx', function(d) { return x(d.x) }) | |
.attr('cy', function(d) { return y(d.y) }) | |
.attr('fill', '#a00') | |
.attr('r', 2) | |
drawArrow(values, dataSDT) | |
var filteredFFP = ffpGroup.selectAll('circle') | |
.data(dataFFP, filteredPointID) | |
filteredFFP.exit().remove() | |
filteredFFP | |
.enter().append('circle') | |
.attr('cx', function(d) { return x(d.x) }) | |
.attr('cy', function(d) { return y(d.y) }) | |
.attr('fill', '#0a0') | |
.attr('r', 2) | |
var boundariesSDT = [] | |
var boundariesFFP = [] | |
function offsetLine (line, offset) { | |
return [{x: line[0].x, y: line[0].y + offset}, {x: line[1].x, y: line[1].y + offset}] | |
} | |
d3.pairs(dataFFP).forEach(function(d) { | |
boundariesFFP.push(offsetLine(d, Δ)) | |
boundariesFFP.push(offsetLine(d, -Δ)) | |
}) | |
d3.pairs(dataSDT).forEach(function(d) { | |
boundariesSDT.push(offsetLine(d, Δ)) | |
boundariesSDT.push(offsetLine(d, -Δ)) | |
}) | |
function linesID(d) { return d[0].x + " " + d[0].y + " " + d[1].x + " " + d[1].y} | |
var linesSDT = sdtLines.selectAll('line') | |
.data(boundariesSDT, linesID) | |
linesSDT.exit().remove() | |
linesSDT.enter().append('line') | |
.attr('stroke', '#a00') | |
.attr('opacity', 0.3) | |
.attr('x1', function(d) { return x(d[0].x) }) | |
.attr('y1', function(d) { return y(d[0].y) }) | |
.attr('x2', function(d) { return x(d[1].x) }) | |
.attr('y2', function(d) { return y(d[1].y) }) | |
var linesFFP = ffpLines.selectAll('line') | |
.data(boundariesFFP, linesID) | |
linesFFP.exit().remove() | |
linesFFP.enter().append('line') | |
.attr('stroke', '#0a0') | |
.attr('opacity', 0.3) | |
.attr('x1', function(d) { return x(d[0].x) }) | |
.attr('y1', function(d) { return y(d[0].y) }) | |
.attr('x2', function(d) { return x(d[1].x) }) | |
.attr('y2', function(d) { return y(d[1].y) }) | |
if (values.length > 1){ | |
SDTtext.text(dataSDT.length + ' points saved, average error is ' + calculateErr(values, dataSDT)) | |
FFPtext.text(dataFFP.length + ' points saved, average error is ' + calculateErr(values, dataFFP)) | |
} | |
} | |
setInterval(function() { | |
if (i < pointsNumber) { | |
values.push(currentValue) | |
currentValue = limit(currentValue + .3 * Math.random() - .15 + .01 * Math.cos(i * 5)) | |
draw() | |
i++ | |
} | |
}, 300) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d3.svg.shadow = function (){ | |
return d3_svg_shadow('black',9,2); | |
} | |
function d3_svg_shadow(color, deviation, multiplication) { | |
var id = 0 | |
function shadow(svg) { | |
var filterId = 'shadow-' + id++ | |
var rgb = d3.rgb(color) | |
var filter = svg | |
.append('defs') | |
.append('filter') | |
.attr('id', filterId) | |
.attr('x', '-50%') | |
.attr('y', '-50%') | |
.attr('width', '200%') | |
.attr('height', '200%') | |
filter.append('feGaussianBlur') | |
.attr('in', "SourceAlpha") | |
.attr('stdDeviation', deviation) | |
for (var i=0; i < multiplication; i++){ | |
filter.append('feBlend').attr('mode', 'multiply') | |
} | |
var feComponentTransfer = filter.append('feComponentTransfer') | |
feComponentTransfer.append('feFuncR') | |
.attr('type', 'linear') | |
.attr('slope', 0) | |
.attr('intercept', rgb.r / 255) | |
feComponentTransfer.append('feFuncG') | |
.attr('type', 'linear') | |
.attr('slope', 0) | |
.attr('intercept', rgb.g / 255) | |
feComponentTransfer.append('feFuncB') | |
.attr('type', 'linear') | |
.attr('slope', 0) | |
.attr('intercept', rgb.b / 255) | |
var feMerge = filter.append('feMerge') | |
feMerge.append('feMergeNode') | |
feMerge.append('feMergeNode').attr('in','SourceGraphic') | |
return filterId | |
} | |
shadow.color = function (c){ | |
if (!arguments.length) return color | |
color = c | |
return shadow | |
} | |
shadow.deviation = function (d){ | |
if (!arguments.length) return deviation | |
deviation = d | |
return shadow | |
} | |
shadow.multiplication = function (m){ | |
if (!arguments.length) return multiplication | |
multiplication = m | |
return shadow | |
} | |
return shadow | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment