BIBB
DAX Featured
•

DAX and UDF SVG Charts in Power BI: Complete Guide

Oscar MartĂ­nez
Oscar MartĂ­nez Data, BI and AI | Operational Lead | Power BI & Azure Expert | Governance-Driven Strategy | Product Owner

Transform Your Power BI Visualizations with DAX and UDF SVG Charts

Project Goal: Create a dynamic SVG bar chart in Power BI using DAX that generates rect elements based on table data, with automatic positioning and sizing.

Traditional Power BI visuals offer limited customization options and often require external dependencies. This comprehensive guide demonstrates how to create DAX and UDF SVG charts in Power BI using pure code, giving you complete control over visual design and behavior without any third-party tools.

By the end of this tutorial, you’ll have mastered DAX and UDF SVG charts in Power BI with a reusable User-Defined Function that generates professional-quality visualizations for any measure and dimension combination, featuring conditional formatting, negative value support, and automatic scaling.

What You'll Build

These DAX and UDF SVG charts in Power BI provide a complete custom visualization solution:

Core Features:

  • Dynamic charts that adapt to any number of data points
  • Automatic positioning and sizing calculations based on data
  • Support for negative values with dynamic zero baseline positioning
  • Conditional color coding with highest and lowest values highlighted
  • Flexible color schemes that adapt to different business contexts
  • Reusable UDF for creating multiple chart instances with minimal code

Technical Benefits:

  • No external dependencies or custom visuals required
  • Complete control over visual appearance and behavior
  • Responsive design that works across different report sizes
  • Performance-optimized with single-measure implementation
  • Easy to maintain and extend with additional features
Visual Specifications and Design Goals

Before writing any DAX code, it’s essential to understand exactly what we’re building and how SVG charts work in Power BI.

Target Visual Output

