iwmlib/lib/utils.html

174 lines
5.6 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>
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>