Added LowPassFilter and support for smooth scatter rotation and zoom.
This commit is contained in:
parent
2a11f02bd2
commit
cd76ae22a4
2600
dist/iwmlib.js
vendored
2600
dist/iwmlib.js
vendored
File diff suppressed because it is too large
Load Diff
129
dist/iwmlib.pixi.js
vendored
129
dist/iwmlib.pixi.js
vendored
@ -3265,8 +3265,8 @@
|
||||
|
||||
// Distance == 0.0 indicates an inside relation.
|
||||
static distanceToRect(p, r) {
|
||||
var cx = Math.max(Math.min(p.x, r.x + r.width), r.x);
|
||||
var cy = Math.max(Math.min(p.y, r.y + r.height), r.y);
|
||||
let cx = Math.max(Math.min(p.x, r.x + r.width), r.x);
|
||||
let cy = Math.max(Math.min(p.y, r.y + r.height), r.y);
|
||||
return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy))
|
||||
}
|
||||
|
||||
@ -3702,6 +3702,92 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LowPassFilter {
|
||||
|
||||
constructor(smoothing = 0.5, bufferMaxSize=10) {
|
||||
this.smoothing = smoothing; // must be smaller than 1
|
||||
this.buffer = []; // FIFO queue
|
||||
this.bufferMaxSize = bufferMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup buffer with array of values
|
||||
*
|
||||
* @param {array} values
|
||||
* @returns {array}
|
||||
* @access public
|
||||
*/
|
||||
setup(values) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
this.__push(values[i]);
|
||||
}
|
||||
return this.buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear buffer to prepare for new values.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
clear() {
|
||||
this.buffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new value to buffer (FIFO queue)
|
||||
*
|
||||
* @param {integer|float} value
|
||||
* @returns {integer|float}
|
||||
* @access private
|
||||
*/
|
||||
__push(value) {
|
||||
let removed = (this.buffer.length === this.bufferMaxSize)
|
||||
? this.buffer.shift()
|
||||
: 0;
|
||||
|
||||
this.buffer.push(value);
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth value from stream
|
||||
*
|
||||
* @param {integer|float} nextValue
|
||||
* @returns {integer|float}
|
||||
* @access public
|
||||
*/
|
||||
next(nextValue) {
|
||||
|
||||
// push new value to the end, and remove oldest one
|
||||
let removed = this.__push(nextValue);
|
||||
// smooth value using all values from buffer
|
||||
let result = this.buffer.reduce((last, current) => {
|
||||
return this.smoothing * current + (1 - this.smoothing) * last
|
||||
}, removed);
|
||||
// replace smoothed value
|
||||
this.buffer[this.buffer.length - 1] = result;
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth array of values
|
||||
*
|
||||
* @param {array} values
|
||||
* @returns {undefined}
|
||||
* @access public
|
||||
*/
|
||||
smoothArray(values) {
|
||||
let value = values[0];
|
||||
for (let i = 1; i < values.length; i++) {
|
||||
let currentValue = values[i];
|
||||
value += (currentValue - value) * this.smoothing;
|
||||
values[i] = Math.round(value);
|
||||
}
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
/* global apollo, subscriptions, gql */
|
||||
|
||||
/**
|
||||
@ -6053,11 +6139,8 @@
|
||||
window.Capabilities = Capabilities;
|
||||
window.CapabilitiesTests = CapabilitiesTests;
|
||||
|
||||
/** Basic class for poppable elements that need to be closed as soon as one poppable is
|
||||
* shown.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/**
|
||||
* A base class for scatter specific events.
|
||||
*
|
||||
@ -6322,7 +6405,8 @@
|
||||
scaleAutoClose = false,
|
||||
scaleCloseThreshold = 0.10,
|
||||
scaleCloseBuffer = 0.05,
|
||||
maxRotation = Angle.degree2radian(5)
|
||||
maxRotation = Angle.degree2radian(5),
|
||||
useLowPassFilter = true
|
||||
} = {}) {
|
||||
if (rotationDegrees != null && rotation != null) {
|
||||
throw new Error('Use rotationDegrees or rotation but not both')
|
||||
@ -6366,7 +6450,12 @@
|
||||
this.resizable = resizable;
|
||||
this.mouseZoomFactor = mouseZoomFactor;
|
||||
this.autoBringToFront = autoBringToFront;
|
||||
|
||||
this.useLowPassFilter = useLowPassFilter;
|
||||
if (useLowPassFilter) {
|
||||
this.rotateLPF = new LowPassFilter();
|
||||
this.zoomLPF = new LowPassFilter();
|
||||
this.zoomLPF.setup([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
}
|
||||
this.dragging = false;
|
||||
this.onTransform = onTransform != null ? [onTransform] : null;
|
||||
this.onClose = onClose != null ? [onClose] : null;
|
||||
@ -6390,6 +6479,11 @@
|
||||
this.bringToFront();
|
||||
this.killAnimation();
|
||||
this.observeVelocity();
|
||||
if (this.useLowPassFilter) {
|
||||
this.rotateLPF.clear();
|
||||
this.zoomLPF.clear();
|
||||
this.zoomLPF.setup([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -6403,14 +6497,19 @@
|
||||
let delta = interaction.delta();
|
||||
if (delta != null) {
|
||||
this.addVelocity(delta);
|
||||
let alpha = delta.rotate;
|
||||
let rotate = delta.rotate;
|
||||
let zoom = delta.zoom;
|
||||
if (this.maxRotation != null) {
|
||||
if (Math.abs(alpha) > this.maxRotation) {
|
||||
alpha = 0;
|
||||
if (Math.abs(rotate) > this.maxRotation) {
|
||||
rotate = 0;
|
||||
}
|
||||
}
|
||||
this.transform(delta, delta.zoom, alpha, delta.about);
|
||||
if (delta.zoom != 1) this.interactionAnchor = delta.about;
|
||||
if (this.useLowPassFilter) {
|
||||
rotate = this.rotateLPF.next(rotate);
|
||||
zoom = this.zoomLPF.next(zoom);
|
||||
}
|
||||
this.transform(delta, zoom, rotate, delta.about);
|
||||
if (zoom != 1) this.interactionAnchor = delta.about;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14870,7 +14969,7 @@
|
||||
* @extends Popup
|
||||
* @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/popupmenu.html|DocTest}
|
||||
*/
|
||||
class PopupMenu$1 extends Popup {
|
||||
class PopupMenu extends Popup {
|
||||
|
||||
/**
|
||||
* Creates an instance of a PopupMenu.
|
||||
@ -15519,7 +15618,7 @@
|
||||
window.Stylus = Stylus;
|
||||
window.Switch = Switch;
|
||||
window.Popup = Popup;
|
||||
window.PopupMenu = PopupMenu$1;
|
||||
window.PopupMenu = PopupMenu;
|
||||
window.Modal = Modal;
|
||||
window.Volatile = Volatile;
|
||||
window.Message = Message;
|
||||
|
@ -16,7 +16,7 @@ import {FrameContainer, FrameTarget} from './frames.js'
|
||||
import {Inspect} from './inspect.js'
|
||||
import {PointMap, InteractionPoints, Interaction, IInteractionTarget, InteractionDelta, InteractionMapper, InteractionDelegate, IInteractionMapperTarget} from './interaction.js'
|
||||
import {ResizeEvent, DOMScatterContainer, AbstractScatter, DOMScatter, ScatterEvent, BaseEvent} from './scatter.js'
|
||||
import {Cycle, Colors, Elements, Angle, Dates, Points, Polygon, Rect, Sets, Strings, isEmpty, getId, lerp, debounce, randomInt, randomFloat} from './utils.js'
|
||||
import {Cycle, Colors, Elements, Angle, Dates, Points, Polygon, Rect, Sets, Strings, isEmpty, getId, lerp, debounce, randomInt, randomFloat, LowPassFilter} from './utils.js'
|
||||
import UITest from './uitest.js'
|
||||
|
||||
/* Needed to ensure that rollup.js includes class definitions and the classes
|
||||
@ -62,6 +62,7 @@ window.InteractionMapper = InteractionMapper
|
||||
window.InteractionPoints = InteractionPoints
|
||||
window.Interface = Interface
|
||||
window.Logging = Logging
|
||||
window.LowPassFilter = LowPassFilter
|
||||
window.PointMap = PointMap
|
||||
window.Rect = Rect
|
||||
window.Points = Points
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable no-console */
|
||||
/* globals TweenLite debugCanvas */
|
||||
import { Points, Polygon, Angle, Elements } from './utils.js'
|
||||
import { Points, Polygon, Angle, Elements, LowPassFilter } from './utils.js'
|
||||
import Events from './events.js'
|
||||
import { InteractionMapper } from './interaction.js'
|
||||
import { Capabilities } from './capabilities.js'
|
||||
import PopupMenu from './popupmenu.js'
|
||||
|
||||
/**
|
||||
* A base class for scatter specific events.
|
||||
*
|
||||
@ -270,7 +270,8 @@ export class AbstractScatter extends Throwable {
|
||||
scaleAutoClose = false,
|
||||
scaleCloseThreshold = 0.10,
|
||||
scaleCloseBuffer = 0.05,
|
||||
maxRotation = Angle.degree2radian(5)
|
||||
maxRotation = Angle.degree2radian(5),
|
||||
useLowPassFilter = true
|
||||
} = {}) {
|
||||
if (rotationDegrees != null && rotation != null) {
|
||||
throw new Error('Use rotationDegrees or rotation but not both')
|
||||
@ -314,7 +315,12 @@ export class AbstractScatter extends Throwable {
|
||||
this.resizable = resizable
|
||||
this.mouseZoomFactor = mouseZoomFactor
|
||||
this.autoBringToFront = autoBringToFront
|
||||
|
||||
this.useLowPassFilter = useLowPassFilter
|
||||
if (useLowPassFilter) {
|
||||
this.rotateLPF = new LowPassFilter()
|
||||
this.zoomLPF = new LowPassFilter()
|
||||
this.zoomLPF.setup([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
}
|
||||
this.dragging = false
|
||||
this.onTransform = onTransform != null ? [onTransform] : null
|
||||
this.onClose = onClose != null ? [onClose] : null
|
||||
@ -338,6 +344,11 @@ export class AbstractScatter extends Throwable {
|
||||
this.bringToFront()
|
||||
this.killAnimation()
|
||||
this.observeVelocity()
|
||||
if (this.useLowPassFilter) {
|
||||
this.rotateLPF.clear()
|
||||
this.zoomLPF.clear()
|
||||
this.zoomLPF.setup([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -351,14 +362,19 @@ export class AbstractScatter extends Throwable {
|
||||
let delta = interaction.delta()
|
||||
if (delta != null) {
|
||||
this.addVelocity(delta)
|
||||
let alpha = delta.rotate
|
||||
let rotate = delta.rotate
|
||||
let zoom = delta.zoom
|
||||
if (this.maxRotation != null) {
|
||||
if (Math.abs(alpha) > this.maxRotation) {
|
||||
alpha = 0
|
||||
if (Math.abs(rotate) > this.maxRotation) {
|
||||
rotate = 0
|
||||
}
|
||||
}
|
||||
this.transform(delta, delta.zoom, alpha, delta.about)
|
||||
if (delta.zoom != 1) this.interactionAnchor = delta.about
|
||||
if (this.useLowPassFilter) {
|
||||
rotate = this.rotateLPF.next(rotate)
|
||||
zoom = this.zoomLPF.next(zoom)
|
||||
}
|
||||
this.transform(delta, zoom, rotate, delta.about)
|
||||
if (zoom != 1) this.interactionAnchor = delta.about
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,27 @@ is compatible with arrays of absolute points.
|
||||
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>
|
||||
|
91
lib/utils.js
91
lib/utils.js
@ -75,7 +75,7 @@ export function sample(population, k) {
|
||||
if (n <= setsize) {
|
||||
// An n-length list is smaller than a k-length set
|
||||
let pool = population.slice()
|
||||
for (let i = 0; i < k; i++) { // invariant: non-selected at [0,n-i)
|
||||
for (let i = 0; i < k; i++) { // inletiant: non-selected at [0,n-i)
|
||||
let j = Math.random() * (n - i) | 0
|
||||
result[i] = pool[j]
|
||||
pool[j] = pool[n - i - 1] // move non-selected item into vacancy
|
||||
@ -404,8 +404,8 @@ export class Points {
|
||||
|
||||
// Distance == 0.0 indicates an inside relation.
|
||||
static distanceToRect(p, r) {
|
||||
var cx = Math.max(Math.min(p.x, r.x + r.width), r.x)
|
||||
var cy = Math.max(Math.min(p.y, r.y + r.height), r.y)
|
||||
let cx = Math.max(Math.min(p.x, r.x + r.width), r.x)
|
||||
let cy = Math.max(Math.min(p.y, r.y + r.height), r.y)
|
||||
return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy))
|
||||
}
|
||||
|
||||
@ -968,4 +968,89 @@ export class Strings {
|
||||
}
|
||||
|
||||
|
||||
export class LowPassFilter {
|
||||
|
||||
constructor(smoothing = 0.5, bufferMaxSize=10) {
|
||||
this.smoothing = smoothing // must be smaller than 1
|
||||
this.buffer = [] // FIFO queue
|
||||
this.bufferMaxSize = bufferMaxSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup buffer with array of values
|
||||
*
|
||||
* @param {array} values
|
||||
* @returns {array}
|
||||
* @access public
|
||||
*/
|
||||
setup(values) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
this.__push(values[i])
|
||||
}
|
||||
return this.buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear buffer to prepare for new values.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
clear() {
|
||||
this.buffer = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new value to buffer (FIFO queue)
|
||||
*
|
||||
* @param {integer|float} value
|
||||
* @returns {integer|float}
|
||||
* @access private
|
||||
*/
|
||||
__push(value) {
|
||||
let removed = (this.buffer.length === this.bufferMaxSize)
|
||||
? this.buffer.shift()
|
||||
: 0
|
||||
|
||||
this.buffer.push(value)
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth value from stream
|
||||
*
|
||||
* @param {integer|float} nextValue
|
||||
* @returns {integer|float}
|
||||
* @access public
|
||||
*/
|
||||
next(nextValue) {
|
||||
|
||||
// push new value to the end, and remove oldest one
|
||||
let removed = this.__push(nextValue)
|
||||
// smooth value using all values from buffer
|
||||
let result = this.buffer.reduce((last, current) => {
|
||||
return this.smoothing * current + (1 - this.smoothing) * last
|
||||
}, removed)
|
||||
// replace smoothed value
|
||||
this.buffer[this.buffer.length - 1] = result
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth array of values
|
||||
*
|
||||
* @param {array} values
|
||||
* @returns {undefined}
|
||||
* @access public
|
||||
*/
|
||||
smoothArray(values) {
|
||||
let value = values[0]
|
||||
for (let i = 1; i < values.length; i++) {
|
||||
let currentValue = values[i]
|
||||
value += (currentValue - value) * this.smoothing
|
||||
values[i] = Math.round(value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user