/* Copyright 2014-2017 Research Foundation State University of New York */
/* This file is part of QUB Online. */
/* QUB Online is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* QUB Online is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* You should have received a copy of the GNU General Public License, */
/* named LICENSE.txt, in the QUB Online program directory. If not, see */
/* . */
define("qubtrace", ["hammer"], Hammer => {
"use strict";
const exports = {};
function batchDelayed(action, msDelay) {
let handle = null;
const timeout = () => {
handle = null;
actSoon.active = false;
action();
};
const actSoon = () => {
if ( handle ) {
clearTimeout(handle);
}
handle = setTimeout(timeout, msDelay);
actSoon.active = true;
}
return actSoon;
}
function eventOffset(e, pageX, pageY) {
var x, y;
if ( typeof(pageX) === 'undefined' ) {
x = e.clientX;
} else {
x = pageX - (document.body.scrollLeft + document.documentElement.scrollLeft);
}
if ( typeof(pageY) === 'undefined' ) {
y = e.clientY;
} else {
y = pageY - (document.body.scrollTop + document.documentElement.scrollTop);
}
const target = e.target || e.srcElement,
rect = target.getBoundingClientRect();
return {
x: x - rect.left,
y: y - rect.top
};
};
function extrema(buf, start, count) {
start = start || 0;
count = count || buf.length - start;
if ( ! count ) {
return {lo: 0, hi: 0};
}
let low = buf[start],
high = low,
i = 1;
for (; i high ) {
high = y;
}
}
return {lo: low, hi: high};
}
exports.pinchInterval = function pinchInterval(from, to, center, scale) {
const pInFrame = (center - from) / (to - from),
w = Math.min(1, (to - from)/scale);
return {
from: Math.max(0, center - pInFrame*w),
to: Math.min(1, center + (1 - pInFrame)*w)
};
}
function stats(buf, start, count) {
let mean = 0.0,
rss = 0.0,
n = 0,
i = 1;
var last_mean;
if ( n ) {
mean = buf[start];
n = 1;
}
for (; i hi ) {
gs_hi = hi;
}
return {lo, hi, gs_lo, gs_hi};
}
exports.raster_samples = function raster_samples(buf, start, count, w, spp, y_res) {
const lows = [],
highs = [],
gauss_lows = [],
gauss_highs = [],
arr = new Float32Array(buf);
let i = 0;
for (; i= count ) {
break;
}
iSam += start;
if ( iSam >= arr.length ) {
break;
}
const jSam = Math.min(arr.length, start + Math.round((i+1)*spp)),
rs = raster_stats(arr, iSam, (jSam-iSam), y_res);
lows.push(rs.lo);
highs.push(rs.hi);
gauss_lows.push(rs.gs_lo);
gauss_highs.push(rs.gs_hi);
}
return {lows, highs, gauss_lows, gauss_highs};
}
const int32ToFloat = a => {
const buf = new ArrayBuffer(4),
v = new DataView(buf);
v.setInt32(0, a);
return v.getFloat32(0);
};
const UNSET = int32ToFloat(0x2aaaaaaa);
function extrema_std(buf, start, count) {
let lo = UNSET,
hi = UNSET;
if ( ! count ) {
return {lo, hi};
}
let i = 0;
for (; i= 0 ) {
const y = buf[2*start];
if ( lo === UNSET ) {
lo = y - std;
hi = y + std;
} else {
lo = Math.min(lo, y-std);
hi = Math.max(hi, y+std);
}
}
}
return {lo, hi};
}
function raster_stats_std(buf, start, n, y_res) {
if ( ! n ) { return {lo: 0, hi: 0}; };
const ex = extrema_std(buf, start, n);
if ( ex.lo === UNSET ) {
return [ex.lo, ex.hi];
}
const midpoint = (ex.hi + ex.lo) / 2;
let diff = (ex.hi - ex.lo) / 2.0;
if ( diff < y_res ) {
diff = y_res;
}
const lo = midpoint - diff,
hi = midpoint + diff;
return [lo, hi];
}
exports.raster_samples_std = function raster_samples_std(buf, start, count, w, spp, y_res) {
const lows = new Float32Array(w),
highs = new Float32Array(w),
arr = new Float32Array(buf),
alen2 = arr.length>>1;
start = start|0;
count = count|0;
w = Math.min(w, 1+(count-1)/spp, 1+(alen2 - 1 - start)/spp)|0;
let jSam = start,
i = 0;
for (; i pxh - (y - yLo)*ppy;
ctx.save();
ctx.strokeStyle = lineStyle;
ctx.beginPath();
ctx.translate(px0+0.5, 0);
ctx.moveTo(0, y2p(points[stride*offset]));
const dx = 1/spp;
let i = 0,
x = 0;
for (; i pxh - (y - yLo)*ppy,
pps = 1.0 / spp,
x2p = x => pps*(x-offset),
ndot = X.length;
ctx.save();
ctx.fillStyle = dotStyle;
selectedRad = selectedRad || 1.4*rad;
let i = 0;
for (; i= offset) && (x < (offset+n)) ) {
if ( x === selectedX ) {
ctx.fillRect(x2p(x)-selectedRad, y2p(Y[i])-selectedRad, 2*selectedRad, 2*selectedRad);
} else {
ctx.fillRect(x2p(x)-rad, y2p(Y[i])-rad, 2*rad, 2*rad);
}
}
}
ctx.restore();
};
exports.drawLinesStd = function drawLinesStd(ctx, px0, pxw, pxh, points, offset, n, lineStyle, spp, yLo, ypp) {
const segments = [];
let start = null,
haveStd = false,
i = 0;
for (; i= 0 ) {
start = i;
}
if ( s > 0 ) {
haveStd = true;
}
}
if ( start !== null ) {
segments.push({start: start, end: n});
}
if ( segments.length && (segments[0].start === 0) && (segments[0].end === n) && ! haveStd ) {
return exports.drawLines(ctx, px0, pxw, pxh, points, offset, n, lineStyle, spp, yLo, ypp, 2);
}
const ppy = 1.0 / ypp,
y2p = y => pxh - (y - yLo)*ppy;
ctx.save();
ctx.fillStyle = lineStyle;
ctx.beginPath();
ctx.translate(px0+0.5, 0);
const dx = 1/spp;
segments.forEach(seg => {
let x = dx*seg.start;
const start = seg.start + offset,
end = seg.end + offset;
let s = Math.max(2*ypp, points[2*start+1]);
ctx.moveTo(x, y2p(points[2*start]-s));
let i = start;
for (; i=start; --i, x-=dx) {
s = Math.max(2*ypp, points[2*i+1]);
ctx.lineTo(x, y2p(points[2*i]+s));
}
ctx.fill();
});
ctx.restore();
};
exports.drawScalebar = function drawScalebar(canvas, emsize, plotW, plotH, xpp, signals, scalebarHeight, scalebarFont, scalebarBack, scalebarStyle) {
const h_ = Math.round(scalebarHeight * plotH)|0,
ctx = canvas.getContext("2d"),
fontHeight = (.3*h_)|0;
ctx.font = ''+fontHeight+'px '+scalebarFont;
const metrics = ctx.measureText("-0.00e+21"),
dx = exports.nearestRound(Math.min(plotW/2, Math.max(1.5*emsize, metrics.width*2/3)), xpp),
px = dx/xpp,
dy = signals.map(sig => exports.nearestRound(Math.min(plotH, Math.max(1.5*emsize, h_*2/3)), sig.ypp)),
py = dy.map((d,i) => d/signals[i].ypp);
let pyMax = 0;
py.forEach(p => {
if ( p && ! isNaN(p) ) {
pyMax = Math.max(p, pyMax);
}
});
const h = (pyMax + 1.2*fontHeight)|0,
lw = Math.max(1, Math.round((py.length ? h : h_)/20)),
w = (3*lw + px + fontHeight)|0,
wFull = Math.max(w, signals.length*(fontHeight+2*lw))|0;
if ( (h !== canvas.height) || (wFull != canvas.width) ) {
canvas.height = h;
canvas.width = wFull;
ctx.font = ''+fontHeight+'px '+scalebarFont;
}
ctx.save();
ctx.clearRect(0, 0, wFull, h)
ctx.fillStyle = scalebarBack;
ctx.fillRect(0, 0, wFull, h);
const offX = 0.5 + Math.round(0.5*(w-(px+2*lw+fontHeight))) + Math.max(0, wFull-w),
offY = 0.5 + (lw+fontHeight) + Math.round(0.5*(h - (pyMax+fontHeight+3*lw)));
ctx.translate(offX, offY);
ctx.lineWidth = lw;
ctx.strokeStyle = scalebarStyle;
ctx.fillStyle = scalebarStyle;
ctx.beginPath();
ctx.moveTo(lw, lw);
ctx.lineTo(lw+px, lw);
ctx.stroke();
const lw2 = lw/2;
py.forEach((p, i) => {
const x = lw+px-i*(fontHeight+2*lw);
ctx.strokeStyle = signals[i].color;
ctx.beginPath();
ctx.moveTo(x-lw2, lw2);
ctx.lineTo(x-lw2, lw2+p);
ctx.stroke();
});
const lblX = exports.formatTime(dx),
lblW = ctx.measureText(lblX).width;
ctx.textBaseline = 'alphabetic';
ctx.fillText(lblX, lw, -lw2);
ctx.textBaseline = 'top';
dy.forEach((d, i) => {
ctx.save();
ctx.translate(px+lw-i*(fontHeight+2*lw), py[i]);
ctx.rotate(-Math.PI/2);
ctx.fillText(exports.formatRound(d)+signals[i].units, 0, 0);
ctx.restore();
});
ctx.restore();
};
exports.nearestRound = function nearestRound(pixels, npp) {
const nExact = pixels * npp,
logExact = Math.log10(nExact),
decade = Math.floor(logExact),
logRem = logExact - decade,
base = Math.pow(10, decade);
if ( logRem < .1 ) {
return base;
} else if ( logRem < .3 ) {
return 2*base;
} else if ( logRem < .5 ) {
return 3*base;
} else if ( logRem < .8 ) {
return 5*base;
} else {
return 10*base;
}
};
exports.formatRound = function formatRound(x) {
if ( Math.abs(x) >= 1e-3 ) {
let s = x.toFixed(2);
while ( s.length > 1 ) {
const c = s[s.length-1];
if ( c === '0' || c === '.' ) {
s = s.substring(0, s.length-1);
if ( c === '.' ) {
break;
}
} else {
break;
}
}
return s;
} else {
return x.toExponential(2);
}
};
exports.formatTime = function formatTime(s) {
if ( s >= 50 ) {
// hh:mm:ss?
return ''+(Math.round(s)|0)+' s';
}
if ( s >= 5e-1 ) {
return exports.formatRound(s)+' s';
}
if ( s >= 5e-4 ) {
return exports.formatRound(1e3*s)+' ms';
}
if ( s >= 5e-7 ) {
return exports.formatRound(1e6*s)+' \u00b5s';
}
//if ( s >= 5e-10 ) {
return exports.formatRound(1e9*s)+' ns';
//}
};
exports.layoutSignals = function layoutSignals(w, h, emsize, Nstim) {
// top: stimulus signals
// default height h/3, min 3em, max h
// each signal default height sh/Nstim, min 2em, max h
// top at iStim*sh1
// bottom: current
// default height h*2/3, min 5em, max h
// -> {current: {top, height in px}, stimulus: [{top, height}, ...]
const stimsH = Nstim ? Math.min(h, Math.max(1.5*emsize, h/3)) : 0,
stimH = Nstim ? Math.min(h, Math.max(1.5*emsize, stimsH/Nstim)) : 0,
stimOff = Nstim ? (stimH / Nstim) : 0,
curH = Nstim ? Math.min(h, Math.max(2.5*emsize, h*2/3)) : h;
function inset(top, height) {
const margin = .05 * height;
return {
top: (top + margin)|0,
height: (height - 2*margin)|0
};
};
const stimulus = [];
let i = 0;
for (; i {
const propPx = hires ? 'scalebarHiresPx' : 'scalebarLoresPx',
propPy = hires ? 'scalebarHiresPy' : 'scalebarLoresPy';
if ( scale ) {
const parent = scale.parentNode;
let down = null;
const onMouseUp = evt => {
document.body.removeEventListener('mouseup', onMouseUp);
parent.removeEventListener('mousemove', onMouseMove);
};
const onMouseMove = evt => {
const rel = relMouseCoords(parent, evt),
x = Math.max(0, Math.min(parent.clientWidth-5, parseInt(scale.style.left) + rel.x - down.x)),
y = Math.max(0, Math.min(parent.clientHeight-5, parseInt(scale.style.top) + rel.y - down.y));
scale.style.left = ''+x+'px';
scale.style.top = ''+y+'px';
this.config[propPx] = (x < (parent.clientWidth/2)) ? (x/parent.clientWidth) : (x/(parent.clientWidth - scale.clientWidth));
this.config[propPy] = (y < (parent.clientHeight/2)) ? (y/parent.clientHeight) : (y/(parent.clientHeight - scale.clientHeight));
down = rel;
};
scale.addEventListener('mousedown', evt => {
evt.stopPropagation();
evt.preventDefault();
down = relMouseCoords(parent, evt);
document.body.addEventListener('mouseup', onMouseUp);
parent.addEventListener('mousemove', onMouseMove);
});
}
});
this.selFrom = this.selTo = 0; // as fractions 0..1 of total
this.segments = [];
this._chosen = -1;
this.maxNpoint = 0;
this.lo = this.hi = this.dyEx = this.ypp = null;
this.loStim = this.hiStim = this.dyStim = this.yppStim = null;
this._sampling = this.config.sampling;
this.drawSoon = batchDelayed(this.draw.bind(this), 50);
this.drawHiresSoon = batchDelayed(this.drawHires.bind(this), 50);
this.drawFitSoon = batchDelayed(this.drawFit.bind(this), 50);
this.reSelectSoon = batchDelayed(this.reSelect.bind(this), 50);
this.reshape();
this.mouseX0 = null;
this.canvasTouch = new Hammer(this.selCanvas);
this.canvasTouch.on("pan", this._onPanSel.bind(this));
this.zoom(0,1);
}
segmentCount(n) {
const nPrev = this.segments.length;
if ( typeof(n) === 'undefined' ) {
return nPrev;
}
if ( n < nPrev ) {
this.segments.splice(n, nPrev-n);
this.chosen(Math.min(this._chosen, n-1));
return this;
}
while ( n > this.segments.length ) {
this.segments.push({
n: 0,
seen: 0,
points: [],
stimuli: [],
stimIdl: [],
extrema: null,
indVar: null,
resampled: null
});
}
return this;
}
chosen(i) {
if ( typeof(i) === 'undefined' ) {
return this._chosen;
}
if ( (this._chosen !== i) && (i >= -1) && (i < this.segments.length) ) {
this._chosen = i;
this.drawSoon();
this.hires && this.drawHiresSoon();
}
return this;
}
segmentSize(i, n, stimCount) {
const seg = this.segments[i],
nPrev = seg.n;
if ( typeof(n) === 'undefined' ) {
return seg.n;
}
let nStim = seg.stimuli.length || 1;
if ( typeof(stimCount) !== 'undefined' ) {
nStim = stimCount;
}
if ( (n !== nPrev) || (seg.stimuli.length !== nStim) ) {
seg.points = new Float32Array(n);
seg.fit = new Float32Array(2*n);
let j = 0;
for (; j {
seg.seen = 0;
seg.extrema = null;
seg.resampled = null;
});
this.erase();
}
setBounds(lo, hi) {
this.lo = lo;
this.hi = hi;
expandY(this, this.h);
this.yppHi = this.dyEx / this.hHi;
this.segments.forEach(seg => seg.extrema && expandY(seg.extrema));
this.drawSoon();
this.hires && this.drawHiresSoon();
}
setStimBounds(lo, hi) {
//// TODO: separate scaling for each stim signal?
this.loStim = lo;
this.hiStim = hi;
this.dyStim = hi - lo;
this.yppStim = this.dyStim / (this.config.stimulusHeight * this.h);
this.drawSoon();
this.hires && this.drawHiresSoon();
}
zoom(from, to, immediate) {
if ( typeof(to) === 'undefined' ) {
const z = {
from: this.selFrom,
to: this.selTo,
offset: Math.round(this.selFrom*this.maxNpoint)
};
z.count = Math.round(z.to*this.maxNpoint) - z.offset + 1;
return z;
}
from = Math.max(0, from);
to = Math.min(1, to);
if ( (from !== this.selFrom) || (to !== this.selTo) ) {
this.selFrom = from;
this.selTo = to;
this.sppHi = this.zoom().count / this.w;
this.xppHi = this._sampling * this.sppHi;
this.drawSel();
if ( this.hires ) {
if ( immediate ) {
this.drawHires();
this.reSelect();
} else {
this.drawHiresSoon();
this.reSelectSoon();
}
} else {
this.reSelect();
}
}
return this;
}
reSelect(onSelect) {
onSelect = onSelect || this.config.onSelect;
if ( onSelect ) {
const iFrom = Math.round(this.selFrom*this.maxNpoint),
iTo = this.selTo ? Math.round(this.selTo*(this.maxNpoint-1)) : (this.maxNpoint-1),
segments = this.segments.map(seg => {
const segOut = {
selFrom: Math.min(seg.points.length-1, iFrom),
selTo: Math.min(seg.points.length-1, iTo),
indVar: seg.indVar,
sources: [seg.points],
signals: []
};
seg.stimuli.forEach((arr, i) => {
segOut.sources[i+1] = arr;
});
segOut.sources.forEach((src, i) => {
segOut.signals[i] = src.slice && src.slice(iFrom, Math.min(iTo, src.length));
});
return segOut;
});
onSelect(iFrom, iTo-iFrom+1, segments);
}
}
set(iSeg, offset, x, count, xOffset, segExtrema) {
const seg = this.segments[iSeg];
var ex;
xOffset = xOffset|0;
if ( x.length ) {
count = count || x.length;
if ( x.copyWithin ) {
seg.points.set(x.subarray(xOffset, xOffset+count), offset);
} else {
let i = 0;
for (; i {
if ( seg.n > maxNpoint ) {
maxNpoint = seg.n;
}
});
this.maxNpoint = maxNpoint;
this.spp = maxNpoint / this.w;
this.xpp = this._sampling * this.spp;
this.ypp = this.dyEx / this.h;
if ( this.hires ) {
this.wHi = this.hiresLayer.clientWidth;
this.hHi = this.hiresLayer.clientHeight;
if ( window.devicePixelRatio ) {
this.wHi *= window.devicePixelRatio;
this.hHi *= window.devicePixelRatio;
}
this.hiresCanvas.width = this.wHi;
this.hiresCanvas.height = this.hHi;
this.fitCanvas.width = this.wHi;
this.fitCanvas.height = this.hHi;
this.sppHi = this.zoom().count / this.wHi;
this.xppHi = this._sampling * this.sppHi;
this.yppHi = this.dyEx / this.hHi;
}
this.drawSoon();
this.drawSel();
this.hires && this.drawHiresSoon();
}
drawSel() {
const ctx = this.selCanvas.getContext('2d'),
zoom = this.zoom();
ctx.clearRect(0, 0, this.w, this.h);
ctx.fillStyle = this.config.selStyle;
ctx.fillRect(this.selFrom*this.w, 0, (this.selTo - this.selFrom)*this.w, this.h);
}
erase() {
const toErase = [this.canvas];
if ( this.hires ) {
toErase.push(this.hiresCanvas);
toErase.push(this.fitCanvas);
}
toErase.forEach(canvas => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = this.config.backStyle;
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
}
draw(offset, count) {
if ( typeof(offset) === 'undefined' ) {
offset = 0;
count = this.maxNpoint;
this.erase();
} else if ( typeof(count) === 'undefined' ) {
count = this.maxNpoint - offset;
}
const spp = this.spp,
i0 = Math.max(0, Math.min(this.w-1, Math.floor(offset/spp))),
i1 = Math.max(0, Math.min(this.w-1, Math.ceil((offset+count)/spp))),
ctx = this.canvas.getContext('2d'),
n = this.segments.length;
let i = 0;
for (; i= this.config.minSppRaster ) {
this._drawRasterStd(ctx, px0, pxw, pxh, seg.fit, offset, n, fitStyle, spp, extrema.loEx, extrema.dyEx/pxh);
} else {
exports.drawLinesStd(ctx, px0, pxw, pxh, seg.fit, offset, n, fitStyle, spp, extrema.loEx, extrema.dyEx/pxh);
}
}
_draw(ctx, px0, pxw, pxh, iSeg, offset, count, dataStyle, normStyle, lineStyle, stimulusStyle, resampledStyle) {
let n = count;
const seg = this.segments[iSeg];
if ( (seg.seen - offset) < count ) {
n = seg.seen - offset;
pxw = Math.round(pxw * n/count);
}
if ( n <= 1 ) { return; }
const spp = n / pxw,
extrema = seg.extrema || this;
if ( spp >= this.config.minSppRaster ) {
ctx.lineWidth = 1.0;
seg.stimuli.map((points, iStim) => this._drawRaster(ctx, px0, pxw, pxh*1.1*this.config.stimulusHeight, points, offset, n,
stimulusStyle[iStim], null, spp, this.loStim,
this.dyStim/(this.config.stimulusHeight*pxh)));
this._drawRaster(ctx, px0, pxw, pxh, seg.points, offset, n, dataStyle, normStyle, spp, extrema.loEx, extrema.dyEx/pxh);
} else {
ctx.lineWidth = 0.5*Math.max(1, Math.round(pxh*this.config.lineHeight)|0);
seg.stimuli.map((points, iStim) =>exports.drawLines(ctx, px0, pxw, pxh*1.1*this.config.stimulusHeight, points, offset, n,
stimulusStyle[iStim], spp, this.loStim,
this.dyStim/(this.config.stimulusHeight*pxh)));
exports.drawLines(ctx, px0, pxw, pxh, seg.points, offset, n, lineStyle, spp, extrema.loEx, extrema.dyEx/pxh);
}
if ( resampledStyle && seg.resampled ) {
exports.drawDots(ctx, px0, pxw, pxh, seg.resampled.closests, seg.resampled.means, offset, n, resampledStyle, this.config.resampledRad, spp, extrema.loEx, extrema.dyEx/pxh);
}
}
_drawRaster(ctx, px0, pxw, pxh, points, offset, n, dataStyle, normStyle, spp, yLo, ypp) {
if ( n > (points.length - offset) ) {
const coverage = (points.length - offset) / n;
n = points.length - offset;
pxw = Math.round(pxw * coverage)|0;
}
const lineWidth = Math.max(1, Math.round(pxh*this.config.lineHeight)|0),
raster = this.config.raster_samples(points.buffer, offset, n, pxw, spp, 2*lineWidth*ypp);
exports.drawRastered(ctx, px0, pxw, pxh, raster, dataStyle, normStyle, spp, yLo, ypp);
}
_drawRasterStd(ctx, px0, pxw, pxh, points, offset, n, dataStyle, spp, yLo, ypp) {
const raster = this.config.raster_samples_std(points.buffer, offset, n, pxw, spp, ypp);
exports.drawRastered(ctx, px0, pxw, pxh, raster, dataStyle, null, spp, yLo, ypp);
}
_onPanSel(event) {
const offset = eventOffset(event, event.center.x, event.center.y);
var x0, x1;
if ( event.deltaX < 0 ) {
x0 = offset.x;
x1 = x0 - event.deltaX;
} else {
x1 = offset.x;
x0 = x1 - event.deltaX;
}
this.zoom(x0/this.w, x1/this.w);
}
_onPanHires(event) {
if ( ! this._hiPan ) {
this._hiPan = {
selFrom: this.selFrom,
selTo: this.selTo,
pw: this.selTo - this.selFrom
};
}
const rq = - this._hiPan.pw * event.deltaX / this.wHi,
d = Math.max(-this._hiPan.selFrom, Math.min(1-this._hiPan.selTo, rq));
this.zoom(this._hiPan.selFrom+d, this._hiPan.selTo+d, true);
}
_onPanendHires(event) {
this._hiPan = null;
}
_onPinchHires(event) {
if ( ! this._hiPinch && (event.offsetDirection != Hammer.DIRECTION_NONE) ) {
const offset = eventOffset(event, event.center.x, event.center.y);
this._hiPinch = {
dir: event.offsetDirection,
x: offset.x,
y: offset.y,
selFrom: this.selFrom,
selTo: this.selTo
};
}
if ( this._hiPinch ) {
if ( this._hiPinch.dir & (Hammer.DIRECTION_LEFT | Hammer.DIRECTION_RIGHT) ) {
const center = this._hiPinch.selFrom + (this._hiPinch.selTo - this._hiPinch.selFrom)*this._hiPinch.x/this.wHi,
scaled = exports.pinchInterval(this._hiPinch.selFrom, this._hiPinch.selTo, center, event.scale);
this.zoom(scaled.from, scaled.to, true);
} else {
/// zoom in Y direction...
}
}
}
_onPinchendHires(event) {
this._hiPinch = null;
}
};
return exports;
});