2019-03-21 09:57:27 +01:00
|
|
|
<!doctype html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
|
|
<title>Doctests</title>
|
2019-05-22 16:03:19 +02:00
|
|
|
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
|
2019-03-21 09:57:27 +01:00
|
|
|
<link rel="stylesheet" href="../css/doctest.css">
|
2019-05-22 16:03:19 +02:00
|
|
|
<script src="./3rdparty/highlight/highlight.pack.js"></script>
|
|
|
|
<script src="../dist/iwmlib.js"></script>
|
2019-03-21 09:57:27 +01:00
|
|
|
</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>
|
|
|
|
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>
|