Building a Custom Bar Chart in Power BI with TypeScript and D3.js
Building a Custom Bar Chart in Power BI with TypeScript and D3.js
Welcome to this exciting, real-world tutorial! Today, we're going to build a custom bar chart visual for Power BI using TypeScript and the D3.js library. This end-to-end walkthrough, guided by Raushan, will take you from setting up your development environment to seeing your custom visual come to life in Power BI Desktop.
🎯 Goal:
Build a custom bar chart visual in Power BI using the Power BI Custom Visual SDK with TypeScript and D3.js.
✅ Step-by-Step Walkthrough
🧰 Prerequisites:
Before we start, ensure you have Node.js and npm installed. Then, install the Power BI Custom Visuals Tools globally:
npm install -g powerbi-visuals-tools
Next, create a new custom visual project. This command generates a boilerplate structure with TypeScript, D3, and Power BI APIs already set up:
pbiviz new BarChartVisual cd BarChartVisual
🗂️ Folder Structure Overview
After running pbiviz new
, you'll see a project structure similar to this. Here's a quick overview of the most important files:
BarChartVisual/ │ ├── src/ │ ├── visual.ts <- Your main visual logic (TypeScript + D3.js) │ ├── settings.ts <- Defines properties for the visual's formatting pane │ ├── capabilities.json <- Declares what data roles and properties this visual needs ├── pbiviz.json <- Visual metadata, ID, name, and dependencies └── ... <- Other configuration files and folders
✏️ Step 1: Define Data Fields in capabilities.json
This file tells Power BI what kind of data your visual expects (e.g., categories, values). Open capabilities.json
and modify the dataRoles
and dataViewMappings
sections as follows:
"dataRoles": [ { "name": "category", "kind": "Grouping", "displayName": "Category" }, { "name": "value", "kind": "Measure", "displayName": "Value" } ], "dataViewMappings": [ { "categorical": { "categories": { "for": { "in": "category" } }, "values": { "select": [{ "bind": { "to": "value" } }] } } } ]
dataRoles
: Defines the fields that users can drag into your visual's data wells (e.g., "Category" and "Value").dataViewMappings
: Specifies how these data roles map to the visual's internal data structure (categorical data in this case).
✏️ Step 2: Write Custom Visual Logic in visual.ts
This is the core of your custom visual, where you'll use TypeScript and D3.js to render the bar chart. Open src/visual.ts
and replace its content with the following code:
import * as d3 from "d3"; // Import D3.js library import powerbi from "powerbi-visuals-api"; // Power BI Visuals API import DataView = powerbi.DataView; // Type definition for DataView import VisualConstructorOptions = powerbi.VisualConstructorOptions; import VisualUpdateOptions = powerbi.VisualUpdateOptions; import IVisual = powerbi.extensibility.IVisual; // Define an interface for our data structure interface BarData { category: string; value: number; } export class Visual implements IVisual { private svg: d3.Selection<SVGElement, any, any, any>; private barsGroup: d3.Selection<SVGGElement, any, any, any>; // Constructor: Called once when the visual is initialized constructor(options: VisualConstructorOptions) { // Select the host element and append an SVG container this.svg = d3.select(options.element) .append('svg') .classed('barChart', true); // Add a CSS class for styling // Append a group element to hold our bars (makes transformations easier) this.barsGroup = this.svg.append('g') .classed('bars', true); } // Update method: Called whenever data or visual properties change public update(options: VisualUpdateOptions) { // Get the current width and height of the visual container const width = options.viewport.width; const height = options.viewport.height; // Set the SVG dimensions to match the viewport this.svg.attr("width", width).attr("height", height); // Get the dataView from Power BI options const dataView: DataView = options.dataViews[0]; // Extract categories and values from the dataView const categories = dataView.categorical.categories[0].values; const values = dataView.categorical.values[0].values; // Map the raw Power BI data into a more D3-friendly structure const data: BarData[] = categories.map((cat, i) => ({ category: cat.toString(), value: <number>values[i] })); // Define X-scale (Band scale for categories) const xScale = d3.scaleBand() .domain(data.map(d => d.category)) // Categories as domain .range([0, width]) // Map to full width of the visual .padding(0.2); // Add padding between bars // Define Y-scale (Linear scale for values) const yScale = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)!]) // Values from 0 to max value .range([height, 0]); // Map to full height (inverted for SVG y-axis) // D3 Data Join: Select existing bars, bind new data const bars = this.barsGroup.selectAll("rect").data(data); // ENTER + UPDATE: Add new bars and update existing ones bars.enter() .append("rect") // Append a new rect for each new data point .merge(bars as any) // Merge enter and update selections .attr("x", d => xScale(d.category)!) // X position based on category .attr("y", d => yScale(d.value)) // Y position based on value (top of bar) .attr("width", xScale.bandwidth()) // Width of bar from scaleBand .attr("height", d => height - yScale(d.value)) // Height of bar .attr("fill", "steelblue"); // Bar color // EXIT: Remove bars that no longer have corresponding data bars.exit().remove(); } }
- Imports: We import
d3
for drawing and Power BI API types for visual development. - Constructor: This method is called once when the visual is initialized. We select the host HTML element provided by Power BI and append an SVG container to it. We also create a `
` (group) element to hold our bars, which helps in organizing the SVG elements. - Update Method: This is the most important method, called whenever the data, size, or properties of the visual change.
- It retrieves the current
width
andheight
of the visual and sets the SVG dimensions. - It extracts the
category
andvalue
data from Power BI'sdataView
and maps it into a D3-friendly array of objects. - Scales: It defines two D3 scales:
xScale
(ad3.scaleBand()
): Maps categorical data (e.g., product names) to positions along the x-axis, providing bandwidth for each bar.yScale
(ad3.scaleLinear()
): Maps numerical data (e.g., sales values) to pixel heights along the y-axis. Note that SVG's y-axis typically increases downwards, so we map fromheight
to0
.
- D3 Data Join (
enter()
,merge()
,exit()
): This is the core D3 pattern for updating visuals.bars.enter().append("rect")
: Creates new `` elements for any new data points. .merge(bars as any)
: Combines the newly created elements with existing elements, allowing a single chain of attributes to apply to both.- Attributes like
x
,y
,width
,height
, andfill
are set dynamically based on the data and scales. bars.exit().remove()
: Removes any `` elements that no longer have corresponding data points (e.g., if a category is filtered out).
- It retrieves the current
⚙️ Step 3: Test the Visual
Now, let's see your custom bar chart in action!
- Open your terminal in the
BarChartVisual
folder and run:pbiviz start
https://localhost:8080/visuals/
). - Open Power BI Desktop.
- Go to the Developer tab (if not visible, enable it in File > Options and settings > Options > Preview features > Developer mode visual).
- Click the Developer Visual icon in the Visualizations pane.
- Drag some sample data (e.g., "Product Category" to Category, "Total Sales" to Value) onto your visual. You should now see your custom D3.js bar chart!
🎨 Add Event Handling (Optional)
Let's make our bars interactive! You can add event listeners to your D3 elements. Inside the update()
method, after the .attr("fill", "steelblue");
line, add the following:
// Add a click event listener to the bars bars.enter() .append("rect") .merge(bars as any) // ... (existing attributes) ... .on("click", (event, d) => { // In a real visual, you'd use powerbi.extensibility.utils.interactivity.selectionManager // to handle selections. For this demo, we'll use a simple alert. alert(`You clicked on ${d.category} with value ${d.value}`); });
Now, refresh your visual in Power BI Desktop (using the refresh button on the Developer Visual) and click on a bar. You should see a browser alert!
🚀 Build for Production
Once you're happy with your visual, you can package it into a .pbiviz
file for distribution.
- In your terminal (still in the
BarChartVisual
folder), run:pbiviz package
.pbiviz
file inside the/dist
folder. This file can then be imported into any Power BI Desktop report or uploaded to Power BI Service.
🧠 Recap – What You Used
Technology | Purpose |
---|---|
TypeScript | Used to write clean, type-safe, and maintainable logic for rendering the visual, catching errors early. |
D3.js | The powerful JavaScript library used to draw and manipulate SVG elements dynamically based on data, creating the bars and handling their positioning and sizing. |
Power BI Custom Visual SDK | Provided the framework and APIs (like IVisual , DataView , VisualUpdateOptions ) to integrate our D3.js code with Power BI, handle visual packaging, data binding, and formatting pane properties. |
💡 Real-World Scenarios:
Building custom visuals is not just an academic exercise; it solves real business problems:
- Advanced animations: When you need more sophisticated visual transitions or animations than built-in visuals provide (e.g., changing bar color on click, animating growth).
- Unique charts: To create highly specialized or non-standard chart types (e.g., Sunburst charts, Radial Bar charts, Bullet charts, Sankey diagrams) that are not available natively.
- Visual identity/Branding: For organizations that require specific branded or company-themed visuals that strictly adhere to design guidelines.
- Interactive components: To implement custom drilldowns, tooltips, dynamic highlights, or other interactive behaviors that go beyond standard Power BI interactions.
- Integrating external libraries: When you need to leverage other JavaScript libraries for specific functionalities within your visual.
Comments
Post a Comment