@gitlab/ui
Version:
GitLab UI Components
372 lines (357 loc) • 11.1 kB
JavaScript
import * as echarts from 'echarts';
import { uid, debounceByAnimationFrame } from '../../../../utils/utils';
import GlPopover from '../../../base/popover/popover';
import { popoverPlacements } from '../../../../utils/constants';
import { TOOLTIP_LEFT_OFFSET, TOOLTIP_TOP_OFFSET } from '../../../../utils/charts/constants';
import { getTooltipAxisConfig, getTooltipTitle, getTooltipContent } from '../../../../utils/charts/config';
import TooltipDefaultFormat from './tooltip_default_format/tooltip_default_format';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
//
var script = {
name: 'GlChartTooltip',
components: {
GlPopover,
TooltipDefaultFormat
},
inheritAttrs: false,
props: {
chart: {
type: Object,
required: true,
validator(chart) {
return Object.is(chart, echarts.getInstanceByDom(chart.getDom()));
}
},
id: {
type: String,
required: false,
default: () => uid()
},
/**
* Position of the popover respective to the chart.
* Sets the `top` style property.
*/
top: {
type: String,
required: false,
default: null
},
/**
* Position of the popover respective to the chart.
* Sets the `bottom` style property.
*/
bottom: {
type: String,
required: false,
default: null
},
/**
* Position of the popover respective to the chart.
* Sets the `left` style property.
*/
left: {
type: String,
required: false,
default: null
},
/**
* Position of the popover respective to the chart.
* Sets the `right` style property.
*/
right: {
type: String,
required: false,
default: null
},
/**
* Set to `true` to show, set to `false` to not show.
* Set to `null` to show only when the mouse is in the chart.
*/
show: {
type: Boolean,
required: false,
default: null
},
/**
* Popover placement
*/
placement: {
type: String,
required: false,
default: popoverPlacements.right
},
/**
* Distance between the popover and the pointer when
* no position is defined
*/
xOffset: {
type: Number,
required: false,
default: TOOLTIP_LEFT_OFFSET,
validator(value) {
// popover target must have a size of at least 1
return value >= 1;
}
},
/**
* Distance between the popover and the pointer when
* no position is defined
*/
yOffset: {
type: Number,
required: false,
default: TOOLTIP_TOP_OFFSET,
validator(value) {
// popover target must have a size of at least 1
return value >= 1;
}
},
/**
* Set to true to use the default tooltip formatter.
*/
useDefaultTooltipFormatter: {
type: Boolean,
required: false,
default: false
},
/**
* Set to true to enable click-to-pin functionality.
* When enabled, clicking on a chart point will keep the tooltip open, so the user can interact with the tooltip's content.
* The tooltip will unpin when clicking again on the chart and not on the tooltip.
*/
clickToPin: {
type: Boolean,
required: false,
default: false
},
/**
* Specifies which axis contains the dimensional data used in the tooltip.
* When `xAxis`, x-axis value becomes the tooltip's title and
* y-axis values become the tooltip's values.
* When `yAxis`, roles are reversed.
*/
dimensionAxis: {
type: String,
required: false,
default: 'xAxis',
validator(value) {
return value === 'xAxis' || value === 'yAxis';
}
}
},
data() {
return {
pointerCoords: null,
isPointerInChart: false,
pinnedPosition: null,
debouncedMouseHandler: debounceByAnimationFrame(this.mouseHandler),
title: null,
content: {},
params: null
};
},
computed: {
targetId() {
// if multiple tooltips are used in a chart component,
// `this.id` can be used to uniquely identify them
return `${this.chart.getDom().getAttribute('_echarts_instance_')}-tooltip-${this.id}`;
},
targetStyle() {
// the target is a rectangular space between cursor and popover
return {
marginTop: `${-this.yOffset}px`,
height: `${this.yOffset * 2}px`,
marginLeft: `${-this.xOffset}px`,
width: `${this.xOffset * 2}px`,
pointerEvents: this.isPinned ? 'auto' : 'none',
...this.tooltipPosition
};
},
fixedPosition() {
const {
top,
left,
bottom,
right
} = this;
if (top || left || bottom || right) {
return {
top,
left,
bottom,
right
};
}
return null;
},
tooltipPosition() {
if (this.fixedPosition) {
return this.fixedPosition;
}
if (this.pinnedPosition) {
return this.pinnedPosition;
}
return this.pointerPosition;
},
shouldShowPopover() {
if (this.show !== null) {
return this.show;
}
return this.isPinned || this.isPointerInChart;
},
isPinned() {
return this.pinnedPosition !== null;
},
pointerPosition() {
if (!this.pointerCoords) {
return null;
}
return {
left: `${this.pointerCoords.x}px`,
top: `${this.pointerCoords.y}px`
};
}
},
created() {
this.chart.getZr().on('mousemove', this.debouncedMouseHandler);
this.chart.getZr().on('mouseout', this.debouncedMouseHandler);
if (this.clickToPin) {
document.addEventListener('keydown', this.keyDownHandler);
this.chart.getZr().on('click', this.clickHandler);
}
if (this.useDefaultTooltipFormatter) {
this.chart.setOption({
[this.dimensionAxis]: {
axisPointer: {
show: true,
label: {
formatter: params => {
var _options$this$dimensi, _options$this$dimensi2, _options$valueAxis, _options$valueAxis$;
const options = this.chart.getOption();
const {
dimensionIndex,
metricIndex,
valueAxis
} = getTooltipAxisConfig(this.dimensionAxis);
const titleAxisName = (_options$this$dimensi = options[this.dimensionAxis]) === null || _options$this$dimensi === void 0 ? void 0 : (_options$this$dimensi2 = _options$this$dimensi[0]) === null || _options$this$dimensi2 === void 0 ? void 0 : _options$this$dimensi2.name;
const valueAxisName = (_options$valueAxis = options[valueAxis]) === null || _options$valueAxis === void 0 ? void 0 : (_options$valueAxis$ = _options$valueAxis[0]) === null || _options$valueAxis$ === void 0 ? void 0 : _options$valueAxis$.name;
this.title = getTooltipTitle(params, titleAxisName, dimensionIndex);
this.content = getTooltipContent(params, valueAxisName, metricIndex);
this.params = params;
}
}
}
}
});
}
},
beforeDestroy() {
this.chart.getZr().off('mousemove', this.debouncedMouseHandler);
this.chart.getZr().off('mouseout', this.debouncedMouseHandler);
if (this.clickToPin) {
this.chart.getZr().off('click', this.clickHandler);
document.removeEventListener('keydown', this.keyDownHandler);
}
},
methods: {
getEventCoordsWithinChart(_ref) {
let {
event
} = _ref;
const {
zrX,
zrY
} = event;
const x = Math.round(zrX);
const y = Math.round(zrY);
if (!Number.isFinite(x) || !Number.isFinite(y)) {
return null;
}
if (!this.chart.containPixel('grid', [x, y])) {
return null;
}
return {
x,
y
};
},
mouseHandler(event) {
const coords = this.getEventCoordsWithinChart(event);
if (!coords) {
this.isPointerInChart = false;
return;
}
this.pointerCoords = coords;
this.isPointerInChart = true;
},
clickHandler(event) {
if (!this.getEventCoordsWithinChart(event)) {
return;
}
if (!this.isPinned) {
this.pinTooltip();
} else {
this.unpinTooltip(event.event);
}
},
pinTooltip() {
this.pinnedPosition = this.pointerPosition;
// prevents any axis pointers from being moved to a new position, they should also be pinned
this.chart.setOption({
tooltip: {
triggerOn: 'none'
}
});
},
unpinTooltip() {
this.pinnedPosition = null;
// restores the axis pointers to be unpinned and be triggered by mousemove
this.chart.setOption({
tooltip: {
triggerOn: 'mousemove',
show: false
}
});
// shows the tooltip and axis pointer at the position of the click
this.chart.dispatchAction({
type: 'showTip',
x: this.pointerCoords.x,
y: this.pointerCoords.y
});
},
keyDownHandler(event) {
if (event.key === 'Escape' && this.isPinned) {
this.unpinTooltip();
}
}
}
};
/* script */
const __vue_script__ = script;
/* template */
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.chart)?_c('div',[_c('div',{staticClass:"gl-chart-tooltip",style:(_vm.targetStyle),attrs:{"id":_vm.targetId}}),_vm._v(" "),_c('gl-popover',_vm._b({attrs:{"show":_vm.shouldShowPopover,"target":_vm.targetId,"container":_vm.targetId,"placement":_vm.placement,"triggers":""},scopedSlots:_vm._u([{key:"title",fn:function(){return [_vm._t("title",function(){return [_vm._v(_vm._s(_vm.title))]},null,{ title: _vm.title, params: _vm.params })]},proxy:true}],null,true)},'gl-popover',_vm.$attrs,false),[_vm._v(" "),_vm._t("default",function(){return [_c('tooltip-default-format',{attrs:{"tooltip-content":_vm.content},scopedSlots:_vm._u([(_vm.$scopedSlots['tooltip-value'])?{key:"tooltip-value",fn:function(scope){return [_vm._t("tooltip-value",null,null,scope)]}}:null],null,true)})]},null,{ content: _vm.content, params: _vm.params })],2)],1):_vm._e()};
var __vue_staticRenderFns__ = [];
/* style */
const __vue_inject_styles__ = undefined;
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = /*#__PURE__*/__vue_normalize__(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
undefined,
undefined,
undefined
);
export { __vue_component__ as default };