c3
Version:
D3-based reusable chart library
291 lines (261 loc) • 8.32 kB
text/typescript
import CLASS from './class'
import { ChartInternal } from './core'
ChartInternal.prototype.initEventRect = function() {
var $$ = this,
config = $$.config
$$.main
.select('.' + CLASS.chart)
.append('g')
.attr('class', CLASS.eventRects)
.style('fill-opacity', 0)
$$.eventRect = $$.main
.select('.' + CLASS.eventRects)
.append('rect')
.attr('class', CLASS.eventRect)
// event rect handle zoom event as well
if (config.zoom_enabled && $$.zoom) {
$$.eventRect.call($$.zoom).on('dblclick.zoom', null)
if (config.zoom_initialRange) {
// WORKAROUND: Add transition to apply transform immediately when no subchart
$$.eventRect
.transition()
.duration(0)
.call($$.zoom.transform, $$.zoomTransform(config.zoom_initialRange))
}
}
}
ChartInternal.prototype.redrawEventRect = function() {
const $$ = this,
d3 = $$.d3,
config = $$.config
function mouseout() {
$$.svg.select('.' + CLASS.eventRect).style('cursor', null)
$$.hideXGridFocus()
$$.hideTooltip()
$$.unexpandCircles()
$$.unexpandBars()
}
const isHoveringDataPoint = (mouse, closest) =>
closest &&
($$.isBarType(closest.id) ||
$$.dist(closest, mouse) < config.point_sensitivity)
const withName = d => (d ? $$.addName(Object.assign({}, d)) : null)
// rects for mouseover
$$.main
.select('.' + CLASS.eventRects)
.style(
'cursor',
config.zoom_enabled
? config.axis_rotated
? 'ns-resize'
: 'ew-resize'
: null
)
$$.eventRect
.attr('x', 0)
.attr('y', 0)
.attr('width', $$.width)
.attr('height', $$.height)
.on(
'mouseout',
config.interaction_enabled
? function() {
if (!config) {
return
} // chart is destroyed
if ($$.hasArcType()) {
return
}
if ($$.mouseover) {
config.data_onmouseout.call($$.api, $$.mouseover)
$$.mouseover = undefined
}
mouseout()
}
: null
)
.on(
'mousemove',
config.interaction_enabled
? function() {
// do nothing when dragging
if ($$.dragging) {
return
}
const targetsToShow = $$.getTargetsToShow()
// do nothing if arc type
if ($$.hasArcType(targetsToShow)) {
return
}
const mouse = d3.mouse(this)
const closest = withName(
$$.findClosestFromTargets(targetsToShow, mouse)
)
const isMouseCloseToDataPoint = isHoveringDataPoint(mouse, closest)
// ensure onmouseout is always called if mousemove switch between 2 targets
if (
$$.mouseover &&
(!closest ||
closest.id !== $$.mouseover.id ||
closest.index !== $$.mouseover.index)
) {
config.data_onmouseout.call($$.api, $$.mouseover)
$$.mouseover = undefined
}
if (closest && !$$.mouseover) {
config.data_onmouseover.call($$.api, closest)
$$.mouseover = closest
}
// show cursor as pointer if we're hovering a data point close enough
$$.svg
.select('.' + CLASS.eventRect)
.style('cursor', isMouseCloseToDataPoint ? 'pointer' : null)
// if tooltip not grouped, we want to display only data from closest data point
const showSingleDataPoint =
!config.tooltip_grouped || $$.hasType('stanford', targetsToShow)
// find data to highlight
let selectedData
if (showSingleDataPoint) {
if (closest) {
selectedData = [closest]
}
} else {
let closestByX
if (closest) {
// reuse closest value
closestByX = closest
} else {
// try to find the closest value by X values from the mouse position
const mouseX = config.axis_rotated ? mouse[1] : mouse[0]
closestByX = $$.findClosestFromTargetsByX(
targetsToShow,
$$.x.invert(mouseX)
)
}
// highlight all data for this 'x' value
if (closestByX) {
selectedData = $$.filterByX(targetsToShow, closestByX.x)
}
}
// ensure we have data to show
if (!selectedData || selectedData.length === 0) {
return mouseout()
}
// inject names for each point
selectedData = selectedData.map(withName)
// show tooltip
$$.showTooltip(selectedData, this)
// expand points
if (config.point_focus_expand_enabled) {
$$.unexpandCircles()
selectedData.forEach(function(d) {
$$.expandCircles(d.index, d.id, false)
})
}
// expand bars
$$.unexpandBars()
selectedData.forEach(function(d) {
$$.expandBars(d.index, d.id, false)
})
// Show xgrid focus line
$$.showXGridFocus(selectedData)
}
: null
)
.on(
'click',
config.interaction_enabled
? function() {
const targetsToShow = $$.getTargetsToShow()
if ($$.hasArcType(targetsToShow)) {
return
}
const mouse = d3.mouse(this)
const closest = withName(
$$.findClosestFromTargets(targetsToShow, mouse)
)
if (!isHoveringDataPoint(mouse, closest)) {
return
}
// select if selection enabled
let sameXData
if (!config.data_selection_grouped || $$.isStanfordType(closest)) {
sameXData = [closest]
} else {
sameXData = $$.filterByX(targetsToShow, closest.x)
}
// toggle selected state
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)
}
})
})
// call data_onclick on the closest data point
if (closest) {
const shape = $$.main
.selectAll(
'.' + CLASS.shapes + $$.getTargetSelectorSuffix(closest.id)
)
.select('.' + CLASS.shape + '-' + closest.index)
config.data_onclick.call($$.api, closest, shape.node())
}
}
: null
)
.call(
config.interaction_enabled && config.data_selection_draggable && $$.drag
? d3
.drag()
.on('drag', function() {
$$.drag(d3.mouse(this))
})
.on('start', function() {
$$.dragstart(d3.mouse(this))
})
.on('end', function() {
$$.dragend()
})
: function() {}
)
}
ChartInternal.prototype.getMousePosition = function(data) {
var $$ = this
return [$$.x(data.x), $$.getYScale(data.id)(data.value)]
}
ChartInternal.prototype.dispatchEvent = function(type, mouse) {
var $$ = this,
selector = '.' + CLASS.eventRect,
eventRect = $$.main.select(selector).node(),
box = eventRect.getBoundingClientRect(),
x = box.left + (mouse ? mouse[0] : 0),
y = box.top + (mouse ? mouse[1] : 0),
event = document.createEvent('MouseEvents')
event.initMouseEvent(
type,
true,
true,
window,
0,
x,
y,
x,
y,
false,
false,
false,
false,
0,
null
)
eventRect.dispatchEvent(event)
}