in src/visual.ts [1688:1933]
private drawTooltips(data: ChartData): void {
let xScale: LinearScale = <LinearScale>data.xScale,
yScales: LinearScale[] = <LinearScale[]>data.yScales,
node: ClassAndSelector = Visual.Tooltip,
nodeParent: ClassAndSelector = Visual.TooltipContainer,
width: number = this.data.settings.popup.width,
height: number = this.data.settings.popup.height,
marginTop: number = Visual.DefaultTooltipSettings.marginTop,
showTimeDisplayProperty: string = this.data.settings.popup.showTime ? "inherit" : "none",
showTitleDisplayProperty: string = this.data.settings.popup.showTitle ? "inherit" : "none";
let rootSelection: Selection<any> = this.rootSelection;
let line: Line = d3Line<PointXY>()
.x((d: PointXY) => d.x)
.y((d: PointXY) => {
return d.y;
});
let tooltipShiftY = (y: number, groupIndex: number): number => {
return this.isHigherMiddle(y, groupIndex) ? (-1 * marginTop + Visual.topShift) : this.size.height + marginTop;
};
let tooltipRoot: Selection<any> = rootSelection.select(nodeParent.selectorName).selectAll(node.selectorName)
.data(d => {
return filter(d.data, (value: DataPoint) => this.isPopupShow(value));
});
let tooltipRootMerged = tooltipRoot
.enter()
.append("g")
.merge(tooltipRoot);
tooltipRootMerged.classed(node.className, true);
tooltipRootMerged
.attr("transform", (d: DataPoint) => {
let x: number = xScale(d.x) - width / 2;
let y: number = tooltipShiftY(d.y, d.groupIndex);
d.popupInfo.offsetX = Math.min(this.viewport.width - this.margin.right - width, Math.max(-this.margin.left, x)) - x;
return manipulation.translate(x + d.popupInfo.offsetX, y);
});
let tooltipRect: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipRect.selectorName).data(d => [d]);
let tooltipRectMerged = tooltipRect
.enter()
.append("path")
.merge(tooltipRect);
tooltipRectMerged
.classed(Visual.TooltipRect.className, true)
.attr("display", (d: DataPoint) => d.popupInfo ? "inherit" : "none")
.style("fill", this.data.settings.popup.color)
.style("stroke", this.data.settings.popup.stroke)
.attr("d", (d: DataPoint) => {
const firstPoint: PointXY = {
"x": -2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,
};
const points: PointXY[] = [
firstPoint,
{
"x": -2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : height,
},
{
"x": width - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : height,
},
{
"x": width - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,
},
firstPoint,
];
return line(points);
});
let tooltipTriangle: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipTriangle.selectorName).data(d => [d]);
let tooltipTriangleMerged = tooltipTriangle
.enter()
.append("path")
.merge(tooltipTriangle);
tooltipTriangleMerged.classed(Visual.TooltipTriangle.className, true);
tooltipTriangleMerged
.style("fill", this.data.settings.popup.color)
.style("stroke", this.data.settings.popup.stroke)
.attr("d", (d: DataPoint) => {
let path = [
{
"x": width / 2 - 5 - d.popupInfo.offsetX,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,
},
{
"x": width / 2 - d.popupInfo.offsetX,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop - 5)) : -5,
},
{
"x": width / 2 + 5 - d.popupInfo.offsetX,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,
},
];
return line(<DataPoint[]>path);
})
.style("stroke-width", "1px");
let tooltipLine: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipLine.selectorName).data(d => [d]);
let tooltipLineMerged = tooltipLine.enter().append("path").merge(tooltipLine);
tooltipLineMerged.classed(Visual.TooltipLine.className, true);
tooltipLineMerged
.style("fill", this.data.settings.popup.color)
.style("stroke", this.data.settings.popup.stroke || this.data.settings.popup.color)
.style("stroke-width", "1px")
.attr("d", (d: DataPoint) => {
let path = [
{
"x": width / 2 - d.popupInfo.offsetX,
"y": this.isHigherMiddle(d.y, d.groupIndex) ?
yScales[d.groupIndex](d.y) + tooltipShiftY(d.y, d.groupIndex) - d.eventSize :
yScales[d.groupIndex](d.y) - tooltipShiftY(d.y, d.groupIndex) + d.eventSize,
},
{
"x": width / 2 - d.popupInfo.offsetX,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0, // end
}];
return line(<DataPoint[]>path);
});
let timeRect: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipTimeRect.selectorName).data(d => [d]);
let timeRectMerged = timeRect.enter().append("path").merge(timeRect);
timeRectMerged.classed(Visual.TooltipTimeRect.className, true);
timeRectMerged
.style("fill", this.data.settings.popup.timeFill)
.style("stroke", this.data.settings.popup.stroke)
.style("display", showTimeDisplayProperty)
.attr("d", (d: DataPoint) => {
let path = [
{
"x": width - this.data.widthOfTooltipValueLabel - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : 0,
},
{
"x": width - this.data.widthOfTooltipValueLabel - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex)
? (-1 * (marginTop + height - Visual.DefaultTooltipSettings.timeHeight))
: Visual.DefaultTooltipSettings.timeHeight,
},
{
"x": width - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex)
? (-1 * (marginTop + height - Visual.DefaultTooltipSettings.timeHeight))
: Visual.DefaultTooltipSettings.timeHeight,
},
{
"x": width - 2,
"y": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : 0,
}
];
return line(<DataPoint[]>path);
});
let time: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipTime.selectorName).data(d => [d]);
let timeMerged = time.enter().append("text").merge(time);
timeMerged.classed(Visual.TooltipTime.className, true);
const timeFontStyles = Visual.CONVERT_TEXT_PROPERTIES_TO_STYLE(Visual.getPopupValueTextProperties());
Visual.APPLY_TEXT_FONT_STYLES(timeMerged, timeFontStyles);
timeMerged
.style("display", showTimeDisplayProperty)
.style("fill", this.data.settings.popup.timeColor)
.attr("x", (d: DataPoint) => width - this.data.widthOfTooltipValueLabel)
.attr("y", (d: DataPoint) => this.isHigherMiddle(d.y, d.groupIndex)
? (-1 * (marginTop + height - Visual.DefaultTooltipSettings.timeHeight + 3))
: Visual.DefaultTooltipSettings.timeHeight - 3)
.text((d: DataPoint) => textMeasurementService.getTailoredTextOrDefault(Visual.getPopupValueTextProperties(d.popupInfo.value.toString()), this.data.widthOfTooltipValueLabel));
let title: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipTitle.selectorName).data(d => [d]);
let titleMerged = title.enter().append("text").merge(title);
titleMerged
.classed(Visual.TooltipTitle.className, true);
const titleFontStyles = Visual.CONVERT_TEXT_PROPERTIES_TO_STYLE(Visual.getPopupTitleTextProperties());
Visual.APPLY_TEXT_FONT_STYLES(titleMerged, titleFontStyles);
titleMerged
.style("display", showTitleDisplayProperty)
.style("fill", this.data.settings.popup.fontColor)
.attr("x", (d: DataPoint) => Visual.PopupTextPadding)
.attr("y", (d: DataPoint) =>
(this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height - 12)) : 12) + Visual.PopupTextPadding)
.text((d: DataPoint) => {
if (!d.popupInfo) {
return "";
}
let maxWidth = width - Visual.PopupTextPadding * 2 -
(this.data.settings.popup.showTime ? (this.data.widthOfTooltipValueLabel - Visual.PopupTextPadding) : 0) - 10;
return textMeasurementService.getTailoredTextOrDefault(Visual.getPopupTitleTextProperties(d.popupInfo.title), maxWidth);
});
let getDescriptionDimenstions = (d: DataPoint): ElementDimensions => {
let shiftY: number = Visual.PopupTextPadding + this.data.settings.popup.fontSize;
let descriptionYOffset: number = shiftY + Visual.DefaultTooltipSettings.timeHeight;
if (d.popupInfo) {
shiftY = ((showTitleDisplayProperty && d.popupInfo.title) || (showTimeDisplayProperty && d.popupInfo.value)) ? descriptionYOffset : shiftY;
}
return {
y: this.isHigherMiddle(d.y, d.groupIndex)
? (-1 * (marginTop + height - shiftY))
: shiftY,
x: Visual.PopupTextPadding,
width: width - Visual.PopupTextPadding * 2,
height: height - shiftY,
};
};
let description: Selection<any> = tooltipRootMerged.selectAll(Visual.TooltipDescription.selectorName).data(d => [d]);
let descriptionMerged = description.enter().append("text").merge(description);
descriptionMerged.classed(Visual.TooltipDescription.className, true);
const descriptionFontStyles = Visual.CONVERT_TEXT_PROPERTIES_TO_STYLE(Visual.getPopupDescriptionTextProperties(null, this.data.settings.popup.fontSize));
Visual.APPLY_TEXT_FONT_STYLES(descriptionMerged, descriptionFontStyles);
descriptionMerged
.style("fill", this.data.settings.popup.fontColor)
.text((d: DataPoint) => d.popupInfo && d.popupInfo.description)
.each(function (series: Series) {
let node = <SVGTextElement>this;
const allowedWidth = width - 2 - Visual.PopupTextPadding * 2;
const allowedHeight = height - Visual.DefaultTooltipSettings.timeHeight - Visual.PopupTextPadding * 2;
textMeasurementService.wordBreak(node, allowedWidth, allowedHeight);
})
.attr("transform", (d: DataPoint) => {
let descriptionDimenstions: ElementDimensions = getDescriptionDimenstions(d);
return manipulation.translate(0, descriptionDimenstions.y);
});
descriptionMerged.selectAll("tspan").attr("x", Visual.PopupTextPadding);
tooltipRect
.exit()
.remove();
tooltipRoot
.exit()
.remove();
}