custom visual development
9 TopicsNeed help for defining properties in format visual pane.
Hi, I've created a custom 3D Pie Chart using react + typescript. I need to define property to i) change color of each slice based on adoptive data ii) font size & style of dataLabels in format visual panel under Vizualizations pane. Please suggest a approach to take for same. I'm attaching my source code snippets as well. PieChart.tsx File: import * as React from "react"; import * as Highcharts from "highcharts"; import HighchartsReact from "highcharts-react-official"; import HC_more from "highcharts/highcharts-3d"; import powerbi from "powerbi-visuals-api"; import DataView = powerbi.DataView; import IVisual = powerbi.extensibility.visual.IVisual; import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions; import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; // Initialize the 3D module HC_more(Highcharts); interface Props { dataView: DataView | undefined; chartData?: { name: string; y: number }[]; // Make chartData optional } interface State { chartOptions: Highcharts.Options; } export class CustomPieChart extends React.Component<Props, State> implements IVisual { private target: HTMLElement; private updateCount: number; constructor(props: Props) { super(props); this.updateCount = 0; const defaultData = [ { name: "No Data", y: 100 } ]; this.state = { chartOptions: this.getChartOptions(props.chartData || defaultData) }; } public update(options: VisualUpdateOptions) { if (options.dataViews && options.dataViews[0]) { const dataView = options.dataViews[0]; const extractedData = this.extractDataFromDataView(dataView); const userDefinedColors = extractedData.map((_, index) => { const dataPointObject = dataView.metadata.objects?.dataPoint; const fillProperty = dataPointObject && dataPointObject[`fill_$[index]`]; return typeof fillProperty === 'object' && 'solid' in fillProperty ? fillProperty.solid.color : undefined; // return dataPointObject && dataPointObject[`fill_${index}`]?.solid?.color; }); const dynamicColors = extractedData.map((_, index) => userDefinedColors[index] || this.state.chartOptions.colors?.[index] ); const chartOptions = this.getChartOptions(extractedData); chartOptions.colors = dynamicColors; // Apply updated colors this.setState({ chartOptions: this.getChartOptions(extractedData) }); } } componentDidMount() { if (this.props.dataView) { const extractedData = this.extractDataFromDataView(this.props.dataView); this.setState({ chartOptions: this.getChartOptions(extractedData) }); } } componentDidUpdate(prevProps: Props) { if (prevProps.chartData !== this.props.chartData) { this.setState({ chartOptions: this.getChartOptions(this.props.chartData) }); } } extractDataFromDataView(dataView: DataView): any[] { if (!dataView?.categorical?.categories?.[0]?.values || !dataView?.categorical?.values?.[0]?.values) { return [{ name: "No Data", y: 100 }]; } try { const categorical = dataView.categorical; const categories = categorical.categories[0].values; const values = categorical.values[0].values; return categories.map((category, index) => ({ name: String(category), y: Number(values[index]) || 0 })); } catch (error) { console.error("Error extracting data:", error); return [{ name: "Error", y: 100 }]; } } getChartOptions(data: any[]): Highcharts.Options { return { colors: ['#AEDFF7', '#73C6F3', '#3498DB', '#2E86C1', '#1B4F72', '#154360'], // colors: ['#00a6e9', '#9bdafc'], chart: { type: 'pie', options3d: { enabled: true, alpha: 50, beta: 0 }, height: 400, animation: true, backgroundColor: 'transparent' }, title: { text: '3D Pie Chart', align: 'left', style: { fontSize: '14px', color: 'red' } }, accessibility: { point: { valueSuffix: '%' } }, tooltip: { pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>' }, plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', depth: 55, dataLabels: { enabled: true, useHTML: true, formatter: function () { const point = this.point; const color = point.color || 'black'; // Default to black if no color is defined return `<span style="color:${color}; font-size:11px;">${point.name}: ${point.percentage.toFixed(1)}%</span>`; }, style: { fontSize: '11px', textOutline: 'none' // Prevent text from having an outline } }, showInLegend: true } }, legend: { enabled: true, itemStyle: { fontSize: '11px' } }, series: [{ type: 'pie', name: 'Share', data: data }] as any, credits: { enabled: false } }; } render() { return ( <div style={{ minHeight: "400px", width: "100%", display: "flex", justifyContent: "center", alignItems: "center" }}> <HighchartsReact highcharts={Highcharts} options={this.state.chartOptions} containerProps={{ style: { height: "100%", width: "100%" } }} /> </div> ); } } export default CustomPieChart; Visual.ts File: import * as React from "react"; import * as ReactDOM from "react-dom"; import DataView = powerbi.DataView; import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions; import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; import IVisual = powerbi.extensibility.visual.IVisual; import ITooltipService = powerbi.extensibility.ITooltipService; import IVisualHost = powerbi.extensibility.visual.IVisualHost; import { VisualFormattingSettingsModel } from "./settings"; import customPieChart from "./PieChart"; import powerbi from "powerbi-visuals-api"; import "./../style/visual.less"; export class Visual implements IVisual { private chartData: Array<{ name: string; y: number }> = [{ name: "No Data", y: 100 }]; private target: HTMLElement; private reactRoot: React.ReactElement; private dataView: DataView | undefined; private host: IVisualHost; private visualSettings: VisualFormattingSettingsModel; private tooltipService: ITooltipService; constructor(options: VisualConstructorOptions) { this.target = options.element; this.host = options.host; this.chartData = [] this.tooltipService = this.host.tooltipService; // Initialize with empty chartData this.reactRoot = React.createElement(customPieChart, { dataView: undefined, chartData: [{ name: "No Data", y: 100 }] }); ReactDOM.render(this.reactRoot, this.target); } public update(options: VisualUpdateOptions) { // Update the dataView with the new data this.dataView = options.dataViews?.[0]; let extractedData = [{ name: "No Data", y: 100 }]; // Default data if (this.dataView && this.dataView.categorical) { const categorical = this.dataView.categorical; const categories = categorical.categories?.[0]?.values; const values = categorical.values?.[0]?.values; if (categories && values) { extractedData = categories.map((category, index) => ({ name: String(category), y: Number(values[index]) || 0 })); } } // Re-render the component with the updated or default data this.reactRoot = React.createElement(customPieChart, { dataView: this.dataView, chartData: extractedData }); ReactDOM.render(this.reactRoot, this.target); } public enumerateObjectInstances(options: powerbi.EnumerateVisualObjectInstancesOptions): powerbi.VisualObjectInstanceEnumeration { const enumeration: powerbi.VisualObjectInstance[] = []; if (options.objectName === "dataPoint") { this.chartData?.forEach((dataPoint: any, index: string | number) => { enumeration.push({ objectName: options.objectName, displayName: dataPoint.name, selector: { id: dataPoint.name }, properties: { fill: { solid: { color: this.chartData?.[index]?.color } } }, }); }); } return enumeration; } } Capabilities.json File: { "dataRoles": [ { "displayName": "Category", "name": "category", "kind": "Grouping" }, { "displayName": "Values", "name": "values", "kind": "Measure" } ], "dataViewMappings": [ { "categorical": { "categories": { "for": { "in": "category" }, "dataReductionAlgorithm": { "top": {} } }, "values": { "for": { "in": "values" } } } } ], "objects": { "general": { "displayName": "General", "properties": { "show": { "displayName": "Show", "type": { "bool": true } } } }, "visuals": { "displayName": "Visuals", "properties": { "fill": { "displayName": "Fill", "type": { "fill": { "solid": { "color": true } } } } } }, "dataPoint":{ "displayName": "Data Point Colors", "properties": { "fill": { "type": {"fill": {"solid": {"color": true } } } } } }, "colorSettings": { "displayName": "Colors", "properties": { "fill": { "displayName": "Fill Color", "type": { "fill": { "solid": { "color": true } } } } } }, "sizeSettings": { "displayName": "Size", "properties": { "size": { "displayName": "Size", "type": { "numeric": true } } } } }, "supportsHighlight": true, "supportsMultiVisualSelection": true, "tooltips": { "roles": ["category", "values"] }, "privileges": [] }Create Conditional Formatting Group
Hi, I'm trying to create a Conditional Formatting Group in the Customization tab like in the table visual: I thought I may be able to use the ConditionalFormattingControl as the Formatting Component but couldn't find anywhere how to insert it to the pane. Can someone provide an example on how to create something similar? Is it even possible? I want to create a status field that can be formatted as we do in the table visual, so I will want to know how to apply those to my Visual. Thanks in advance!Using the LocalStorage service inside a Dialog Box
Hello everyone I'm developing a custom visual that makes use of Dialog Boxes, as documented here. The visual also uses the local storage API to read and write data to the local storage. Now, while these two features work well on their own, there is a problem when trying to use them together. This is becasue, as far as I can tell, there seems to be no way to access the Local Storage API from inside the Dialog Box. In particular, the DialogBox's options do not give access to the IVisualLocalStorageV2Service. So, unlike with the main Visual's class, we cannot obtain the storage service in a DialogBox like this: as you can see, we get an error because that property doesn't exist. It also cannot be passed through via the "initialState" object, since it seems IVisualLocalStorageV2Service is not serializable. How can we work around this? Is ti possible?SolvedPublishing visual having free trial tier
Hello, I created custom visual and want to place it in the AppSource. This documentation article says: >The PBIX report must use the same version of the visual as the PBIVIZ. Another article says that sample PBIX is required and: > The sample .pbix report file must work offline, without any external connections. Does offline means that the visual can't check the licence using Licensing API? How licenced visuals are suppose to work then? If for example I define free trial tier then how can I verify that trial period expired without the connection to the Licensing API? Best regards, Damian Łoziński665Views4likes0CommentsHow to filter all visuals in a Power BI custom visual without specifying a table name?
I have a custom visual in Power BI, and I've written the following code to filter other tables based on a column called DateID: public update(options: VisualUpdateOptions) { const filter = { $schema: "http://powerbi.com/product/schema#basic", target: { table: "MRS3 vwMrsFactSales", column: "DateID", }, operator: "In", values: [13991002, 13991006], }; this.host.applyJsonFilter( filter, "general", "filter", powerbi.FilterAction.merge ); } it works when i specify table name in target but what i want is to filter all tables in report and i dont know waht are table names and when i remove table from target it broke. what should i do? I searched a lot, but there is no good documentation or YouTube video about this.Half Donut Chart or Gauge Chart with legends
Hi community, I need to recreate a visualization and have included the image below. It displays Impact Likelihoods, ranging from Rare to Possible, Likely, and Certain.Can anyone suggest a way to replicate this visualization? Also, note that the pointer indicates the average of the likelihood. This is the data is there any way to replicate the above visualisation? and also the pointer points to the Average of the Likelihood.Overwrite default value from property panel
Hello everyone, I have a custom visual where user can drag the horizontal line which change the column width. Default value is set to 125. I try to figure out how to ovewrite the default value from property panel like this: I did try to overwite the value this way: this.settings.columnProps.column_one = currentLines[0].getBoundingClientRect().left; ... but it doesn't work. When refresh the report the value back to default. Is there any API or ready solution to do this ? Can I use come kind of local storage ? Thanks for any help!SolvedApply multiple advanced filters from one custom visual
From a custom visual I want to filter two columns of a table not related to visual's data: a text column where all entries of a specific Value should be found a date column to filter for a date range I've implemented an advanced filter that can do one of these tasks following this documentation: https://learn.microsoft.com/en-us/power-bi/developer/visuals/filter-api#the-advanced-filter-api However, I can only use one of these two filters, because when the applyJsonFilter() method is called again, the first filter is overwritten. I tried adding a second filter with a different name to the capabilities.json, but this has no effect when calling it in the applyJsonFilter() method. Is there any way to achieve this in a custom visual? Thank you for your help