Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Join us for an expert-led overview of the tools and concepts you'll need to become a Certified Power BI Data Analyst and pass exam PL-300. Register now.

Reply
SureshA
Helper I
Helper I

Velocity chart similar to Azure Devops

I am trying to build a velocity chart similar to Azure Devops to depict for each sprint

1. Planned

2. Completed 

3. Removed

 

I have used ado analytic views with history 

I am unable to get the planned story points in powerbi to match with the velocity chart of ado

Ado calculate differently for the planned story points based on snapshot view. How to replicate the planned story points 

 

Completed and removed are straight forward based on work item state at the end of spirint

21 REPLIES 21
SureshAnanth
Frequent Visitor

@v-hjannapu i have tried the dax it doesnt match with ADO velocity chart

DataNinja777
Super User
Super User

Hi @SureshA ,

 

To replicate the Azure DevOps velocity chart in Power BI, particularly the "Planned" story points, you must align with the platform's snapshot-based calculation. "Planned" work in Azure DevOps represents all work items assigned to a sprint before its official start date. To capture this, connect Power BI to an Azure DevOps Analytics view configured with daily historical granularity. This provides a daily snapshot of work item states, which is essential for accurate historical reporting.

For calculating completed work, you can sum the effort for items in a terminal state at the end of the sprint.

Completed Story Points = 
CALCULATE(
    SUM('YourTableName'[Story Points]),
    'YourTableName'[State] IN {"Done", "Closed"}
)

Removed work includes items that were part of the sprint at some point but were moved to a different iteration before the sprint ended. Accurately tracking this often involves analyzing changes to the Iteration Path field over the sprint's duration.

The crucial part is calculating the "Planned" story points, which requires querying the historical data for the state of the backlog on the sprint's start date. Assuming you have a WorkItemBoardSnapshot table from your Analytics View and a related 'Sprints' table with start dates, the DAX measure would isolate the work items assigned to the specific sprint on that initial day.

Planned Story Points =
VAR CurrentSprintStartDate = MIN('Sprints'[Start Date])
VAR CurrentSprintPath = SELECTEDVALUE('Sprints'[Iteration Path])
RETURN
CALCULATE(
    SUM('WorkItemBoardSnapshot'[Story Points]),
    FILTER(
        ALL('WorkItemBoardSnapshot'),
        'WorkItemBoardSnapshot'[Date] = CurrentSprintStartDate &&
        'WorkItemBoardSnapshot'[Iteration Path] = CurrentSprintPath
    )
)

This approach ensures your Power BI report mirrors the Azure DevOps methodology by summing the story points of work items that were planned for the sprint at the moment it began, providing an accurate baseline for your velocity calculation. These measures can then be used in a stacked column chart with the sprint on the axis to visualize the planned, completed, and removed work over time.

 

Best regards,

Thanks 

I have the following fields with all history from ado analytics view filtered for my required squads

1. Work item type

2. Iteration path

3. Interation start date

4.. Iteration end date

5. Created date

6. Changed Date

7. Story points

 

In your dax measure for planned story points, you have referred 

<<WorkItemBoardSnapshot table from your Analytics View and a related 'Sprints' table with start dates>>

  • I have only one table which has history along with the above listed fields containing sprint start date i.e iteration start datehow do I use this in the Dax
  • secondly ado also considers story points that got planned during sprint progress. How do I sum up these with the created and change date

Hi @SureshA,
Thank you for your followup.

It shows how to load your data, create DAX measures using your existing columns like Iteration Start Date, Changed Date, Created Date, and State, and finally build a stacked column chart.

I tested it with my sample data, and it worked fine. Please find the attached screenshot and Pbix for your reference.

vhjannapu_0-1750133146491.png

If I misunderstand your needs or you still have problems on it, please feel free to let us know.  

If the response has addressed your query, please Accept it as a solution and give a 'Kudos' so other members can easily find it

Best Regards,
Harshitha.




Thanks for your revert. Pls can you provide the dax I can't download the pbix in my official env

Hi @SureshA,
Thank you for getting back to me.
I will share the DAX formula directly here so you can implement it in your report:

