Skip to content

Commit

Permalink
Finshed fixing tap/drag event disambiguation
Browse files Browse the repository at this point in the history
click to navigate now works on tablets (ipad safari) and PCs (chrome)
  • Loading branch information
atruskie committed Dec 9, 2015
1 parent 6e1a7a6 commit 9d69081
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ event-distribution-detail {
.visualizationBrushLaneOverlay {
stroke: $visualization-brush-stroke;
fill: $visualization-brush-lane-overlay-fill;
pointer-events: none;
}

.tiles {
Expand Down
143 changes: 127 additions & 16 deletions src/app/d3Bindings/eventDistribution/distributionDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ angular
.service(
"DistributionDetail",
[
"$window",
"$timeout",
"d3",
"TimeAxis",
"distributionCommon",
"distributionTilingFunctions",
function (d3, TimeAxis, common, TilingFunctions) {
function ($window, $timeout, d3, TimeAxis, common, TilingFunctions) {
return function DistributionDetail(target, data, dataFunctions, uniqueId) {
var self = this,
container = d3.select(target),
Expand Down Expand Up @@ -48,6 +50,14 @@ angular
visualizationDuration = null,
// HACK: a "lock" placed around the invocation of manual zoom events. Assumes synchronicity.
_lockManualZoom = false,
/**
* HACK: a flag used to disambiguate between clicks and drags.
* @type {Number}
* @private
*/
_isZooming = null,
_hasMouseMoved = null,
_navigateTimeoutPromise = null,
laneLinesGroup,
laneLabelsGroup,
visualizationBrushArea,
Expand Down Expand Up @@ -109,7 +119,9 @@ angular
* A cache of tiles generated from items.
* @type {WeakMap<item, Map<resolution, Array<tiles>>>}
*/
tileCache = new WeakMap();
tileCache = new WeakMap(),
clickOrDblTimeoutMilliseconds = 300,
clickOrDragThresholdPixels = 1;

// exports
self.updateData = updateData;
Expand Down Expand Up @@ -155,7 +167,7 @@ angular
return;
}

// update public property
// update public property
self.visibleExtent = extent;

// redraw elements and axes
Expand Down Expand Up @@ -340,8 +352,9 @@ angular

tilesGroup.clipPath("url(#" + tilesGroupClipId + ")");


tilesGroup.on("click", () => common.navigateTo(tilingFunctions, visibleTiles, xScale, tilesGroup));
tilesGroup.on("mousedown", onMouseDown);
tilesGroup.on("click", onClickNavigate);
tilesGroup.on("dblclick", onDblClick);


xAxisSelected = new TimeAxis(
Expand Down Expand Up @@ -424,6 +437,7 @@ angular
// falsely trigger zoom events to force d3 to re-render with new scale
zoomUpdate();


// by this point the two methods for calculating visible duration should be equivalent
let min = +self.minimum || 0,
max = +self.maximum || 0,
Expand Down Expand Up @@ -512,7 +526,7 @@ angular
var labelAttrs = {
x: -laneLabelMarginRight,
y: labelY,
dy: fontHeight - (fontLineHeight - fontHeight) / 2 ,
dy: fontHeight - (fontLineHeight - fontHeight) / 2,
"text-anchor": "start",
class: "laneText"
},
Expand Down Expand Up @@ -651,7 +665,8 @@ angular
*/
width: d => {
var imageScale = d.resolution / self.resolution;
//console.debug("DistributionVisualisation:updateElements:width: current image ratio:", imageScale, d.resolution, self.resolution);
//console.debug("DistributionVisualisation:updateElements:width: current image
// ratio:", imageScale, d.resolution, self.resolution);
return tileWidthPixels * (imageScale);
}
},
Expand Down Expand Up @@ -777,17 +792,46 @@ angular
visualizationBrushLaneOverlay.attr("width", width).translate([left, top]);
}

/**
* Analogous to touchstart / mousedown.
* HOWEVER: touchstart happens before mousedown,
* so if it is a touch event, onZoomStart happens significantly before onMouseDown
* whereas if it is a mouse event, onMouseDown happens before onZoomStart.
* We can use `d3.event.sourceEvent instanceof TouchEvent` to disambiguate.
*/
function onZoomStart() {
//console.debug("DistributionDetail:zoomStart:", d3.event.translate, d3.event.scale);
console.debug("DistributionDetail:zoomStart:", d3.event.translate, d3.event.scale);

// HACK: check whether this event was triggered manually
var isManual = _lockManualZoom;

if (isManual) {
return;
}

// update which lane is shown in visualisation
var categoryChanged = switchSelectedCategory();

// update y-axis and lane heights
updateYScales();
// if categoryChanged don't let onMouseDown/onClickNavigate trigger
if (categoryChanged) {
console.warn("DistributionDetail:zoomStart:preventDefault");
d3.event.sourceEvent.preventDefault();
}

// need the full re-render because of the touchstart event
if (categoryChanged) {
// updates the public visibleExtent field
// and re-renders entire surface
self.updateExtent(xScale.domain());
}

// re-renders entire surface
updateMain(false, false, categoryChanged);
//if (categoryChanged) {
// // update y-axis and lane heights
// updateYScales();
//
// // re-renders entire surface
// updateMain(false, false, categoryChanged);
//}
}

function onZoom() {
Expand All @@ -798,7 +842,8 @@ angular
var isManual = _lockManualZoom;

// debugging, fixed zoom scale at specified resolutoin
//zoom.scale([ getZoomFactorForResolution([self.minimum, self.maximum], self.visibleExtent, 60) ]);
//zoom.scale([ getZoomFactorForResolution([self.minimum, self.maximum], self.visibleExtent, 60)
// ]);

// prevent translating off the edge of our data (i.e. clamp the zoom/pan)
var domain = null;
Expand All @@ -809,7 +854,8 @@ angular

updatePublicZoomScale();

//console.debug("DistributionDetail:zoom:", d3.event.translate, d3.event.scale, domain, zoom.translate(), isManual);
console.debug("DistributionDetail:zoom:", d3.event.translate, d3.event.scale, domain,
zoom.translate(), isManual);

// don't propagate cyclical events
if (isManual) {
Expand All @@ -828,7 +874,7 @@ angular
}

function onZoomEnd() {
//console.debug("DistributionDetail:zoomEnd:", d3.event.translate, d3.event.scale);
console.debug("DistributionDetail:zoomEnd:", d3.event.translate, d3.event.scale);

if (isItemsToRender) {
updatePublicZoomScale();
Expand Down Expand Up @@ -874,7 +920,7 @@ angular
//console.debug("DistributionDetail:Category switch");
var rounded = 0;
if (!_lockManualZoom) {
var mouseY = d3.mouse(main[0][0])[1];
var mouseY = d3.mouse(main.node())[1];
var inverted = yScale.invert(mouseY);
rounded = Math.floor(inverted);
}
Expand Down Expand Up @@ -1038,6 +1084,71 @@ angular
return yScale(self.lanes.indexOf(self.selectedCategory)) + xAxisHeight + lanePaddingTop;
}

function distance(pointA, pointB) {
return Math.sqrt(
Math.pow(pointA[0] - pointB[0], 2) +
Math.pow(pointA[1] - pointB[1], 2)
);
}

function onMouseDown() {
console.debug("distributionDetail::onMouseDown:");
// HACK: disambiguate between clicks and pans
_isZooming = $window.performance.now();
_hasMouseMoved = d3.mouse(main.node());
}

function onClickNavigate() {
let now = $window.performance.now(),
deltaTime = now - _isZooming,
newPosition = d3.mouse(main.node()),
deltaPosition = distance(newPosition, _hasMouseMoved);

console.debug("distributionDetail::onClickNavigate: deltaTime, deltaPosition:", deltaTime, deltaPosition);

// if the mouse hasn't moved
if (deltaPosition < clickOrDragThresholdPixels) {
console.debug("distributionDetail::onClickNavigate: **click** NOT drag", $window.performance.now());

// and if we're not already tracking another click in a series for a double click
if (!_navigateTimeoutPromise) {
console.warn("distributionDetail::onClickNavigate::beforeTimeout:", $window.performance.now());
// delay navigate for a set amount of time...
// give the double click event handler time to cancel the navigate
_navigateTimeoutPromise = $timeout(
//() => console.debug("distributionDetail::onClickNavigate::timeoutComplete:", $window.performance.now()),
() => {
// only navigate if the timeout was successful
console.warn("distributionDetail::onClickNavigate::timeoutResolved: Navigating now!", $window.performance.now());
//common.navigateTo(tilingFunctions, visibleTiles, xScale, tilesGroup);
},
clickOrDblTimeoutMilliseconds);

// make sure we keep original reference to $timeout promise (i.e. don't chain the then)
_navigateTimeoutPromise.finally(() => {
// this may not matter if we're navigating away,
// but cleaning up state is responsible
_navigateTimeoutPromise = null;
});
}
}

_isZooming = null;
_hasMouseMoved = null;
}

/**
* This handler exists solely to cancel navigation on a single click
* if it in fact turns out to be a double click.
*/
function onDblClick() {
console.debug("distributionDetail::onDblClick: Cancelling navigate");

// cancel the navigate from a single click
$timeout.cancel(_navigateTimeoutPromise);
}


};
}
]
Expand Down

0 comments on commit 9d69081

Please sign in to comment.