164 lines
7.5 KiB
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>
|