Forum Discussion
Add tooltip to a custom Visual
Hello everyone!
I developed a custom visual and I want to add tooltips. I have installed the package like described in the Microsoft documentation. If I want to add a mouse event with the tooltips to the dots I get this error:
Here is my code, if I forgot an import statement or something else is totally wrong, I am happy to know about 🙂
Thank you already in advance 😄
/*
* Power BI Visual CLI
*
* Copyright (c) Microsoft CorporationAll rights reserved.
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the ""Software""), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
'use strict';
import 'core-js/stable';
import 'regenerator-runtime/runtime'; /* Right here */
import '../style/visual.less';
import './../style/visual.less';
import powerbi from 'powerbi-visuals-api';
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import ISandboxExtendedColorPalette = powerbi.extensibility.ISandboxExtendedColorPalette;
import { VisualSettings } from './settings';
import { valueFormatter } from "powerbi-visuals-utils-formattingutils";
import { createTooltipServiceWrapper } from "powerbi-visuals-utils-tooltiputils";
import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import ITooltipService = powerbi.extensibility.ITooltipService;
import * as d3 from 'd3';
import { DateTimeUnit } from 'powerbi-visuals-utils-formattingutils/lib/src/formattingService/iFormattingService';
// Build Interface for Data Structure
interface ILineChartRow {
milestone: string,
report_date: Date,
vor_ende: Date,
tooltip: VisualTooltipDataItem[];
}
// Interface for diagonale
interface IDiagonal {
begin: Date
end: Date
}
export class Visual implements IVisual {
private target: HTMLElement;
private settings: VisualSettings;
private container: d3.Selection<HTMLDivElement, any, HTMLDivElement, any>; // private container: d3.Selection<any, any, any, any>;???
private svg: d3.Selection<SVGElement, any, any, any>
constructor(options: VisualConstructorOptions) {
console.log('Visual constructor', options);
this.target = options.element;
/** Create the chart container when the visual loads */
this.container = d3.select(this.target)
.append('div')
.attr('id', 'my_dataviz');
createTooltipServiceWrapper(
options.host.tooltipService,
options.element
)
}
public update(options: VisualUpdateOptions) {
console.log('Visual update', options);
this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
/** Clear down existing plot */
this.container.selectAll('*').remove();
/** Test 1: Data view has both fields added */
let dataViews = options.dataViews;
console.log('Test 1: Valid data view...');
if (!dataViews
|| !dataViews[0]
|| !dataViews[0].table
|| !dataViews[0].table.rows
|| !dataViews[0].table.columns
|| !dataViews[0].metadata
) {
console.log('Test 1 FAILED. No data to draw table.');
return;
}
let table = dataViews[0].table
//
// Map Data in JSON Structure
let data: ILineChartRow[] = table.rows.map(
(cat, idx) => (
{
milestone: <string>table.rows[idx][0],
report_date: <Date>table.rows[idx][1],
vor_ende: <Date>table.rows[idx][2],
tooltip: [{
displayName: "Milestone",
value: `${table.rows[idx][0]}`
}]
}
)
);
// Format Dates
let parseDate = d3.timeParse("%d.%m.%Y")
data.forEach(function(d: any){
d.report_date = parseDate(d.report_date),
d.vor_ende = parseDate(d.vor_ende)
})
// Create groups for every milestone
let sumstat = d3.nest<ILineChartRow>()
.key(d => d.milestone)
.entries(data)
// Check the result
console.log(sumstat)
// Add Project Begin and and from formatting pane
let diagonale: IDiagonal = {
begin: parseDate(this.settings.dataPoint.projektStart),
end: parseDate(this.settings.dataPoint.projektEnd)
}
// Set Dimensions
let margin = {top: 10, right: 30, bottom: 30, left: 60},
width = options.viewport.width - margin.left - margin.right,
height = options.viewport.height - margin.top - margin.bottom;
// Append the svg object to the body of the page
var svg = this.container
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform',
'translate(' + margin.left + ',' + margin.top + ')');
// Set Minimum and Maximum Values for the axes
/* let minDate_report_date = d3.min(data, function(d){ return d.report_date})
let maxDate_report_date = d3.max(data, function(d){ return d.report_date}) */
// Set Minimum and Maximum for the report date
let minDate_report_date = parseDate(this.settings.dataPoint.projektStart)
let maxDate_report_date = parseDate(this.settings.dataPoint.projektEnd)
// Define x position
let x = d3.scaleTime()
.domain([minDate_report_date,maxDate_report_date])
.range([0,width]);
// Define y0 position
let y0 = d3.scaleTime()
.domain([minDate_report_date,maxDate_report_date])
.range([height,0])
// Define y1 position
let y1 = d3.scaleTime()
.domain([minDate_report_date,maxDate_report_date])
.range([height,0])
// Add diagonal Line first so it is behind the dots
/* svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "darkgrey")
.attr("stroke-width", 1.5)
.attr("d", d3.line<ILineChartRow>()
.x(function(d) {return x(d.report_date)})
.y(function(d) { return y1(d.report_date)})) */
svg.append("line")
.attr("y1", y1(minDate_report_date))
.attr("y2", y1(maxDate_report_date))
.attr("x1", x(minDate_report_date))
.attr("x2", x(maxDate_report_date))
.style("stroke", "darkgrey")
.attr("stroke-width", 1.5)
// Add Milestone lines
svg.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", this.settings.dataPoint.fillLine)
.attr("stroke-width", this.settings.dataPoint.thickness)
.attr("d", function(d){
return d3.line<ILineChartRow>()
.x(function(d) { return x(d.report_date); })
.y(function(d) { return y0(d.vor_ende); })
(d.values)
})
// Add Milestone Dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.report_date); } )
.attr("cy", function (d) { return y0(d.vor_ende); } )
.attr("r", this.settings.dataPoint.thickness * 1.15)
.style("fill", this.settings.dataPoint.fillLine)
// HERE OCCURS THE ERROR
.on("mouseover", (d) => {
let mouse = d3.mouse(this.svg.node())
let x = mouse[0]
let y = mouse[]
})
// Add rectangle to hide something
svg.append("rect")
.attr("x", 0 - margin.left)
.attr("y", 0)
.attr("width", margin.left)
.attr("height", options.viewport.height)
.attr("fill", "white")
// Add X axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y0 axis
svg.append("g")
.call(d3.axisLeft(y0))
// Add Y1 axis
svg.append("g")
.attr("transform", "translate( " + width + ", 0 )")
.call(d3.axisRight(y1) // Remove the axis ticks
.ticks(0))
.call(g => g.select(".domain")
.remove()) // Remove the axis itself
// Loop iterates through sumstat object and places for every first Item of "Vorr_Ende" a textfield
// As text is the i version of the key of the sumstat object used
console.log("this is my sumstat *-*: ", sumstat)
// Labels placed next to y axis
/* for ( let i = 0; i < Object.keys(sumstat).length; i++) {
svg.append("g").selectAll("text")
.data(sumstat)
.enter()
.append("text")
.attr("x", 10)
.attr("y", y1(sumstat[i].values[0].vor_ende)-2)
.attr("font-size", 10)
.style("fill", "darkgrey")
.text(sumstat[i].key)
}
*/
// Labels placed at the end of the lines
for ( let i = 0; i < Object.keys(sumstat).length; i++) {
svg.append("g").selectAll("text")
.data(sumstat)
.enter()
.append("text")
.attr("x", x(sumstat[i].values[Object.keys(sumstat[i].values).length - 1].report_date) + 8)
.attr("y", y1(sumstat[i].values[Object.keys(sumstat[i].values).length - 1].vor_ende) + 3)
.attr("font-size", this.settings.dataPoint.radius) // gets infos from the settings.ts file
.style("fill", this.settings.dataPoint.fillLabel)
.text(sumstat[i].key)
}
}
private static parseSettings(dataView: DataView): VisualSettings {
return VisualSettings.parse(dataView) as VisualSettings;
}
/**
* This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
* objects and properties you want to expose to the users in the property pane.
*
*/
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
}
}- Anonymous4 years ago
Hi Anonymous
Here is an example code to add tooltips to custom visual. Try it and compare it with your codes.
For reference: Sample code
... private renderTooltip(selection: Selection<Line | Task | MilestonePath>): void { this.tooltipServiceWrapper.addTooltip( selection, (tooltipEvent: TooltipEventArgs<TooltipEnabledDataPoint>) => { return tooltipEvent.data.tooltipInfo; }); } ...Here is a post with similar requirement like yours.
For reference:
Custom Visual Tooltip Data Option
Best Regards,
Rico ZhouIf this post helps, then please consider Accept it as the solution to help the other members find it more quickly.
1 Reply
- AnonymousNot applicable
Hi Anonymous
Here is an example code to add tooltips to custom visual. Try it and compare it with your codes.
For reference: Sample code
... private renderTooltip(selection: Selection<Line | Task | MilestonePath>): void { this.tooltipServiceWrapper.addTooltip( selection, (tooltipEvent: TooltipEventArgs<TooltipEnabledDataPoint>) => { return tooltipEvent.data.tooltipInfo; }); } ...Here is a post with similar requirement like yours.
For reference:
Custom Visual Tooltip Data Option
Best Regards,
Rico ZhouIf this post helps, then please consider Accept it as the solution to help the other members find it more quickly.