At A Glance Main Projects Tutorials Resume

Contact


Email: palen1c at gmail.com




Dygraphs Tablet and Phone Custom Interaction Touch Point Selection

Sat, 20 Apr 2013 00:09:02 EST

In this article I am going to show you how to implement custom interactions in the Dygraphs javascript charting library specifically for mobile devices. I was updating the backed reporting system of an exhibit this week that uses line graphs generated by javascript to show real time stats regarding donations. I replaced the current charting library I was using with Dygraphs and was very pleased. Dygraphs is a very capable and fast open source charting library. Dygraphs comes with an out of the box HTML5 touch implementation for devices with touchscreens. The problem with the out of the box implementation is that data points are not selectable.

In order to change how touches and clicks manipulate a dygraphs graph, the development team has exposed what are called custom interactions. The custom interactions demo has a good general overview for interactions with a mouse, but not touch.

After digging and digging, some experimentation I got point selection working on the default interaction template with one finger touch. I tested this on an Ipad1, Ipad2, Android 2.2.3 Sony, Android 2.2.3 VM, and Android 4 VM. Here's a demo:



OPEN IN NEW WINDOW

The Code

So in order to do this, you setup a custom interaction function to route the touchstart events through. You can actually clone the default interaction model for other events you want to keep. For further investigation you can see the dygraph-interaction-model.js file on github .

So in order to re-route the touchstart event while keeping the default interaction model, in my dygraphs instantiation I have:
function init() {
// Create the chart
chart = new Dygraph(
document.getElementById("chartContainer"),
"userData.csv", // path to CSV file
{ ylabel: 'User Patience',
xlabel: 'Time',
hideOverlayOnMouseOut: false,
drawPoints: true,
animatedZooms:false,
fillGraph:true,
legend: "always",
interactionModel:{
mousedown: Dygraph.defaultInteractionModel.mousedown,
mousemove: Dygraph.defaultInteractionModel.mousemove,
mouseup: Dygraph.defaultInteractionModel.mouseup,
touchstart: newDygraphTouchstart,
touchend: Dygraph.defaultInteractionModel.touchend,
touchmove: Dygraph.defaultInteractionModel.touchmove
}
} // options
);
};


The important part is the interactionModel object. You'll see that I specify the default interaction model for many of the events, except touchstart. We just create another javascript function to route the touchstart events through.

My function in this example is called "newDygraphTouchstart". Here is that function. Keep in mind this is just a copy of the default touchstart except the section under one touch where I changed three lines. The function parameter "g" references the actual dygraph graph, so you can use the dygraph api functions on it.

UPDATE: 4/30/2013 - I submitted a patch to dygraphs interaction model with what was previously seen below, and Dan Vanderkam, the author of Dygraphs contacted me with a way better solution. which can now be found below. I used strikethrough in the example below so you can see the original solution which is not recommended.

function newDygraphTouchstart(event, g, context) {
// This right here is what prevents IOS from doing its own zoom/touch behavior
// It stops the node from being selected too
event.preventDefault(); // touch browsers are all nice.

if (event.touches.length > 1) {
// If the user ever puts two fingers down, it's not a double tap.
context.startTimeForDoubleTapMs = null;
}

var touches = [];
for (var i = 0; i < event.touches.length; i++) {
var t = event.touches[i];
// we dispense with 'dragGetX_' because all touchBrowsers support pageX
touches.push({
pageX: t.pageX,
pageY: t.pageY,
dataX: g.toDataXCoord(t.pageX),
dataY: g.toDataYCoord(t.pageY)
// identifier: t.identifier
});
}
context.initialTouches = touches;

if (touches.length == 1) {
// This is just a swipe.
context.initialPinchCenter = touches[0];
context.touchDirections = { x: true, y: true };

// ADDITION - this needs to select the points
var closestTouchP = g.findClosestPoint(touches[0].pageX,touches[0].pageY);
if(closestTouchP) {
var selectionChanged = g.setSelection(closestTouchP.row, closestTouchP.seriesName);
}

g.mouseMove_(event);

} else if (touches.length >= 2) {
// It's become a pinch!
// In case there are 3+ touches, we ignore all but the "first" two.

// only screen coordinates can be averaged (data coords could be log scale).
context.initialPinchCenter = {
pageX: 0.5 * (touches[0].pageX + touches[1].pageX),
pageY: 0.5 * (touches[0].pageY + touches[1].pageY),

// TODO(danvk): remove
dataX: 0.5 * (touches[0].dataX + touches[1].dataX),
dataY: 0.5 * (touches[0].dataY + touches[1].dataY)
};

// Make pinches in a 45-degree swath around either axis 1-dimensional zooms.
var initialAngle = 180 / Math.PI * Math.atan2(
context.initialPinchCenter.pageY - touches[0].pageY,
touches[0].pageX - context.initialPinchCenter.pageX);

// use symmetry to get it into the first quadrant.
initialAngle = Math.abs(initialAngle);
if (initialAngle > 90) initialAngle = 90 - initialAngle;

context.touchDirections = {
x: (initialAngle < (90 - 45/2)),
y: (initialAngle > 45/2)
};
}

// save the full x & y ranges.
context.initialRange = {
x: g.xAxisRange(),
y: g.yAxisRange()
};
};


IOS Bonus

If you are still attentive after that mess; you deserve a BONUS! Lets discuss an IOS bonus in this example. I have made it so on IOS the canvas is not highlighted when users touch. I didn't know this was possible when doing the sample robot farm game. You just set a series of CSS styles telling webkit to not highlight the canvas element. Awesome!
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
canvas {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
outline: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */
}
</style>


Charles Palen has been involved in the technology sector for several years. His formal education focused on Enterprise Database Administration. He currently works as the principal software architect and manager at Transcending Digital where he can be hired for your next contract project. Charles is a full stack developer who has been on the front lines of small business and enterprise for over 10 years. Charles current expertise covers the areas of .NET, Java, PHP, Node.js, Javascript, HTML, and CSS. Charles created Technogumbo in 2008 as a way to share lessons learned while making original products.

Comments

No one has posted any comments yet, be the first

Comments are currently disabled.