3d pie chart
1 TopicNeed 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": [] }