Added During Sprint = 
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
CALCULATE(
    SUM('Planned Story Points'[Story Points]),
    FILTER(
        'Planned Story Points',
        'Planned Story Points'[Iteration Path] = SprintPath &&
        'Planned Story Points'[Changed Date] > SprintStart &&
        'Planned Story Points'[Changed Date] <= SprintEnd
    )
)

 

Completed Story Points = 
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
CALCULATE(
    SUM('Planned Story Points'[Story Points]),
    FILTER(
        'Planned Story Points',
        'Planned Story Points'[Iteration Path] = SprintPath &&
        'Planned Story Points'[State] IN { "Done", "Closed" }
    )
)

 

Planned Story Points = 
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
CALCULATE(
    SUM('Planned Story Points'[Story Points]),
    FILTER(
        'Planned Story Points',
        'Planned Story Points'[Iteration Path] = SprintPath &&
        'Planned Story Points'[Changed Date] <= SprintStart
    )
)


If you face any challenges applying this, feel free to share more details, and I will be happy to guide you step by step. 
Best regards,
Harshitha.
Microsoft Fabric Community Support.

Hi @v-hjannapu thanks for sharing dax formulas. I have used in my data set to depict then them in stack column chart.

  • I am able to get more story points, since the computation is done on all rows. I have used history in my ado analytics view hence my dataset has multiple rows for each work item. I need to get the latest update done on the work item to compute planned story points

Hi @SureshA,
Thanks  for getting back and explaining clearly.
This formula looks at each work item, takes only the last update just before the sprint started, and adds up the story points from that  so that the same work item is not counted more than once.
updated DAX you can use:

Planned Story Points =
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])

RETURN
CALCULATE(
    SUMX(
        FILTER(
            ADDCOLUMNS(
                SUMMARIZE(
                    'Planned Story Points',
                    'Planned Story Points'[Work Item ID],
                    "LatestChangeBeforeSprint",
                    CALCULATE(
                        MAX('Planned Story Points'[Changed Date]),
                        'Planned Story Points'[Changed Date] <= SprintStart
                    )
                ),
                "StoryPoints",
                CALCULATE(
                    MAX('Planned Story Points'[Story Points]),
                    'Planned Story Points'[Changed Date] <= SprintStart
                )
            ),
            NOT(ISBLANK([StoryPoints]))
        ),
        [StoryPoints]
    ),
    'Planned Story Points'[Iteration Path] = SprintPath
)

 

If this helped, please mark the answer as solution and give a Kudos so it helps others too .

Best regards,
Harshitha.
Microsoft Fabric Community Support.

@v-hjannapu Thanks. What about the below edge cases

1. New stories with story points that gets added after sprint start

2. Existing stories that gets modified after sprint start

In summary the dax for planned story points should exactly replicate the velocity chart that is in built as a gadget in azure devops

Hi @SureshA,

To make sure the Planned Story Points match exactly how Azure DevOps calculates them, we need to take care of two important things. First, if any new user stories were added after the sprint started, we should not count them in the planned total, because they were not part of the original sprint plan. Second, if existing stories had their story points changed after the sprint began, we should ignore those updates and only consider the story points value as it was before the sprint started. 
To handle both these cases, I have updated the DAX for Planned Story Points.

Planned Story Points =
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])

RETURN
SUMX(
    FILTER(
        ADDCOLUMNS(
            SUMMARIZE(
                'Planned Story Points',
                'Planned Story Points'[Work Item ID],
                "LatestBeforeSprint",
                CALCULATE(
                    MAX('Planned Story Points'[Changed Date]),
                    'Planned Story Points'[Changed Date] <= SprintStart &&
                    'Planned Story Points'[Iteration Path] = SprintPath
                )
            ),
            "StoryPointsAtSprintStart",
            CALCULATE(
                MAX('Planned Story Points'[Story Points]),
                'Planned Story Points'[Changed Date] <= SprintStart &&
                'Planned Story Points'[Iteration Path] = SprintPath
            ),
            "CreatedDate",
            CALCULATE(
                MIN('Planned Story Points'[Created Date])
            )
        ),
        [CreatedDate] <= SprintStart &&
        NOT(ISBLANK([StoryPointsAtSprintStart]))
    ),
    [StoryPointsAtSprintStart]
)


Regards,
Community support Team.

