/**
* Get the size of the tooltip
*/
-function getTooltipSize(tooltip) {
+function getTooltipSize(tooltip, options) {
const ctx = tooltip._chart.ctx;
- const {body, footer, options, title} = tooltip;
+ const {body, footer, title} = tooltip;
const {bodyFont, footerFont, titleFont, boxWidth, boxHeight} = options;
const titleLineCount = title.length;
const footerLineCount = footer.length;
return {x, y};
}
-function getAlignedX(tooltip, align) {
- const options = tooltip.options;
+function getAlignedX(tooltip, align, options) {
const padding = toPadding(options.padding);
return align === 'center'
return pushOrConcat([], splitNewlines(callback));
}
+function createTooltipContext(parent, tooltip, tooltipItems) {
+ return Object.assign(Object.create(parent), {
+ tooltip,
+ tooltipItems,
+ type: 'tooltip'
+ });
+}
+
export class Tooltip extends Element {
constructor(config) {
super();
this._eventPosition = undefined;
this._size = undefined;
this._cachedAnimations = undefined;
+ this._tooltipItems = [];
this.$animations = undefined;
+ this.$context = undefined;
this.options = config.options;
this.dataPoints = undefined;
this.title = undefined;
initialize(options) {
this.options = options;
this._cachedAnimations = undefined;
+ this.$context = undefined;
}
/**
}
const chart = me._chart;
- const options = me.options;
+ const options = me.options.setContext(me.getContext());
const opts = options.enabled && chart.options.animation && options.animations;
const animations = new Animations(me._chart, opts);
if (opts._cacheable) {
return animations;
}
- getTitle(context) {
+ /**
+ * @protected
+ */
+ getContext() {
+ const me = this;
+ return me.$context ||
+ (me.$context = createTooltipContext(me._chart.getContext(), me, me._tooltipItems));
+ }
+
+ getTitle(context, options) {
const me = this;
- const opts = me.options;
- const callbacks = opts.callbacks;
+ const {callbacks} = options;
const beforeTitle = callbacks.beforeTitle.apply(me, [context]);
const title = callbacks.title.apply(me, [context]);
return lines;
}
- getBeforeBody(tooltipItems) {
- return getBeforeAfterBodyLines(this.options.callbacks.beforeBody.apply(this, [tooltipItems]));
+ getBeforeBody(tooltipItems, options) {
+ return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems]));
}
- getBody(tooltipItems) {
+ getBody(tooltipItems, options) {
const me = this;
- const callbacks = me.options.callbacks;
+ const {callbacks} = options;
const bodyItems = [];
each(tooltipItems, (context) => {
return bodyItems;
}
- getAfterBody(tooltipItems) {
- return getBeforeAfterBodyLines(this.options.callbacks.afterBody.apply(this, [tooltipItems]));
+ getAfterBody(tooltipItems, options) {
+ return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems]));
}
// Get the footer and beforeFooter and afterFooter lines
- getFooter(tooltipItems) {
+ getFooter(tooltipItems, options) {
const me = this;
- const callbacks = me.options.callbacks;
+ const {callbacks} = options;
const beforeFooter = callbacks.beforeFooter.apply(me, [tooltipItems]);
const footer = callbacks.footer.apply(me, [tooltipItems]);
/**
* @private
*/
- _createItems() {
+ _createItems(options) {
const me = this;
const active = me._active;
- const options = me.options;
const data = me._chart.data;
const labelColors = [];
const labelPointStyles = [];
update(changed) {
const me = this;
- const options = me.options;
+ const options = me.options.setContext(me.getContext());
const active = me._active;
let properties;
+ let tooltipItems = [];
if (!active.length) {
if (me.opacity !== 0) {
}
} else {
const position = positioners[options.position].call(me, active, me._eventPosition);
- const tooltipItems = me._createItems();
+ tooltipItems = me._createItems(options);
- me.title = me.getTitle(tooltipItems);
- me.beforeBody = me.getBeforeBody(tooltipItems);
- me.body = me.getBody(tooltipItems);
- me.afterBody = me.getAfterBody(tooltipItems);
- me.footer = me.getFooter(tooltipItems);
+ me.title = me.getTitle(tooltipItems, options);
+ me.beforeBody = me.getBeforeBody(tooltipItems, options);
+ me.body = me.getBody(tooltipItems, options);
+ me.afterBody = me.getAfterBody(tooltipItems, options);
+ me.footer = me.getFooter(tooltipItems, options);
- const size = me._size = getTooltipSize(me);
+ const size = me._size = getTooltipSize(me, options);
const positionAndSize = Object.assign({}, position, size);
const alignment = determineAlignment(me._chart, options, positionAndSize);
const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, me._chart);
};
}
+ me._tooltipItems = tooltipItems;
+ me.$context = undefined;
+
if (properties) {
me._resolveAnimations().update(me, properties);
}
}
}
- drawCaret(tooltipPoint, ctx, size) {
- const caretPosition = this.getCaretPosition(tooltipPoint, size);
+ drawCaret(tooltipPoint, ctx, size, options) {
+ const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
ctx.lineTo(caretPosition.x1, caretPosition.y1);
ctx.lineTo(caretPosition.x2, caretPosition.y2);
ctx.lineTo(caretPosition.x3, caretPosition.y3);
}
- getCaretPosition(tooltipPoint, size) {
- const {xAlign, yAlign, options} = this;
+ getCaretPosition(tooltipPoint, size, options) {
+ const {xAlign, yAlign} = this;
const {cornerRadius, caretSize} = options;
const {x: ptX, y: ptY} = tooltipPoint;
const {width, height} = size;
return {x1, x2, x3, y1, y2, y3};
}
- drawTitle(pt, ctx) {
+ drawTitle(pt, ctx, options) {
const me = this;
- const options = me.options;
const title = me.title;
const length = title.length;
let titleFont, titleSpacing, i;
if (length) {
const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width);
- pt.x = getAlignedX(me, options.titleAlign);
+ pt.x = getAlignedX(me, options.titleAlign, options);
ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
ctx.textBaseline = 'middle';
/**
* @private
*/
- _drawColorBox(ctx, pt, i, rtlHelper) {
+ _drawColorBox(ctx, pt, i, rtlHelper, options) {
const me = this;
- const options = me.options;
const labelColors = me.labelColors[i];
const labelPointStyle = me.labelPointStyles[i];
const {boxHeight, boxWidth, bodyFont} = options;
- const colorX = getAlignedX(me, 'left');
+ const colorX = getAlignedX(me, 'left', options);
const rtlColorX = rtlHelper.x(colorX);
const yOffSet = boxHeight < bodyFont.size ? (bodyFont.size - boxHeight) / 2 : 0;
const colorY = pt.y + yOffSet;
ctx.fillStyle = me.labelTextColors[i];
}
- drawBody(pt, ctx) {
+ drawBody(pt, ctx, options) {
const me = this;
- const {body, options} = me;
+ const {body} = me;
const {bodyFont, bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth} = options;
let bodyLineHeight = bodyFont.size;
let xLinePadding = 0;
ctx.textBaseline = 'middle';
ctx.font = toFontString(bodyFont);
- pt.x = getAlignedX(me, bodyAlignForCalculation);
+ pt.x = getAlignedX(me, bodyAlignForCalculation, options);
// Before body lines
ctx.fillStyle = options.bodyColor;
lines = bodyItem.lines;
// Draw Legend-like boxes if needed
if (displayColors && lines.length) {
- me._drawColorBox(ctx, pt, i, rtlHelper);
+ me._drawColorBox(ctx, pt, i, rtlHelper, options);
bodyLineHeight = Math.max(bodyFont.size, boxHeight);
}
pt.y -= bodySpacing; // Remove last body spacing
}
- drawFooter(pt, ctx) {
+ drawFooter(pt, ctx, options) {
const me = this;
- const options = me.options;
const footer = me.footer;
const length = footer.length;
let footerFont, i;
if (length) {
const rtlHelper = getRtlAdapter(options.rtl, me.x, me.width);
- pt.x = getAlignedX(me, options.footerAlign);
+ pt.x = getAlignedX(me, options.footerAlign, options);
pt.y += options.footerMarginTop;
ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
}
}
- drawBackground(pt, ctx, tooltipSize) {
- const {xAlign, yAlign, options} = this;
+ drawBackground(pt, ctx, tooltipSize, options) {
+ const {xAlign, yAlign} = this;
const {x, y} = pt;
const {width, height} = tooltipSize;
const radius = options.cornerRadius;
ctx.beginPath();
ctx.moveTo(x + radius, y);
if (yAlign === 'top') {
- this.drawCaret(pt, ctx, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
if (yAlign === 'center' && xAlign === 'right') {
- this.drawCaret(pt, ctx, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
if (yAlign === 'bottom') {
- this.drawCaret(pt, ctx, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
if (yAlign === 'center' && xAlign === 'left') {
- this.drawCaret(pt, ctx, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
* Update x/y animation targets when _active elements are animating too
* @private
*/
- _updateAnimationTarget() {
+ _updateAnimationTarget(options) {
const me = this;
const chart = me._chart;
- const options = me.options;
const anims = me.$animations;
const animX = anims && anims.x;
const animY = anims && anims.y;
if (!position) {
return;
}
- const size = me._size = getTooltipSize(me);
+ const size = me._size = getTooltipSize(me, options);
const positionAndSize = Object.assign({}, position, me._size);
const alignment = determineAlignment(chart, options, positionAndSize);
const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
draw(ctx) {
const me = this;
- const options = me.options;
+ const options = me.options.setContext(me.getContext());
let opacity = me.opacity;
if (!opacity) {
return;
}
- me._updateAnimationTarget();
+ me._updateAnimationTarget(options);
const tooltipSize = {
width: me.width,
ctx.globalAlpha = opacity;
// Draw Background
- me.drawBackground(pt, ctx, tooltipSize);
+ me.drawBackground(pt, ctx, tooltipSize, options);
overrideTextDirection(ctx, options.textDirection);
pt.y += padding.top;
// Titles
- me.drawTitle(pt, ctx);
+ me.drawTitle(pt, ctx, options);
// Body
- me.drawBody(pt, ctx);
+ me.drawBody(pt, ctx, options);
// Footer
- me.drawFooter(pt, ctx);
+ me.drawFooter(pt, ctx, options);
restoreTextDirection(ctx, options.textDirection);
*/
afterTooltipDraw?(chart: Chart, args: { tooltip: Model }, options: O): void;
}
+
+export interface ScriptableTooltipContext<TType extends ChartType> {
+ chart: UnionToIntersection<Chart<TType>>;
+ tooltip: UnionToIntersection<TooltipModel<TType>>;
+ tooltipItems: TooltipItem<TType>[];
+}
+
export interface TooltipOptions<TType extends ChartType> extends CoreInteractionOptions {
/**
* Are on-canvas tooltips enabled?
* @default true
*/
- enabled: boolean;
+ enabled: Scriptable<boolean, ScriptableTooltipContext<TType>>;
/**
* See custom tooltip section.
*/
/**
* The mode for positioning the tooltip
*/
- position: 'average' | 'nearest';
+ position: Scriptable<'average' | 'nearest', ScriptableTooltipContext<TType>>
/**
* Override the tooltip alignment calculations
*/
- xAlign: TooltipAlignment;
- yAlign: TooltipAlignment;
+ xAlign: Scriptable<TooltipAlignment, ScriptableTooltipContext<TType>>;
+ yAlign: Scriptable<TooltipAlignment, ScriptableTooltipContext<TType>>;
/**
* Sort tooltip items.
* Background color of the tooltip.
* @default 'rgba(0, 0, 0, 0.8)'
*/
- backgroundColor: Color;
+ backgroundColor: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* Color of title
* @default '#fff'
*/
- titleColor: Color;
+ titleColor: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* See Fonts
* @default {style: 'bold'}
*/
- titleFont: FontSpec;
+ titleFont: Scriptable<FontSpec, ScriptableTooltipContext<TType>>;
/**
* Spacing to add to top and bottom of each title line.
* @default 2
*/
- titleSpacing: number;
+ titleSpacing: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Margin to add on bottom of title section.
* @default 6
*/
- titleMarginBottom: number;
+ titleMarginBottom: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Horizontal alignment of the title text lines.
* @default 'left'
*/
- titleAlign: TextAlign;
+ titleAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;
/**
* Spacing to add to top and bottom of each tooltip item.
* @default 2
*/
- bodySpacing: number;
+ bodySpacing: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Color of body
* @default '#fff'
*/
- bodyColor: Color;
+ bodyColor: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* See Fonts.
* @default {}
*/
- bodyFont: FontSpec;
+ bodyFont: Scriptable<FontSpec, ScriptableTooltipContext<TType>>;
/**
* Horizontal alignment of the body text lines.
* @default 'left'
*/
- bodyAlign: TextAlign;
+ bodyAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;
/**
* Spacing to add to top and bottom of each footer line.
* @default 2
*/
- footerSpacing: number;
+ footerSpacing: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Margin to add before drawing the footer.
* @default 6
*/
- footerMarginTop: number;
+ footerMarginTop: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Color of footer
* @default '#fff'
*/
- footerColor: Color;
+ footerColor: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* See Fonts
* @default {style: 'bold'}
*/
- footerFont: FontSpec;
+ footerFont: Scriptable<FontSpec, ScriptableTooltipContext<TType>>;
/**
* Horizontal alignment of the footer text lines.
* @default 'left'
*/
- footerAlign: TextAlign;
+ footerAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;
/**
* Padding to add to the tooltip
* @default 6
*/
- padding: number | ChartArea;
+ padding: Scriptable<number | ChartArea, ScriptableTooltipContext<TType>>;
/**
* Extra distance to move the end of the tooltip arrow away from the tooltip point.
* @default 2
*/
- caretPadding: number;
+ caretPadding: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Size, in px, of the tooltip arrow.
* @default 5
*/
- caretSize: number;
+ caretSize: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Radius of tooltip corner curves.
* @default 6
*/
- cornerRadius: number;
+ cornerRadius: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Color to draw behind the colored boxes when multiple items are in the tooltip.
* @default '#fff'
*/
- multiKeyBackground: Color;
+ multiKeyBackground: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* If true, color boxes are shown in the tooltip.
* @default true
*/
- displayColors: boolean;
+ displayColors: Scriptable<boolean, ScriptableTooltipContext<TType>>;
/**
* Width of the color box if displayColors is true.
* @default bodyFont.size
*/
- boxWidth: number;
+ boxWidth: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Height of the color box if displayColors is true.
* @default bodyFont.size
*/
- boxHeight: number;
+ boxHeight: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* Use the corresponding point style (from dataset options) instead of color boxes, ex: star, triangle etc. (size is based on the minimum value between boxWidth and boxHeight)
* @default false
*/
- usePointStyle: boolean;
+ usePointStyle: Scriptable<boolean, ScriptableTooltipContext<TType>>;
/**
* Color of the border.
* @default 'rgba(0, 0, 0, 0)'
*/
- borderColor: Color;
+ borderColor: Scriptable<Color, ScriptableTooltipContext<TType>>;
/**
* Size of the border.
* @default 0
*/
- borderWidth: number;
+ borderWidth: Scriptable<number, ScriptableTooltipContext<TType>>;
/**
* true for rendering the legends from right to left.
*/
- rtl: boolean;
+ rtl: Scriptable<boolean, ScriptableTooltipContext<TType>>;
/**
* This will force the text direction 'rtl' or 'ltr on the canvas for rendering the tooltips, regardless of the css specified on the canvas
* @default canvas's default
*/
- textDirection: string;
+ textDirection: Scriptable<string, ScriptableTooltipContext<TType>>;
animation: AnimationSpec<TType>;
animations: AnimationsSpec<TType>;
/**
* Parsed data values for the given `dataIndex` and `datasetIndex`
*/
- parsed: ParsedDataType<TType>;
+ parsed: UnionToIntersection<ParsedDataType<TType>>;
/**
* Raw data values for the given `dataIndex` and `datasetIndex`