Power BI Advanced Series · Custom Visuals Development · by Raushan Ranjan, MCT
Welcome to RR Skillverse! This guide is designed to simplify the powerful combination of TypeScript and D3.js, especially for those starting out as Power BI developers. We'll explore what these tools are, why they're crucial in the context of Power BI custom visual development, and how they empower you to create stunning, interactive data visualizations beyond Power BI's built-in capabilities.
✅ 1. TypeScript Language Understanding
🌱 Goal:
Understand the basics of TypeScript — a statically typed superset of JavaScript that compiles to plain JavaScript.
🔹 Step-by-Step:
Setup:
- Install Node.js (which includes npm, the Node Package Manager).
- Open your terminal or command prompt and run:
This installs TypeScript globally on your system.npm install -g typescript - Create a new folder for your project and inside it, create a file named
main.ts(the.tsextension indicates a TypeScript file).
TypeScript Basics (in main.ts):
- Types: TypeScript introduces static types, allowing you to specify the expected data type for variables, function parameters, and return values. Common types include
string,number,boolean,any(for unknown types),void(for functions that don't return a value),null,undefined, andnever. - Interfaces: Define the structure (shape) of an object. They are crucial for ensuring objects conform to a specific contract.
interface Person { name: string; age: number; } const user: Person = { name: "Alice", age: 30 }; - Classes: Allow you to define blueprints for creating objects with properties and methods, supporting Object-Oriented Programming (OOP) principles.
class Employee { constructor(public name: string, private id: number) {} greet(): string { return `Hello, my name is ${this.name} and my ID is ${this.id}.`; } } const emp = new Employee("Bob", 101); console.log(emp.greet()); - Functions: You can define types for function parameters and their return values.
function add(a: number, b: number): number { return a + b; } console.log(add(5, 3));
Compile:
- Open your terminal in the folder containing
main.tsand run:
This command compilestsc main.tsmain.tsand generates a plain JavaScript file namedmain.jsin the same directory. This.jsfile is what web browsers can execute. - You can then use this compiled
main.jswith tools like Webpack for bundling, or directly include it in your HTML file using a<script>tag.
✅ 2. Getting Started with D3 and SVG Graphics
🌱 Goal:
Learn how D3.js can bind data to Scalable Vector Graphics (SVG) elements to create visual representations.
🔹 Step-by-Step:
Create an HTML file (e.g., index.html) and open it in a browser.
Add D3.js Library:
Include the D3.js library in your HTML file, typically in the <head> or before the closing </body> tag:
<script src="https://d3js.org/d3.v7.min.js"></script>
Create an SVG Container:
SVG is an XML-based vector image format for two-dimensional graphics. D3 uses SVG to draw shapes. Add an SVG element to your HTML <body>:
<svg width="500" height="300"></svg>
Draw Shapes Using D3.js (in a <script> block after D3 import):
Now, use D3 to select the SVG and append a basic shape like a circle:
d3.select("svg") // Selects the SVG element
.append("circle") // Appends a circle element to the SVG
.attr("cx", 100) // Sets the x-coordinate of the circle's center
.attr("cy", 100) // Sets the y-coordinate of the circle's center
.attr("r", 40) // Sets the radius of the circle
.style("fill", "steelblue"); // Sets the fill color of the circle
✅ 3. Creating Data-driven Visuals
🌱 Goal:
Use D3’s powerful data binding mechanism to generate visuals dynamically based on an array of data.
🔹 Example: Simple Bar Chart (replace previous D3 code)
const data = [30, 86, 168, 281, 303, 365]; // Our data points
d3.select("svg") // Select the SVG container
.selectAll("rect") // Select all rect elements (none exist yet)
.data(data) // Bind our 'data' array to the selection
.enter() // For each new data point, create a new element
.append("rect") // Append a new rectangle for each data point
.attr("width", (d) => d) // Set width based on the data value (d)
.attr("height", 30) // Fixed height for each bar
.attr("y", (d, i) => i * 35) // Position bars vertically based on index (i)
.style("fill", "teal"); // Set the fill color
💡 Key Concepts of Data Binding:
.data(data): This is the heart of D3. It joins the specifieddataarray with the selected DOM elements..enter(): Returns a selection of placeholder elements for each data point that doesn't have a corresponding DOM element. This is where you append new elements..update(): (Implicitly handled when not using.enter()or.exit()) For existing data points that have matching DOM elements..exit(): Returns a selection of DOM elements that no longer have corresponding data points. This is where you remove old elements.
✅ 4. Enhancing Visuals with Scales and Axes
🌱 Goal:
Map data values (data domain) to visual properties (pixel range) using D3 scales, and then add human-readable axes.
🔹 Example: Adding a Linear Scale (to the bar chart example)
const data = [30, 86, 168, 281, 303, 365];
const xScale = d3.scaleLinear() // Create a linear scale
.domain([0, d3.max(data)]) // Input data range (from 0 to max data value)
.range([0, 450]); // Output pixel range (e.g., 0 to 450 pixels for bar width)
d3.select("svg")
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", (d) => xScale(d)) // Use the scale to set width
.attr("height", 30)
.attr("y", (d, i) => i * 35)
.style("fill", "teal");
🔹 Axes:
Axes are visual representations of scales, providing context to your data.
const xAxis = d3.axisBottom(xScale); // Create a bottom axis based on xScale
d3.select("svg")
.append("g") // Append a group element to hold the axis
.attr("transform", "translate(0, 200)") // Move the axis to the bottom
.call(xAxis); // Call the axis generator to render the axis
✅ 5. Using D3 Layouts
🌱 Goal:
Utilize D3's powerful layout generators (like pie, stack, tree, etc.) to structure complex data for specific chart types.
🔹 Example: Pie Chart (replace previous D3 code)
const data = [30, 86, 168, 281, 303, 365];
const pie = d3.pie()(data); // Create a pie layout generator and apply it to data
const arc = d3.arc().innerRadius(0).outerRadius(100); // Create an arc generator for drawing slices
d3.select("svg")
.selectAll("path")
.data(pie) // Bind the pie-transformed data to path elements
.enter()
.append("path")
.attr("d", arc) // Use the arc generator to define the 'd' attribute (path data)
.attr("transform", "translate(150,150)") // Center the pie chart
.style("fill", (d, i) => d3.schemeCategory10[i]); // Assign colors from a D3 color scheme
✅ 6. Event Handling and Transitions
🌱 Goal:
Add interactivity (responding to user actions) and smooth animations to your D3 visuals.
🔹 Event Handling (add to your bar chart code)
d3.selectAll("rect") // Select all rectangles (bars)
.on("mouseover", function () { // When mouse hovers over a bar
d3.select(this).style("fill", "orange"); // Change its color to orange
})
.on("mouseout", function () { // When mouse leaves a bar
d3.select(this).style("fill", "teal"); // Change its color back to teal
});
🔹 Transitions (add to your bar chart code)
d3.selectAll("rect") // Select all rectangles
.transition() // Start a transition
.duration(1000) // Set transition duration to 1000 milliseconds (1 second)
.attr("width", (d) => xScale(d) * 1.5); // Animate width to 1.5 times its current scaled value
🧩 Combine TypeScript + D3
When building Power BI custom visuals, you'll typically write your D3 code within a TypeScript file. To do this, you need to install the D3.js library and its TypeScript type definitions.
- Install D3.js and its type definitions via npm:
npm install d3 npm install --save-dev @types/d3 - Import D3 in your TypeScript (
.ts) file:
Now you can useimport * as d3 from 'd3';d3.select(),d3.scaleLinear(), etc., directly in your TypeScript code, with full IntelliSense and type checking!
🧠 Mind Map: TypeScript + D3 for Power BI Developers
TypeScript + D3
│
├── TypeScript Primer
│ └── Types, Classes, Interfaces, Functions
│
├── SVG + D3 Setup
│ └── Shapes (circle, rect, line)
│
├── Data Binding
│ └── enter(), update(), exit()
│
├── Scales & Axes
│ └── scaleLinear, scaleBand, axisBottom
│
├── D3 Layouts
│ └── pie(), arc(), tree()
│
└── Events & Transitions
└── on('click'), transition(), animate
TypeScript + D3.js — What, Why, and How (especially for Power BI Developers)
🔍 What is TypeScript?
➤ Concept:
TypeScript is a superset of JavaScript that adds static typing, interfaces, and Object-Oriented Programming (OOP) features like classes and inheritance. Think of it like "JavaScript with safety features." It gets compiled into plain JavaScript, which web browsers can then run.
⚙️ Why Use It?
- Type safety: Catches common programming errors (like trying to add a number to a string) during development, before the code even runs, leading to fewer bugs.
- Better tooling: Provides enhanced autocompletion, refactoring capabilities, and code navigation in editors like VS Code, making development faster and more efficient.
- Maintainability: Easier to understand and manage large codebases, especially in collaborative environments, as the types clearly define data structures and function contracts.
🔍 What is D3.js?
➤ Concept:
D3.js (Data-Driven Documents) is a powerful JavaScript library used to create dynamic, interactive, data-driven visualizations directly in web browsers using SVG, HTML, and CSS. Instead of offering pre-defined chart types (like Power BI's native visuals), D3 lets you build visuals from scratch, binding data directly to graphic elements.
Examples of D3 visuals include:
- Highly customized bar, pie, or radial charts
- Complex interactive dashboards
- Smooth animated data transitions
- Specialized visualizations like network graphs, hierarchies, and geographic maps.
🧠 Why do we have them (in Power BI Developer context)?
🔷 TypeScript:
- It is the foundational language used when developing custom visuals for Power BI.
- Microsoft’s Power BI Custom Visual SDK is built with TypeScript, providing a robust framework.
- Ensures that the code for your custom visuals is type-safe, clean, and scalable, which is vital for complex visual logic.
🔷 D3.js:
- Used to draw custom visuals with full pixel-level control over design, layout, and animation.
- When Power BI's built-in visuals aren’t flexible enough to meet specific design or interaction requirements, D3 helps you go beyond.
- D3 is often the underlying rendering engine for many sophisticated Power BI custom visuals.
🧰 How is it helpful for a Power BI Developer?
| Feature | Why It Matters |
|---|---|
| 🧑💻 TypeScript | Helps write reliable, maintainable, and bug-resistant code for custom visuals, especially when the visual logic is complex or part of a large project. |
| 📊 D3.js | Gives full creative control over how data is presented—perfect when Power BI’s native visuals don’t fit the exact design or interactivity needs. |
| 🎨 SVG-based drawing | Allows you to draw and animate elements exactly how you want (e.g., draw curved lines, create custom shapes, animate graphs smoothly), providing unparalleled visual flexibility. |
| 🔧 Power BI Custom Visual SDK | This SDK itself leverages TypeScript and D3.js under the hood, providing the framework to define your visual's data binding, logic, and rendering. |
🔄 Real-life Analogy
Imagine Power BI’s default visuals are like readymade LEGO sets. You can build some amazing things, but you’re limited to what’s in the box and how the pieces are designed.
Using TypeScript + D3.js is like:
- Getting raw LEGO blocks (D3) and detailed blueprints (TypeScript) to design anything you want, exactly how you want it.
- You can animate, customize, and control every single piece and interaction, building truly unique data experiences.
💡 Typical Use Cases for Power BI Developers:
| Use Case | How TypeScript + D3 Helps |
|---|---|
| Need a custom gauge or dial visual | D3 lets you draw intricate SVG shapes for the gauge, while TypeScript manages its data binding, logic, and parameters. |
| Want to animate bars or pie chart slices | D3 has built-in animation methods (.transition(), .duration()) that allow for smooth and engaging data transitions. |
| Dynamic tooltips or drill-through behavior | D3 handles advanced event listeners (.on()) for precise interaction, and TypeScript organizes the complex behavior and data handling. |
| Building reusable visuals for clients | TypeScript ensures the code is structured, strongly typed, and testable, making it easier to maintain and scale for client projects. |
🚀 Final Summary:
| Concept | Description |
|---|---|
| TypeScript | Typed, structured JavaScript – helps write more reliable, maintainable, and bug-resistant code for custom visuals. |
| D3.js | A powerful JavaScript library for data-driven documents, acting as the drawing engine to create custom, interactive, and animated charts using SVG. |
| Power BI Custom Visual SDK | The framework provided by Microsoft for building custom visuals, which is itself built using TypeScript and designed to integrate seamlessly with D3.js. |
| Usefulness for Power BI Devs | Enables developers to create tailored, highly customized, and reusable visuals that go beyond the capabilities of Power BI's native options, offering full creative control over data presentation. |
Quick Knowledge Check
Q1. In D3.js, what does the .enter() selection represent?
Show Answer
It represents new data items that have no corresponding DOM element yet. In the enter/update/exit pattern: .data(dataset) binds the array; .enter() returns a selection of "missing" elements (data items without a node); .append('rect') creates the new DOM elements. This is how D3 adds bars for new data rows. .update() handles existing elements, and .exit() removes elements for deleted data rows.
Q2. A TypeScript interface in a Power BI custom visual defines a type for data rows. What is the primary benefit of using an interface over a plain any type?
- A) Interfaces compile to smaller JavaScript bundles
- B) The TypeScript compiler enforces that all required properties exist, catching mismatches at compile time
- C) Interfaces prevent D3 from accessing the data array
- D) Interfaces are required by the pbiviz package command
Show Answer
B. Using an interface (e.g., interface DataPoint { category: string; value: number }) causes the TypeScript compiler to verify that every property access matches the declared shape. Accessing a non-existent property causes a compile error, not a runtime crash. any bypasses all type checking — bugs appear only when the visual runs inside Power BI.
Q3. What is the purpose of d3.scaleBand() in a bar chart visual, and how does it differ from d3.scaleLinear()?
Show Answer
d3.scaleBand() maps a discrete (categorical) domain to a continuous range, evenly distributing bands with padding. It is used for the x-axis of a bar chart where each band represents a category (e.g., product name). d3.scaleLinear() maps a continuous numeric domain to a continuous range — used for the y-axis (the measure values). Using scaleLinear for categories produces overlapping labels; using scaleBand for numeric axes distorts spacing.
5 Things to Remember
- enter/update/exit is D3's core pattern — enter adds elements for new data, update changes existing ones, exit removes elements for deleted data.
- TypeScript interfaces prevent runtime crashes — declare a typed interface for your data rows; the compiler catches property mismatches before packaging.
scaleBand for categories, scaleLinear for numbers — band scale distributes discrete items evenly with padding; linear scale maps continuous numeric ranges.
- SVG coordinate system starts top-left — y increases downward. For a bar chart, bar height =
height - yScale(value) so taller values produce taller bars from the bottom.
- Chain D3 selections —
selection.attr().style().on() chains are idiomatic D3. Each method returns the selection so you can continue chaining without re-selecting.
Quick Knowledge Check
Q1. In D3.js, what does the .enter() selection represent?
Show Answer
It represents new data items that have no corresponding DOM element yet. In the enter/update/exit pattern: .data(dataset) binds the array; .enter() returns a selection of "missing" elements (data items without a node); .append('rect') creates the new DOM elements. This is how D3 adds bars for new data rows. .update() handles existing elements, and .exit() removes elements for deleted data rows.
Q2. A TypeScript interface in a Power BI custom visual defines a type for data rows. What is the primary benefit of using an interface over a plain any type?
- A) Interfaces compile to smaller JavaScript bundles
- B) The TypeScript compiler enforces that all required properties exist, catching mismatches at compile time
- C) Interfaces prevent D3 from accessing the data array
- D) Interfaces are required by the pbiviz package command
Show Answer
B. Using an interface (e.g., interface DataPoint { category: string; value: number }) causes the TypeScript compiler to verify that every property access matches the declared shape. Accessing a non-existent property causes a compile error, not a runtime crash. any bypasses all type checking — bugs appear only when the visual runs inside Power BI.
Q3. What is the purpose of d3.scaleBand() in a bar chart visual, and how does it differ from d3.scaleLinear()?
Show Answer
d3.scaleBand() maps a discrete (categorical) domain to a continuous range, evenly distributing bands with padding. It is used for the x-axis of a bar chart where each band represents a category (e.g., product name). d3.scaleLinear() maps a continuous numeric domain to a continuous range — used for the y-axis (the measure values). Using scaleLinear for categories produces overlapping labels; using scaleBand for numeric axes distorts spacing.
- enter/update/exit is D3's core pattern — enter adds elements for new data, update changes existing ones, exit removes elements for deleted data.
- TypeScript interfaces prevent runtime crashes — declare a typed interface for your data rows; the compiler catches property mismatches before packaging.
scaleBandfor categories,scaleLinearfor numbers — band scale distributes discrete items evenly with padding; linear scale maps continuous numeric ranges.- SVG coordinate system starts top-left — y increases downward. For a bar chart, bar height =
height - yScale(value)so taller values produce taller bars from the bottom. - Chain D3 selections —
selection.attr().style().on()chains are idiomatic D3. Each method returns the selection so you can continue chaining without re-selecting.