Thanks Let me implement and see if this matches ado velocity chart for planned story points 

Hi @SureshA,
Thank you so much for your effort and for keeping us informed.  If you face any issues after the update, we are here to help you.

@v-hjannapu I have used the planned story points DAX, I could see the no of story points have come down. I believe the DAX is working to remove duplicates as the data set has history for each sprint. However there is still difference when compared to velocity chart in ADO

Also,  I would require the DAX for completed story points and removed story points after removing the duplicates and considering the latest state before or on the sprint end date

 

Based on ADO  View and configure team velocity - Azure DevOps | Microsoft Learn

I could see the below calculation 

a) Planned = Work items assigned to a sprint before it starts. If reassigned after the sprint begins, they remain Planned in the original sprint and appear as Late or Incomplete in the new sprint.

b) Completed =Work items assigned to the sprint and completed before the end of the sprint.

 

Trust the DAX will reflect this in PowerBI to match ADO velocity considering the analytic views are sourced using history of work items

 

How to verify the DAX output with ADO velocity based on the source that is pulled in powerbi which has history

 

@v-hjannapu hope you had chance to check

Hi @SureshA ,
Thank you for your response. As you mentioned, since your dataset includes history rows, it is important to select only the latest values before the sprint start or end to align with the Azure DevOps velocity chart.

For Planned, count story points from the final snapshot taken before the sprint start, excluding any items created or updated afterwards.
For Completed, use story points for items that were Done or closed on or before the sprint end, referencing their most recent state prior to sprint end.
For Removed, include work items that were initially in the sprint but moved out before the sprint concluded.

I have updated the DAX formulas for Completed and Removed story points to eliminate duplicates and reflect this approach. You can now use these formulas directly in your model.

DAX for Completed Story Points:

Completed Story Points =
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])

RETURN
SUMX(
    FILTER(
        ADDCOLUMNS(
            SUMMARIZE(
                'Planned Story Points',
                'Planned Story Points'[Work Item ID],
                "LatestChangeBeforeEnd",
                CALCULATE(
                    MAX('Planned Story Points'[Changed Date]),
                    'Planned Story Points'[Changed Date] <= SprintEnd &&
                    'Planned Story Points'[Iteration Path] = SprintPath
                )
            ),
            "StoryPointsIfCompleted",
            CALCULATE(
                MAX('Planned Story Points'[Story Points]),
                'Planned Story Points'[Changed Date] <= SprintEnd &&
                'Planned Story Points'[Iteration Path] = SprintPath &&
                'Planned Story Points'[State] IN { "Done", "Closed" }
            )
        ),
        NOT(ISBLANK([StoryPointsIfCompleted]))
    ),
    [StoryPointsIfCompleted]
)

DAX for Removed Story Points:

Removed Story Points =
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])

RETURN
SUMX(
    FILTER(
        ADDCOLUMNS(
            SUMMARIZE(
                'Planned Story Points',
                'Planned Story Points'[Work Item ID],
                "RemovedDate",
                CALCULATE(
                    MAX('Planned Story Points'[Changed Date]),
                    'Planned Story Points'[Changed Date] <= SprintEnd &&
                    'Planned Story Points'[Iteration Path] <> SprintPath
                )
            ),
            "StoryPointsRemoved",
            CALCULATE(
                MAX('Planned Story Points'[Story Points]),
                'Planned Story Points'[Changed Date] <= SprintEnd
            )
        ),
        NOT(ISBLANK([StoryPointsRemoved]))
    ),
    [StoryPointsRemoved]
)

 

If this solution worked for you, kindly mark it as Accept as Solution and feel free to give a Kudos, it would be much appreciated.
Best Regards,
Harshitha.

@v-hjannapu Thanks I am able to get the completed story points matching with ADO velocity chart removing the duplicate. Further this is getting computed only for 1 sprint for subsequent sprints i dont see the value displayed in the chart

However, the planned story points is not matching i have compared

for past sprint 215 in DAX vs ADO velcity chart shows 126 with a diff of 89

For next sprint the DAX 155 vs ADO 105 with a diff of 50

 

Further can you share the DAX to fix the planned story points and also to get the completed for subsequent sprints

Further it is good to depict Completed Late and Incomplete to replicate similar to ADO velocity chart

