diff --git a/src/config.js b/src/config.js index ca8f057..a582c6f 100644 --- a/src/config.js +++ b/src/config.js @@ -235,6 +235,9 @@ c3_chart_internal_fn.getDefaultConfig = function () { left: 0 }, title_position: 'top-center', + //TouchEvent configuration + touch_tap_radius : 20, //touch movement must be less than this to be a 'tap' + touch_tap_delay : 500, //clicks are suppressed for this many ms after a tap }; Object.keys(this.additionalConfig).forEach(function (key) { diff --git a/src/interaction.js b/src/interaction.js index 0a823ba..89243f0 100644 --- a/src/interaction.js +++ b/src/interaction.js @@ -20,16 +20,168 @@ c3_chart_internal_fn.initEventRect = function () { ); } } + + eventRectUpdate + .attr('class', $$.classEvent.bind($$)) + .attr("x", x) + .attr("y", y) + .attr("width", w) + .attr("height", h); +}; +c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) { + var $$ = this, d3 = $$.d3, config = $$.config, + tap = false, tapX; + + function click(shape, d) { + var index = d.index; + if ($$.hasArcType() || !$$.toggleShape) { return; } + if ($$.cancelClick) { + $$.cancelClick = false; + return; + } + if ($$.isStepType(d) && config.line_step_type === 'step-after' && d3.mouse(shape)[0] < $$.x($$.getXValue(d.id, index))) { + index -= 1; + } + $$.main.selectAll('.' + CLASS.shape + '-' + index).each(function (d) { + if (config.data_selection_grouped || $$.isWithinShape(this, d)) { + $$.toggleShape(this, d, index); + $$.config.data_onclick.call($$.api, d, this); + } + }); + } + + eventRectEnter.append("rect") + .attr("class", $$.classEvent.bind($$)) + .style("cursor", config.data_selection_enabled && config.data_selection_grouped ? "pointer" : null) + .on('mouseover', function (d) { + var index = d.index; + + if ($$.dragging || $$.flowing) { return; } // do nothing while dragging/flowing + if ($$.hasArcType()) { return; } + + // Expand shapes for selection + if (config.point_focus_expand_enabled) { $$.expandCircles(index, null, true); } + $$.expandBars(index, null, true); + + // Call event handler + $$.main.selectAll('.' + CLASS.shape + '-' + index).each(function (d) { + config.data_onmouseover.call($$.api, d); + }); + }) + .on('mouseout', function (d) { + var index = d.index; + if (!$$.config) { return; } // chart is destroyed + if ($$.hasArcType()) { return; } + $$.hideXGridFocus(); + $$.hideTooltip(); + // Undo expanded shapes + $$.unexpandCircles(); + $$.unexpandBars(); + // Call event handler + $$.main.selectAll('.' + CLASS.shape + '-' + index).each(function (d) { + config.data_onmouseout.call($$.api, d); + }); + }) + .on('mousemove', function (d) { + var selectedData, index = d.index, + eventRect = $$.svg.select('.' + CLASS.eventRect + '-' + index); + + if ($$.dragging || $$.flowing) { return; } // do nothing while dragging/flowing + if ($$.hasArcType()) { return; } + + if ($$.isStepType(d) && $$.config.line_step_type === 'step-after' && d3.mouse(this)[0] < $$.x($$.getXValue(d.id, index))) { + index -= 1; + } + + // Show tooltip + selectedData = $$.filterTargetsToShow($$.data.targets).map(function (t) { + return $$.addName($$.getValueOnIndex(t.values, index)); + }); + + if (config.tooltip_grouped) { + $$.showTooltip(selectedData, this); + $$.showXGridFocus(selectedData); + } + + if (config.tooltip_grouped && (!config.data_selection_enabled || config.data_selection_grouped)) { + return; + } + + $$.main.selectAll('.' + CLASS.shape + '-' + index) + .each(function () { + d3.select(this).classed(CLASS.EXPANDED, true); + if (config.data_selection_enabled) { + eventRect.style('cursor', config.data_selection_grouped ? 'pointer' : null); + } + if (!config.tooltip_grouped) { + $$.hideXGridFocus(); + $$.hideTooltip(); + if (!config.data_selection_grouped) { + $$.unexpandCircles(index); + $$.unexpandBars(index); + } + } + }) + .filter(function (d) { + return $$.isWithinShape(this, d); + }) + .each(function (d) { + if (config.data_selection_enabled && (config.data_selection_grouped || config.data_selection_isselectable(d))) { + eventRect.style('cursor', 'pointer'); + } + if (!config.tooltip_grouped) { + $$.showTooltip([d], this); + $$.showXGridFocus([d]); + if (config.point_focus_expand_enabled) { $$.expandCircles(index, d.id, true); } + $$.expandBars(index, d.id, true); + } + }); + }) + .on('click', function (d) { + //click event was simulated via a 'tap' touch event, cancel regular click + if (tap) { + return; + } + + click(this, d); + + }) + .on('touchstart', function(d) { + //store current X selection for comparison during touch end event + tapX = d.x; + }) + .on('touchend', function(d) { + var finalX = d.x; + + //If end is not the same as the start, event doesn't count as a tap + if (tapX !== finalX) { + return; + } + + + click(this, d); + + //indictate tap event fired to prevent click; + tap = true; + setTimeout(function() { tap = false; }, config.touch_tap_delay); + }) + + .call( + config.data_selection_draggable && $$.drag ? ( + d3.behavior.drag().origin(Object) + .on('drag', function () { $$.drag(d3.mouse(this)); }) + .on('dragstart', function () { $$.dragstart(d3.mouse(this)); }) + .on('dragend', function () { $$.dragend(); }) + ) : function () {} + ); }; c3_chart_internal_fn.redrawEventRect = function () { var $$ = this, d3 = $$.d3, config = $$.config, x, y, w, h; - // TODO: rotated not supported yet - x = 0; - y = 0; - w = $$.width; - h = $$.height; +c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) { + var $$ = this, d3 = $$.d3, config = $$.config, + tap = false, tapX, tapY; function mouseout() { $$.svg.select('.' + CLASS.eventRect).style('cursor', null); @@ -39,17 +191,33 @@ c3_chart_internal_fn.redrawEventRect = function () { $$.unexpandBars(); } - // rects for mouseover - $$.main.select('.' + CLASS.eventRects) - .style('cursor', config.zoom_enabled ? config.axis_rotated ? 'ns-resize' : 'ew-resize' : null); - - $$.eventRect - .attr('x', x) - .attr('y', y) - .attr('width', w) - .attr('height', h) - .on('mouseout', config.interaction_enabled ? function () { - if (!config) { return; } // chart is destroyed + function click(shape) { + var targetsToShow = $$.filterTargetsToShow($$.data.targets); + var mouse, closest; + if ($$.hasArcType(targetsToShow)) { return; } + + mouse = d3.mouse(shape); + closest = $$.findClosestFromTargets(targetsToShow, mouse); + if (! closest) { return; } + // select if selection enabled + if ($$.isBarType(closest.id) || $$.dist(closest, mouse) < config.point_sensitivity) { + $$.main.selectAll('.' + CLASS.shapes + $$.getTargetSelectorSuffix(closest.id)).selectAll('.' + CLASS.shape + '-' + closest.index).each(function () { + if (config.data_selection_grouped || $$.isWithinShape(this, closest)) { + $$.toggleShape(this, closest, closest.index); + $$.config.data_onclick.call($$.api, closest, this); + } + }); + } + } + + eventRectEnter.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', $$.width) + .attr('height', $$.height) + .attr('class', CLASS.eventRect) + .on('mouseout', function () { + if (!$$.config) { return; } // chart is destroyed if ($$.hasArcType()) { return; } mouseout(); } : null) @@ -105,32 +273,38 @@ c3_chart_internal_fn.redrawEventRect = function () { $$.mouseover = closest; } } - } : null) - .on('click', config.interaction_enabled ? function () { - var targetsToShow, mouse, closest, sameXData; - if ($$.hasArcType(targetsToShow)) { return; } + }) + .on('click', function () { + //click event was simulated via a 'tap' touch event, cancel regular click + if (tap) { + return; + } - targetsToShow = $$.filterTargetsToShow($$.data.targets); - mouse = d3.mouse(this); - closest = $$.findClosestFromTargets(targetsToShow, mouse); - if (! closest) { return; } - // select if selection enabled - if ($$.isBarType(closest.id) || $$.dist(closest, mouse) < config.point_sensitivity) { - if ($$.isScatterType(closest) || !config.data_selection_grouped) { - sameXData = [closest]; - } else { - sameXData = $$.filterByX(targetsToShow, closest.x); - } - sameXData.forEach(function (d) { - $$.main.selectAll('.' + CLASS.shapes + $$.getTargetSelectorSuffix(d.id)).selectAll('.' + CLASS.shape + '-' + d.index).each(function () { - if (config.data_selection_grouped || $$.isWithinShape(this, d)) { - $$.toggleShape(this, d, d.index); - config.data_onclick.call($$.api, d, this); - } - }); - }); + click(this); + }) + .on('touchstart', function(){ + var mouse = d3.mouse(this); + //store starting coordinates for distance comparision during touch end event + tapX = mouse[0]; + tapY = mouse[1]; + + }) + .on('touchend', function(){ + var mouse = d3.mouse(this), + x = mouse[0], + y = mouse[1]; + + //If end is too far from start, event doesn't count as a tap + if (Math.abs(x - tapX) > config.touch_tap_radius || Math.abs(y - tapY) > config.touch_tap_radius) { + return; } - } : null) + + click(this); + + //indictate tap event fired to prevent click; + tap = true; + setTimeout(function() { tap = false; }, config.touch_tap_delay); + }) .call( config.interaction_enabled && config.data_selection_draggable && $$.drag ? ( d3.drag()