import { Component, jsx, computeLayout } from '@antv/f-engine';
import { isFunction, find } from '@antv/util';
// view 的默认配置
const defaultStyle = {
showTitle: false,
showCrosshairs: false,
crosshairsType: 'y',
crosshairsStyle: {
stroke: 'rgba(0, 0, 0, 0.25)',
lineWidth: '2px',
},
showTooltipMarker: false,
markerBackgroundStyle: {
fill: '#CCD6EC',
opacity: 0.3,
padding: '6px',
},
tooltipMarkerStyle: {
fill: '#fff',
lineWidth: '3px',
},
background: {
radius: '4px',
fill: 'rgba(0, 0, 0, 0.65)',
padding: ['6px', '10px'],
},
titleStyle: {
fontSize: '24px',
fill: '#fff',
textAlign: 'start',
textBaseline: 'top',
},
nameStyle: {
fontSize: '24px',
fill: 'rgba(255, 255, 255, 0.65)',
textAlign: 'start',
textBaseline: 'middle',
},
valueStyle: {
fontSize: '24px',
fill: '#fff',
textAlign: 'start',
textBaseline: 'middle',
},
joinString: ': ',
showItemMarker: true,
itemMarkerStyle: {
width: '12px',
radius: '6px',
symbol: 'circle',
lineWidth: '2px',
stroke: '#fff',
},
layout: 'horizontal',
snap: false,
xTipTextStyle: {
fontSize: '24px',
fill: '#fff',
},
yTipTextStyle: {
fontSize: '24px',
fill: '#fff',
},
xTipBackground: {
radius: '4px',
fill: 'rgba(0, 0, 0, 0.65)',
padding: ['6px', '10px'],
marginLeft: '-50%',
marginTop: '6px',
},
yTipBackground: {
radius: '4px',
fill: 'rgba(0, 0, 0, 0.65)',
padding: ['6px', '10px'],
marginLeft: '-100%',
marginTop: '-50%',
},
};
function directionEnabled(mode: string, dir: string) {
if (mode === undefined) {
return true;
} else if (typeof mode === 'string') {
return mode.indexOf(dir) !== -1;
}
return false;
}
const RenderItemMarker = (props) => {
const { records, coord, context, markerBackgroundStyle } = props;
const point = coord.convertPoint({ x: 1, y: 1 });
const padding = context.px2hd(markerBackgroundStyle.padding || '6px');
const xPoints = [
...records.map((record) => record.xMin),
...records.map((record) => record.xMax),
];
const yPoints = [
...records.map((record) => record.yMin),
...records.map((record) => record.yMax),
// y 要到 coord 顶部
// point.y,
];
if (coord.transposed) {
xPoints.push(point.x);
} else {
yPoints.push(point.y);
}
const xMin = Math.min.apply(null, xPoints);
const xMax = Math.max.apply(null, xPoints);
const yMin = Math.min.apply(null, yPoints);
const yMax = Math.max.apply(null, yPoints);
const x = coord.transposed ? xMin : xMin - padding;
const y = coord.transposed ? yMin - padding : yMin;
const width = coord.transposed ? xMax - xMin : xMax - xMin + 2 * padding;
const height = coord.transposed ? yMax - yMin + 2 * padding : yMax - yMin;
return (
);
};
const RenderCrosshairs = (props) => {
const {
records,
coord,
chart,
crosshairsType,
crosshairsStyle,
xPositionType,
yPositionType,
} = props;
const { left: coordLeft, top: coordTop, right: coordRight, bottom: coordBottom, center } = coord;
const firstRecord = records[0];
const { x, y, origin, xField, coord: coordData } = firstRecord;
if (coord.isPolar) {
// 极坐标下的辅助线
const xScale = chart.getScale(xField);
const ticks = xScale.getTicks();
const tick = find(ticks, (tick) => origin[xField] === tick.tickValue);
const end = coord.convertPoint({
x: tick.value,
y: 1,
});
return (
);
}
return (
{directionEnabled(crosshairsType, 'x') ? (
) : null}
{directionEnabled(crosshairsType, 'y') ? (
) : null}
);
};
const RenderXTip = (props) => {
const { records, coord, xTip, xPositionType, xTipTextStyle, xTipBackground } = props;
const { bottom: coordBottom } = coord;
const firstRecord = records[0];
const { x, coord: coordData } = firstRecord;
const { name: xFirstText } = firstRecord;
return (
);
};
const RenderYTip = (props) => {
const { records, coord, yTip, yPositionType, yTipTextStyle, yTipBackground } = props;
const { left: coordLeft } = coord;
const firstRecord = records[0];
const { y, coord: coordData } = firstRecord;
const { value: yFirstText } = firstRecord;
return (
);
};
// tooltip 内容框
class RenderLabel extends Component {
style = {};
getMaxItemBox(node) {
let maxItemWidth = 0;
let maxItemHeight = 0;
(node.children || []).forEach((child) => {
const { layout } = child;
const { width, height } = layout;
maxItemWidth = Math.max(maxItemWidth, width);
maxItemHeight = Math.max(maxItemHeight, height);
});
return {
width: maxItemWidth,
height: maxItemHeight,
};
}
_getContainerLayout() {
const { records, coord } = this.props;
if (!records || !records.length) return;
const { width } = coord;
const node = computeLayout(this, this.render());
const { width: itemMaxWidth } = this.getMaxItemBox(node?.children[0]);
// 每行最多的个数
const lineMaxCount = Math.max(1, Math.floor(width / itemMaxWidth));
const itemCount = records.length;
// 是否需要换行
if (itemCount > lineMaxCount) {
this.style = {
width,
};
}
}
willMount(): void {
this._getContainerLayout();
}
render() {
const {
records,
background,
showItemMarker,
itemMarkerStyle,
customText,
nameStyle,
valueStyle,
joinString,
arrowWidth,
x,
coord,
itemWidth,
} = this.props;
// 显示内容
const labelView = (left: number, top: number) => {
return (
{records.map((record) => {
const { name, value } = record;
return (
{showItemMarker ? (
) : null}
{customText && isFunction(customText) ? (
customText(record)
) : (
)}
);
})}
);
};
// 计算显示位置
const { layout } = computeLayout(this, labelView(0, 0)); // 获取内容区大小
const { left: coordLeft, top: coordTop, right: coordRight } = coord;
const { width, height } = layout;
const halfWidth = width / 2;
// 让 tooltip 限制在 coord 的显示范围内
const advanceLeft = x - halfWidth;
const advanceTop = coordTop - height;
const left =
advanceLeft < coordLeft
? coordLeft
: advanceLeft > coordRight - width
? coordRight - width
: advanceLeft;
const top = advanceTop < 0 ? 0 : advanceTop;
return labelView(left, top);
}
}
export default class TooltipView extends Component {
render() {
const { props, context } = this;
const { records, coord } = props;
const firstRecord = records[0];
const { x, coord: coordData } = firstRecord;
const {
chart,
background: customBackground,
showTooltipMarker = defaultStyle.showTooltipMarker,
markerBackgroundStyle = defaultStyle.markerBackgroundStyle,
showItemMarker = defaultStyle.showItemMarker,
itemMarkerStyle: customItemMarkerStyle,
nameStyle,
valueStyle,
joinString = defaultStyle.joinString,
showCrosshairs = defaultStyle.showCrosshairs,
crosshairsStyle,
crosshairsType = defaultStyle.crosshairsType,
snap = defaultStyle.snap,
tooltipMarkerStyle = defaultStyle.tooltipMarkerStyle,
showXTip,
xPositionType,
showYTip,
yPositionType,
xTip,
yTip,
xTipTextStyle = defaultStyle.xTipTextStyle,
yTipTextStyle = defaultStyle.yTipTextStyle,
xTipBackground = defaultStyle.xTipBackground,
yTipBackground = defaultStyle.yTipBackground,
custom = false,
customText,
itemWidth,
} = props;
const itemMarkerStyle = {
...defaultStyle.itemMarkerStyle,
...customItemMarkerStyle,
};
const background = {
...defaultStyle.background,
...customBackground,
};
const arrowWidth = context.px2hd('6px');
return (
{showTooltipMarker ? (
) : null}
{/* 辅助线 */}
{showCrosshairs ? (
) : null}
{/* 辅助点 */}
{snap
? records.map((item) => {
const { x, y, color, shape } = item;
return (
);
})
: null}
{/* X 轴辅助信息 */}
{showXTip && (
)}
{/* Y 轴辅助信息 */}
{showYTip && (
)}
{/* 非自定义模式时显示的文本信息 */}
{!custom && (
)}
);
}
}