Kindly help if you can share the DAX for these considering history

@v-hjannapu Hope you have got some feedback

Hi @SureshAnanth ,
Thanks for followup 
Here the Dax you can try:

Planned Story Points =
VAR SprintStart = MIN('Planned Story Points'[Iteration Start Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
SUMX(
    ADDCOLUMNS(
        SUMMARIZE(
            'Planned Story Points',
            'Planned Story Points'[Work Item ID],
            "LatestBeforeSprint",
            CALCULATE(
                MAX('Planned Story Points'[Changed Date]),
                'Planned Story Points'[Changed Date] <= SprintStart &&
                'Planned Story Points'[Iteration Path] = SprintPath
            )
        ),
        "StoryPointsAtStart",
        CALCULATE(
            MAX('Planned Story Points'[Story Points]),
            'Planned Story Points'[Changed Date] = [LatestBeforeSprint] &&
            'Planned Story Points'[Iteration Path] = SprintPath
        )
    ),
    [StoryPointsAtStart]
)

 

Completed Story Points =
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
SUMX(
    ADDCOLUMNS(
        SUMMARIZE(
            'Planned Story Points',
            'Planned Story Points'[Work Item ID],
            "LatestBeforeEnd",
            CALCULATE(
                MAX('Planned Story Points'[Changed Date]),
                'Planned Story Points'[Changed Date] <= SprintEnd &&
                'Planned Story Points'[Iteration Path] = SprintPath
            )
        ),
        "StoryPointsIfDone",
        CALCULATE(
            MAX('Planned Story Points'[Story Points]),
            'Planned Story Points'[Changed Date] = [LatestBeforeEnd] &&
            'Planned Story Points'[State] IN { "Done", "Closed" }
        )
    ),
    [StoryPointsIfDone]
)

 

Completed Late Story Points =
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
SUMX(
    FILTER(
        'Planned Story Points',
        'Planned Story Points'[Iteration Path] = SprintPath &&
        'Planned Story Points'[State] IN { "Done", "Closed" } &&
        'Planned Story Points'[Changed Date] > SprintEnd
    ),
    'Planned Story Points'[Story Points]
)

 

Incomplete Story Points =
VAR SprintEnd = MIN('Planned Story Points'[Iteration End Date])
VAR SprintPath = SELECTEDVALUE('Planned Story Points'[Iteration Path])
RETURN
SUMX(
    ADDCOLUMNS(
        SUMMARIZE(
            'Planned Story Points',
            'Planned Story Points'[Work Item ID],
            "LatestBeforeEnd",
            CALCULATE(
                MAX('Planned Story Points'[Changed Date]),
                'Planned Story Points'[Changed Date] <= SprintEnd &&
                'Planned Story Points'[Iteration Path] = SprintPath
            )
        ),
        "StoryPointsIfIncomplete",
        CALCULATE(
            MAX('Planned Story Points'[Story Points]),
            'Planned Story Points'[Changed Date] = [LatestBeforeEnd] &&
            NOT('Planned Story Points'[State] IN { "Done", "Closed" })
        )
    ),
    [StoryPointsIfIncomplete]
)


After trying this if you want any help let me know I will be happy to support you further if needed.
Please Accept as solution if this meets your needs and a Kudos would be appreciated.

Reagrds,
Harshitha.

 

Hi @SureshA,

Just wanted to check if you had the opportunity to review the suggestion provided?

If the response has addressed your query, please Accept it as a solution and give a 'Kudos' so other members can easily find it.

 

Thank You.

Harshitha.

Hi @SureshA,

 

I wanted to check if you had the opportunity to review the information provided. Please feel free to contact us if you have any further questions. If my response has addressed your query, please Accept it as a solution so that other community members can find it easily.


Thank you.

Helpful resources

Announcements
Join our Fabric User Panel

Join our Fabric User Panel

This is your chance to engage directly with the engineering team behind Fabric and Power BI. Share your experiences and shape the future.

June 2025 Power BI Update Carousel

Power BI Monthly Update - June 2025

Check out the June 2025 Power BI update to learn about new features.

June 2025 community update carousel

Fabric Community Update - June 2025

Find out what's new and trending in the Fabric community.