The final implementation generates SVG markup that creates a fully functional bar chart:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 300' width='400' height='300' preserveAspectRatio='none'>
  <style>.default { fill: #9ca3af; }</style>
  <g id='bars'>
    <rect x='20' y='70%' width='25' height='30%' class='default' rx='2'/>
    <rect x='52' y='62%' width='25' height='38%' class='default' rx='2'/>
    <rect x='84' y='53%' width='25' height='47%' class='default' rx='2'/>
    <!-- ... more bars ... -->
  </g>
  <line x1='20' y1='100%' x2='390' y2='100%' stroke='#e5e7eb' stroke-width='2'/>
</svg>

Visual Specifications

  • SVG Canvas: 500px width Ă— 300px height (fixed)
  • Chart Area: 80% of height (240px) with 10% padding top/bottom
  • Bar Styling: 2px rounded corners, conditional colors
  • Colors:
    • Default: Gray (#9ca3af)
    • Highest value: Green (#014D17) or Red (#C2130C)
    • Lowest value: Red (#C2130C) or Green (#014D17)
  • Layout: 20% spacing between bars, 20px left/right margins

Key Requirements

  1. One bar per data point
  2. Bars proportionally sized based on values
  3. Negative values grow downward from baseline
  4. Minimum bar height for visibility
  5. Dynamic width based on number of bars

Step by Step DAX and UDF SVG Charts Implementation Guide

Step 1: Create Sample Data Table

Before we start building the visualization, let’s create a sample data table that you can use to follow along with this tutorial. This table includes monthly sales data that will be perfect for testing our SVG bar chart.

Create the Sample Table

  1. Open Power BI Desktop
  2. Go to the Modeling tab
  3. Click New Table
  4. Paste the following DAX code:
SampleSalesData = 
DATATABLE(
    "Year-Month", STRING,
    "Sales", CURRENCY,
    "Costs", CURRENCY,
    "Profit", CURRENCY,
    {
        {"2023-01", 4484000, 3587200, 896800},
        {"2023-02", 5295611, 4130376, 1165235},
        {"2023-03", 6267203, 4701402, 1565801},
        {"2023-04", 7145892, 5002124, 2143768},
        {"2023-05", 8234567, 5764197, 2470370},
        {"2023-06", 9123456, 6195848, 2927608},
        {"2023-07", 8567234, 5996864, 2570370},
        {"2023-08", 9876543, 6707729, 3168814},
        {"2023-09", 10234567, 7164197, 3070370},
        {"2023-10", 11456789, 7550272, 3906517},
        {"2023-11", 12375820, 8093054, 4282766},
        {"2023-12", 10987654, 7691358, 3296296},
        {"2024-01", 9234567, 6457797, 2776770},
        {"2024-02", 8567890, 6003923, 2563967},
        {"2024-03", 9876543, 6907980, 2968563},
        {"2024-04", 10456789, 7319752, 3137037}
    }
)

Create Measures

Now create measures to aggregate the data:

Total Sales = SUM(SampleSalesData[Sales])
Total Costs = SUM(SampleSalesData[Costs])
Total Profit = SUM(SampleSalesData[Profit])
Profit Margin % = 
DIVIDE(
    [Total Profit],
    [Total Sales],
    0
) * 100

Verify Your Setup

After creating the table and measures:

  1. Add a Table visual to your report
  2. Add Year-Month, Total Sales, Total Costs, Total Profit, and Profit Margin %
  3. You should see 16 rows of data

You’re now ready to build the SVG bar chart!

Step 2: Prepare Your Sample Data - Understanding Your Data Structure

Explore the Data Structure

Now that you’ve created the sample data table, let’s understand its structure and how we’ll use it in our visualization.

Use this DAX query in the DAX query view to explore the data:

EVALUATE
VAR Base = 
    SUMMARIZECOLUMNS ( SampleSalesData[Year-Month] )

VAR WithValues = 
    ADDCOLUMNS ( Base, "Value", [Total Sales] )

VAR NonBlank = 
    FILTER ( WithValues, NOT ISBLANK ( [Value] ) )

VAR MaxVal = MAXX ( NonBlank, [Value] )
VAR MinVal = MINX ( NonBlank, [Value] )

RETURN
    ADDCOLUMNS (
        NonBlank,
        "HiLoFlag",
            VAR v = [Value]
            RETURN SWITCH ( TRUE(), v = MaxVal, "good", v = MinVal, "bad", "default" )
    )
ORDER BY SampleSalesData[Year-Month]

Expected Query Result

Year-MonthValueHiLoFlag
2023-01$4,484,000bad
2023-02$5,295,611default
2023-03$6,267,203default
2023-04$7,145,892default
………
2023-11$12,375,820good
………

HiLoFlag Meaning:

  • "good" = Highest value (will be highlighted in green)
  • "bad" = Lowest value (will be highlighted in red)
  • "default" = Normal values (will be gray)

Key Insights

This query demonstrates:

  1. Data aggregation using SUMMARIZECOLUMNS
  2. Value calculation by adding measure results to each row
  3. Filtering to remove blank values
  4. Classification to identify highest and lowest values
  5. Ranking logic that will be used for conditional formatting

Understanding this structure is crucial as it forms the foundation of our SVG bar chart generation logic.

Step 3: Create the Complete Measure

Now let’s create the complete DAX measure that generates the SVG bar chart. This measure combines data preparation, styling, canvas calculations, and SVG generation into a single implementation.

Implementation Steps

  1. Create the Measure

    • Go to your data model in Power BI Desktop
    • Create a new measure called Bars SVG
    • Paste the complete code below
  2. Add to Report

    • Add an HTML content visual or Table visual to your report
    • Add the Bars SVG measure to the visual’s data field
    • The SVG should render as a bar chart
  3. Verify Results

    • Check that all bars are visible
    • Verify highest value is green (or red if GoodNature = 2)
    • Verify lowest value is red (or green if GoodNature = 2)
    • Check that bars adapt dynamically when you filter the data

Complete Measure Code

Bars SVG = 

VAR GoodNature = 1

VAR Base = SUMMARIZECOLUMNS ( SampleSalesData[Year-Month] )

VAR WithValues = 
    ADDCOLUMNS ( Base, "Value", [Total Sales])

VAR NonBlank = 
    FILTER ( WithValues, NOT ISBLANK ( [Value] ) )

VAR MaxVal = MAXX ( NonBlank, [Value] )
VAR MinVal = MINX ( NonBlank, [Value] )

VAR RangeVal = MaxVal - MinVal

VAR WithMetrics = 
    ADDCOLUMNS (
        NonBlank,
        "HiLoFlag", 
            VAR v = [Value]
            RETURN SWITCH ( TRUE(), v = MaxVal, "good", v = MinVal, "bad", "default" ),
        "RowNumber", RANKX ( NonBlank, SampleSalesData[Year-Month], , ASC, DENSE )
    )

VAR RowCount = COUNTROWS ( WithMetrics )

var GoodNatureStyle = SWITCH(GoodNature,
    1, ".default { fill: #9ca3af; }
        .good { fill: #014D17; }
        .bad  { fill: #C2130C; }",
    2, ".default { fill: #9ca3af; }
        .good { fill: #C2130C; }
        .bad  { fill: #014D17; }"
)

VAR SVGWidth = 500
VAR SVGHeight = 300
VAR ChartHeight = SVGHeight * 0.8
VAR ChartTop = SVGHeight * 0.1
VAR StartX = 20
VAR EndX = SVGWidth - 20
VAR AvailableWidth = EndX - StartX
VAR BarSpacingRatio = 0.2
VAR TotalBarWidth = AvailableWidth / RowCount
VAR BarWidth = TotalBarWidth * ( 1 - BarSpacingRatio )

VAR ZeroLineY = 
    IF(RangeVal = 0,
        ChartTop + ChartHeight,
        ChartTop + ChartHeight * (MaxVal / RangeVal)
    )

VAR MinBarHeightPercent = 0.05

VAR SVGHeader = 
    "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 " & SVGWidth & " " & SVGHeight & 
    "' width='" & SVGWidth & "' height='" & SVGHeight & "' preserveAspectRatio='none'>" &
    "<style>
        "&GoodNatureStyle&"
    </style><g id='bars'>"

VAR SVGBars = 
    CONCATENATEX (
        WithMetrics,
        VAR CurrentValue = [Value]
        VAR CurrentHighLoFlag = [HiLoFlag]
        VAR CurrentRow = [RowNumber]
        VAR IsNegative = CurrentValue < 0
        
        VAR AbsValue = ABS(CurrentValue)
        VAR ProportionalHeight = 
            IF(RangeVal = 0, 
                MinBarHeightPercent * ChartHeight,
                (AbsValue / RangeVal) * ChartHeight
            )
        VAR BarHeight = MAX(ProportionalHeight, MinBarHeightPercent * ChartHeight)
        
        VAR XPos = StartX + ( CurrentRow - 1 ) * TotalBarWidth
        
        VAR YPos = 
            IF(IsNegative,
                ZeroLineY,
                ZeroLineY - BarHeight
            )
        
        RETURN 
            "<rect x='" & XPos & 
            "' y='" & YPos & 
            "' width='" & BarWidth & 
            "' height='" & BarHeight & 
            "' class='"&CurrentHighLoFlag&"' rx='2'/>",
        "",
        SampleSalesData[Year-Month], DESC
    )

VAR SVGFooter = 
    "</g><line x1='" & StartX & 
    "' y1='" & ZeroLineY & 
    "' x2='" & EndX & 
    "' y2='" & ZeroLineY & 
    "' stroke='#e5e7eb' stroke-width='2'/></svg>"

VAR FinalSVG = SVGHeader & SVGBars & SVGFooter

RETURN 
    "data:image/svg+xml;utf8," & FinalSVG

What This Measure Does

This measure performs several key operations:

  1. Data Preparation: Gathers and filters data from your sample sales table
  2. Normalization: Calculates min/max values and adds classification flags
  3. Color Scheme: Applies context-aware styling based on whether high values are good or bad
  4. Canvas Setup: Defines SVG dimensions, margins, and spacing
  5. Bar Generation: Dynamically creates SVG rect elements with proper positioning and sizing
  6. Baseline Handling: Calculates zero line position for mixed positive/negative values
  7. SVG Assembly: Combines all elements into a complete, embeddable data URI

Test Point: Your measure should output a complete data URI starting with data:image/svg+xml;utf8,<svg...

Step 4: Convert to User-Defined Function (UDF) - Creating a Reusable Function

Now let’s make these DAX and UDF SVG charts in Power BI reusable! A User-Defined Function allows you to use the same visualization logic with different measures and dimensions across multiple reports.

Step 4a: Enable UDF Preview Feature

  1. Go to File > Options and settings > Options
  2. Select Preview features
  3. Check DAX user-defined functions
  4. Click OK and restart Power BI Desktop

Step 4b: Create the UDF

  1. Open DAX query view in Power BI Desktop (bottom icon panel)
  2. Paste the UDF definition below
  3. Click Update model with changes (top toolbar)
  4. The function will appear in Model Explorer under Functions
Click to view the complete UDF definition code
DEFINE
    FUNCTION GenerateSVGBarChart = (
        MeasureValue : anyref ,
        CategoryColumn : anyref,
        CategoryOrder : anyref,
        GoodNature: anyref
    ) =>
VAR Base = SUMMARIZECOLUMNS ( CategoryColumn )

VAR WithValues = 
    ADDCOLUMNS ( Base, "Value", MeasureValue)

VAR NonBlank = 
    FILTER ( WithValues, NOT ISBLANK ( [Value] ) )

VAR MaxVal = MAXX ( NonBlank, [Value] )
VAR MinVal = MINX ( NonBlank, [Value] )

VAR RangeVal = MaxVal - MinVal

VAR WithMetrics = 
    ADDCOLUMNS (
        NonBlank,
        "HiLoFlag", 
            VAR v = [Value]
            RETURN SWITCH ( TRUE(), v = MaxVal, "good", v = MinVal, "bad", "default" ),
        "RowNumber", RANKX ( NonBlank, CategoryColumn, , CategoryOrder, DENSE )
    )

VAR RowCount = COUNTROWS ( WithMetrics )

var GoodNatureStyle = SWITCH(GoodNature,
    1, ".default { fill: #9ca3af; }
        .good { fill: #014D17; }
        .bad  { fill: #C2130C; }",
    2, ".default { fill: #9ca3af; }
        .good { fill: #C2130C; }
        .bad  { fill: #014D17; }"
)

VAR SVGWidth = 500
VAR SVGHeight = 300
VAR ChartHeight = SVGHeight * 0.8
VAR ChartTop = SVGHeight * 0.1
VAR StartX = 20
VAR EndX = SVGWidth - 20
VAR AvailableWidth = EndX - StartX
VAR BarSpacingRatio = 0.2
VAR TotalBarWidth = AvailableWidth / RowCount
VAR BarWidth = TotalBarWidth * ( 1 - BarSpacingRatio )

VAR ZeroLineY = 
    IF(RangeVal = 0,
        ChartTop + ChartHeight,
        ChartTop + ChartHeight * (MaxVal / RangeVal)
    )

VAR MinBarHeightPercent = 0.05

VAR SVGHeader = 
    "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 " & SVGWidth & " " & SVGHeight & 
    "' width='" & SVGWidth & "' height='" & SVGHeight & "' preserveAspectRatio='none'>" &
    "<style>
        "&GoodNatureStyle&"
    </style><g id='bars'>"

VAR SVGBars = 
    CONCATENATEX (
        WithMetrics,
        VAR CurrentValue = [Value]
        VAR CurrentHighLoFlag = [HiLoFlag]
        VAR CurrentRow = [RowNumber]
        VAR IsNegative = CurrentValue < 0
        
        VAR AbsValue = ABS(CurrentValue)
        VAR ProportionalHeight = 
            IF(RangeVal = 0, 
                MinBarHeightPercent * ChartHeight,
                (AbsValue / RangeVal) * ChartHeight
            )
        VAR BarHeight = MAX(ProportionalHeight, MinBarHeightPercent * ChartHeight)
        
        VAR XPos = StartX + ( CurrentRow - 1 ) * TotalBarWidth
        
        VAR YPos = 
            IF(IsNegative,
                ZeroLineY,
                ZeroLineY - BarHeight
            )
        
        RETURN 
            "<rect x='" & XPos & 
            "' y='" & YPos & 
            "' width='" & BarWidth & 
            "' height='" & BarHeight & 
            "' class='"&CurrentHighLoFlag&"' rx='2'/>",
        "",
        CategoryColumn, DESC
    )

VAR SVGFooter = 
    "</g><line x1='" & StartX & 
    "' y1='" & ZeroLineY & 
    "' x2='" & EndX & 
    "' y2='" & ZeroLineY & 
    "' stroke='#e5e7eb' stroke-width='2'/></svg>"

VAR FinalSVG = SVGHeader & SVGBars & SVGFooter

RETURN 
    "data:image/svg+xml;utf8," & FinalSVG
Step 5: Use the UDF in Measures - Creating Multiple Chart Instances

Now you can create multiple DAX and UDF SVG charts in Power BI with just one line of code! Let’s create three different chart types using our sample data to demonstrate the flexibility of this approach.

Example 1: Sales Trend by Month

Sales Trend Chart = 
    GenerateSVGBarChart(
        [Total Sales],
        SampleSalesData[Year-Month],
        ASC,
        1  // High sales are good (green)
    )

Example 2: Costs by Month

Cost Trend Chart = 
    GenerateSVGBarChart(
        [Total Costs],
        SampleSalesData[Year-Month],
        ASC,
        2  // High costs are bad (red)
    )

Example 3: Profit Margin % by Month

Profit Margin Chart = 
    GenerateSVGBarChart(
        [Profit Margin %],
        SampleSalesData[Year-Month],
        ASC,
        1  // High margin is good (green)
    )

How to Use These Charts

  1. Create each measure in your data model using the code above

  2. Add one Card (New) visuals to your report

    • Find “Card (New)” in the Visualizations pane
    • It’s the modern card visual with enhanced formatting capabilities Add new card visual for DAX SVG charts in Power BI
  3. Configure the Card visual:

    • Add Sales Trend Chart measure to the Value well
    • Add Cost Trend Chart measure to the Value well
    • Add Profit Margin Chart measure to the Value well Configure card visual to display DAX and UDF SVG charts
  4. Format each Card to display the SVG:

    • Go to the Format pane (paint roller icon)
    • Expand the Images section
    • Under Apply settings to, select Total Series
    • Turn on Image toggle
    • Set Image type to URL
    • Click the Fx (conditional formatting) button next to Image URL Format image URL for Power BI DAX SVG chart display
    • In the “Format Image URL” dialog, select Field value
    • Choose your SVG measure from the dropdown (e.g., Sales Trend Chart) Select field value for DAX and UDF SVG charts configuration
    • Click OK
  5. Arrange them vertically to compare trends across all metrics

  6. Repeat for each chart measure

Parameter Guide

ParameterTypeDescriptionExample Values
MeasureValueanyrefThe measure to visualize[Total Sales], [Total Costs], [Profit Margin %]
CategoryColumnanyrefThe dimension column for X-axisSampleSalesData[Year-Month]
CategoryOrderanyrefSort direction for categoriesASC (chronological), DESC (reverse)
GoodNatureanyrefColor scheme interpretation1 (high=good), 2 (high=bad)

Technical Deep Dive: Advanced DAX and UDF SVG Charts Concepts

Understanding DAX and UDF SVG Charts Measure Components

This section explains the key technical concepts and calculations used in the measure, breaking down each component for better understanding.

1. Data Foundation and Preparation

VAR Base = SUMMARIZECOLUMNS ( 'Date Table'[Year-Month] )
VAR WithValues = ADDCOLUMNS ( Base, "Value", [Sales])
VAR NonBlank = FILTER ( WithValues, NOT ISBLANK ( [Value] ) )

What This Does:

  • Base: Creates a table with unique Year-Month values from your date dimension
  • WithValues: Adds the measure value ([Sales]) for each month in the current filter context
  • NonBlank: Removes any rows where the measure returns BLANK (no data for that period)

Why It Matters: This ensures we only visualize periods with actual data, preventing empty bars or gaps in the chart.

2. Value Normalization and Classification

VAR MaxVal = MAXX ( NonBlank, [Value] )
VAR MinVal = MINX ( NonBlank, [Value] )
VAR RangeVal = MaxVal - MinVal

VAR WithMetrics = 
    ADDCOLUMNS (
        NonBlank,
        "HiLoFlag", 
            VAR v = [Value]
            RETURN SWITCH ( TRUE(), 
                v = MaxVal, "good", 
                v = MinVal, "bad", 
                "default" 
            ),
        "RowNumber", RANKX ( NonBlank, 'Date Table'[Year-Month], , ASC, DENSE )
    )

What This Does:

  • Finds the highest and lowest values in the dataset
  • Calculates the total range (difference between max and min)
  • Assigns each data point a classification: “good” (highest), “bad” (lowest), or “default”
  • Assigns a sequential row number for positioning bars left to right

Why It Matters: This enables proportional scaling and conditional formatting based on data values.

3. Context-Aware Color Scheme

VAR GoodNature = 1  // 1 = high is good, 2 = high is bad

var GoodNatureStyle = SWITCH(GoodNature,
    1, ".default { fill: #9ca3af; }
        .good { fill: #014D17; }
        .bad  { fill: #C2130C; }",
    2, ".default { fill: #9ca3af; }
        .good { fill: #C2130C; }
        .bad  { fill: #014D17; }"
)

What This Does:

  • GoodNature = 1: High values are positive (Sales, Profit) → Highest = Green, Lowest = Red
  • GoodNature = 2: High values are negative (Costs, Defects) → Highest = Red, Lowest = Green

Why It Matters: The same visualization logic adapts to different business contexts by reversing the color meaning.

4. Canvas Dimensions and Bar Spacing

VAR SVGWidth = 500
VAR SVGHeight = 300
VAR ChartHeight = SVGHeight * 0.8  // 240px
VAR ChartTop = SVGHeight * 0.1      // 30px
VAR StartX = 20
VAR EndX = SVGWidth - 20
VAR AvailableWidth = EndX - StartX  // 460px
VAR BarSpacingRatio = 0.2
VAR TotalBarWidth = AvailableWidth / RowCount
VAR BarWidth = TotalBarWidth * ( 1 - BarSpacingRatio )

Calculation Breakdown:

  • Fixed Canvas: 500Ă—300px provides consistent viewBox across all data scenarios
  • Chart Area: 240px (80% of height) with 30px top padding and 30px bottom padding
  • Horizontal Margins: 20px on left and right sides
  • Available Width: 460px (500 - 20 - 20)
  • Dynamic Bar Width: Automatically adjusts based on data point count
    • With 10 bars: TotalBarWidth = 46px → BarWidth = 36.8px (80%) with 9.2px spacing (20%)
    • With 20 bars: TotalBarWidth = 23px → BarWidth = 18.4px (80%) with 4.6px spacing (20%)

Why It Matters: Fixed canvas ensures consistency while dynamic bar widths provide responsive adaptation to any dataset size.

5. Zero Baseline Calculation

VAR ZeroLineY = 
    IF(RangeVal = 0,
        ChartTop + ChartHeight,
        ChartTop + ChartHeight * (MaxVal / RangeVal)
    )

VAR MinBarHeightPercent = 0.05

What This Does:

  • All Positive Values (0 to 100): Zero line at bottom → 30 + 240 = 270px
  • Mixed Values (-50 to 100): Zero line proportionally positioned
    • Range = 150, MaxVal = 100
    • ZeroLineY = 30 + (240 Ă— 100/150) = 30 + 160 = 190px
  • All Negative Values (-100 to 0): Zero line at top → 30px
  • Minimum Height: 5% (12px) ensures smallest bars remain visible

Why It Matters: Correct baseline positioning is essential for accurate visual representation of both positive and negative values.

6. SVG Structure and Styling

VAR SVGHeader = 
    "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 " & SVGWidth & " " & SVGHeight & 
    "' width='" & SVGWidth & "' height='" & SVGHeight & "' preserveAspectRatio='none'>" &
    "<style>" & GoodNatureStyle & "</style><g id='bars'>"

What This Creates:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 300' 
     width='500' height='300' preserveAspectRatio='none'>
  <style>
    .default { fill: #9ca3af; }
    .good { fill: #014D17; }
    .bad  { fill: #C2130C; }
  </style>
  <g id='bars'>

Why It Matters: CSS classes enable efficient styling without inline attributes on every bar element.

7. Dynamic Bar Generation

VAR SVGBars = 
    CONCATENATEX (
        WithMetrics,
        VAR CurrentValue = [Value]
        VAR CurrentHighLoFlag = [HiLoFlag]
        VAR CurrentRow = [RowNumber]
        VAR IsNegative = CurrentValue < 0
        
        VAR AbsValue = ABS(CurrentValue)
        VAR ProportionalHeight = 
            IF(RangeVal = 0, 
                MinBarHeightPercent * ChartHeight,
                (AbsValue / RangeVal) * ChartHeight
            )
        VAR BarHeight = MAX(ProportionalHeight, MinBarHeightPercent * ChartHeight)
        
        VAR XPos = StartX + ( CurrentRow - 1 ) * TotalBarWidth
        
        VAR YPos = 
            IF(IsNegative,
                ZeroLineY,
                ZeroLineY - BarHeight
            )
        
        RETURN 
            "<rect x='" & XPos & 
            "' y='" & YPos & 
            "' width='" & BarWidth & 
            "' height='" & BarHeight & 
            "' class='"&CurrentHighLoFlag&"' rx='2'/>",
        "",
        'Date Table'[Year-Month], DESC
    )

Position Logic Explained:

For Positive Values (+100 with max 200):

  1. ProportionalHeight = (100 / 200) Ă— 240 = 120px
  2. BarHeight = MAX(120, 12) = 120px
  3. XPos = 20 + (RowNumber - 1) × TotalBarWidth (e.g., 20, 66, 112…)
  4. YPos = 270 - 120 = 150px (starts at top of bar)
  5. Result: Bar drawn from y=150 to y=270 (grows upward from baseline)

For Negative Values (-50 with min -100):

  1. AbsValue = 50
  2. ProportionalHeight = (50 / 200) Ă— 240 = 60px
  3. BarHeight = MAX(60, 12) = 60px
  4. YPos = 270px (starts at zero line)
  5. Result: Bar drawn from y=270 to y=330 (grows downward from baseline)

Why It Matters: Understanding SVG coordinate system (Y increases downward) is key to proper bar positioning.

8. SVG Completion and Data URI

VAR SVGFooter = 
    "</g><line x1='" & StartX & 
    "' y1='" & ZeroLineY & 
    "' x2='" & EndX & 
    "' y2='" & ZeroLineY & 
    "' stroke='#e5e7eb' stroke-width='2'/></svg>"

VAR FinalSVG = SVGHeader & SVGBars & SVGFooter

RETURN 
    "data:image/svg+xml;utf8," & FinalSVG

What This Does:

  • Closes the bars group (</g>)
  • Adds a horizontal baseline line at ZeroLineY position
  • Closes the SVG element (</svg>)
  • Concatenates all parts: header + bars + footer
  • Prefixes with data URI scheme for direct embedding

Why It Matters: The data URI format allows direct embedding in HTML visuals without external file dependencies.

9. Performance Considerations

Single CONCATENATEX Call:

  • Generates all bars in one iteration over the data
  • More efficient than multiple iterator functions
  • DAX engine optimizes string concatenation operations

Pre-calculated Constants:

  • Variables like SVGWidth, ChartHeight, TotalBarWidth are calculated once
  • Reused multiple times without redundant computation
  • Reduces processing overhead in the iterator

Efficient Normalization:

  • Range value calculated once: RangeVal = MaxVal - MinVal
  • Used repeatedly in bar height calculations: (AbsValue / RangeVal) * ChartHeight
  • Avoids recalculating min/max for each bar

Customization Options

Modifying Visual Appearance and Behavior

Change Colors

Edit the GoodNatureStyle variable:

var GoodNatureStyle = SWITCH(GoodNature,
    1, ".default { fill: #9ca3af; }
        .good { fill: #0066CC; }      // Change to blue
        .bad  { fill: #FF6600; }",    // Change to orange
    ...
)

Adjust Spacing

VAR BarSpacingRatio = 0.3  // Increase gap between bars

Change Canvas Size

VAR SVGWidth = 800   // Wider chart
VAR SVGHeight = 400  // Taller chart

Adjust Chart Area

VAR ChartHeight = SVGHeight * 0.9  // Use 90% of height
VAR ChartTop = SVGHeight * 0.05    // 5% top padding

References

Comments

Share your take or ask a question below.