iwmlib/lib/utils.html

164 lines
7.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Doctests</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<main>
<h1><a href="index.html">lib.</a>Utils</h1>
<p>
Utility functions can be used across modules. To avoid name conflicts most of them are defined as static
class functions (i.e. the class mainly serves as a namespace). Typically this class name is in the
plural, e.g. "Points", "Dates" to ensure that existing class names like "Point", "Date" are not in
conflict with the namespace.
</p>
<h2>Cycle</h2>
<p>Cycles simplify to switch between values in a cyclic way.</p>
<script class="doctest">
let cycle = new Cycle(1, 2, 3)
Doctest.expect(cycle.next(), 1)
Doctest.expect(cycle.next(), 2)
Doctest.expect(cycle.next(), 3)
Doctest.expect(cycle.next(), 1)
</script>
<h2>Dates</h2>
<script class="doctest">
let feb1900 = new Date(1900, 1, 1)
Doctest.expect(Dates.daysInMonth(feb1900), 28)
// 1900 was no leap year
let feb2000 = new Date(2000, 1, 1)
Doctest.expect(Dates.daysInMonth(feb2000), 29)
// 2000 was a leap year
let mar1913 = new Date(1913, 2, 1)
Doctest.expect(Dates.daysInMonth(mar1913), 31)
</script>
<p>
A tricky problem is to iterate over years, months, and days to label timelines and calendars in a
consistent way. This can lead to problems with standard (CET) and summer time (CEST). To illustrate the
problem look at the following example. Although march has 31 days the formatted UTC string shows "30.3".
Also note that the standard new Date() constructor uses a zero-based month:
</p>
<script class="doctest">
let format = { timeZone: 'UTC' }
let lastMar1913 = new Date(1913, 2, 31)
Doctest.expect(lastMar1913.toLocaleDateString('de', format), '30.3.1913')
</script>
<p>The following iterators guarantee that correct labels are generated:</p>
<script class="doctest">
let lastDay = null
for (let day of Dates.iterDays(mar1913)) {
lastDay = day
}
Doctest.expect(lastDay.toLocaleDateString('de', format), '31.3.1913')
</script>
<h2>Sets</h2>
<p>
Unfortunately the common set operations of other languages are missing in JavaScript. Therefore we use a
Sets helper class with static methods:
</p>
<script class="doctest">
let set1 = new Set([1, 2, 3])
let set2 = new Set([2, 3, 4, 5])
let set3 = new Set([2, 3, 6])
Doctest.expect(Array.from(Sets.intersect(set1, set2, set3)), [2, 3])
Doctest.expect(Array.from(Sets.union(set1, set2, set3)), [1, 2, 3, 4, 5, 6])
Doctest.expect(Array.from(Sets.difference(set2, set1, set3)), [4, 5])
</script>
<h2>Polygon</h2>
<p>
An intersection of polygons is needed to compute the overlap of rotated rectangles. We are using the
library <a href="https://gist.github.com/cwleonard/e124d63238bda7a3cbfa">jspolygon.js</a> but provide a
more convenient API that is compatible with arrays of absolute points.
</p>
<p>
To detect intersection with another Polygon object, the instance method uses the Separating Axis
Theorem. It returns false if there is no intersection, or an object if there is. The object contains 2
fields, overlap and axis. Moving the other polygon by overlap on axis will get the polygons out of
intersection.
</p>
<p>
The following triangles show an overlap. Moving the triangle along the red line would remove the
overlap.
</p>
<canvas id="canvas" class="grayBorder interactive">Canvas not supported</canvas>
<script class="doctest">
let context = canvas.getContext('2d')
// The jspolygon syntax
let a = Polygon.fromPoints([
{ x: 20, y: 20 },
{ x: 100, y: 100 },
{ x: 150, y: 50 }
])
a.draw(context)
let b = Polygon.fromPoints([
{ x: 70, y: 50 },
{ x: 150, y: 10 },
{ x: 200, y: 70 }
])
b.draw(context)
context.strokeStyle = '#ff0000'
context.beginPath()
let result = a.intersectsWith(b)
if (result != false) {
let { overlap, axis } = result
context.moveTo(b.center.x, b.center.y)
let target = Points.add(b.center, { x: overlap * axis.x, y: overlap * axis.y })
context.lineTo(target.x, target.y)
}
context.stroke()
</script>
<h2>Low Pass Filter</h2>
<p>
Low Pass Filter muffles fast (high-frequency) changes to the signal. For more information visit the
<a href="http://en.wikipedia.org/wiki/Low-pass_filter">wikipedia article</a>.
</p>
<script class="doctest">
let lpf = new LowPassFilter(0.5)
Doctest.expect(
lpf.smoothArray([10, 8, 9, 10, 12, 8, 50, 10, 12, 8]),
[10, 9, 9, 10, 11, 9, 30, 20, 16, 12]
)
Doctest.expect(lpf.next(20), 10.0)
Doctest.expect(lpf.next(20), 12.5)
Doctest.expect(lpf.next(20), 14.375)
Doctest.expect(lpf.next(20), 15.78125)
lpf = new LowPassFilter(0.2)
lpf.setup([10, 10, 10, 10, 10, 10, 10, 10, 10, 10])
Doctest.expect(lpf.next(20), 12.0)
Doctest.expect(lpf.next(10), 10.32)
</script>
<h2>References</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Circular_buffer"> Circular buffer </a></li>
<li>
<a href="http://stackoverflow.com/questions/20867562/create-a-date-object-with-cet-timezone">
Create a Date object with CET timezone
</a>
</li>
<li>
<a
href="http://stackoverflow.com/questions/315760/what-is-the-best-way-to-determine-the-number-of-days-in-a-month-with-javascript"
>
What is the best way to determine the number of days in a month with javascript?
</a>
</li>
</ul>
</main>
</body>
</html>