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

Get Fabric Certified for FREE during Fabric Data Days. Don't miss your chance! Request now

Reply
antalgu
Helper I
Helper I

Custom visual using two measures

Does anyone know how to use two measures in a custom visual? I've seen a thread called "Adding a Second Measure to Custom Visual" where they use two measures by categorising them and then using a chart, I'm trying to do a way more simple thing which is just using two measures in a plain card and the problem I'm seeing is that I don't quite understand how to do it without categorising it. When done with only one measure the code is pretty simple (it's the circle example), is there any way to mantain that simplicity when done with two?

The code with one measure:

  • Visual.ts:  (just the constructor and update part as I think the other parts are not relevant):
constructor(optionsVisualConstructorOptions) {
        this.svg = d3.select(options.element)
            .append('svg')
            .classed('circleCard'true);
        this.container = this.svg.append("g")
            .classed('container'true);
        this.circle = this.container.append("circle")
            .classed('circle'true);
        this.textValue = this.container.append("text")
            .classed("textValue"true);
        this.textLabel = this.container.append("text")
            .classed("textLabel"true);
    }

    public update(optionsVisualUpdateOptions) {
        let dataViewDataView = options.dataViews[0];
        let widthnumber = options.viewport.width;
        let heightnumber = options.viewport.height;
        this.svg.attr("width"width);
        this.svg.attr("height"height);
        let radiusnumber = Math.min(widthheight) / 2.2;
        this.circle
            .style("fill""white")
            .style("fill-opacity"0.5)
            .style("stroke""black")
            .style("stroke-width"2)
            .attr("r"radius)
            .attr("cx"width / 2)
            .attr("cy"height / 2);
        let fontSizeValuenumber = Math.min(widthheight) / 5;
        this.textValue
            .text(<string>dataView.single.value)
            .attr("x""50%")
            .attr("y""50%")
            .attr("dy""0.35em")
            .attr("text-anchor""middle")
            .style("font-size"fontSizeValue + "px");
        let fontSizeLabelnumber = fontSizeValue / 4;
        this.textLabel
            .text(dataView.metadata.columns[0].displayName)
            .attr("x""50%")
            .attr("y"height / 2)
            .attr("dy"fontSizeValue / 1.2)
            .attr("text-anchor""middle")
            .style("font-size"fontSizeLabel + "px");
    }
  • Capabilities.json:  

{

    "dataRoles": [
        {
            "displayName""Current measure",
            "name""measure",
            "kind""Measure"
        }
    ],
    "dataViewMappings": [
        {
            "conditions": [
                {
                    "measure": {
                        "max"1
                }
            ],
            "single": {
                "role""measure"
            }
        }
    ]
}
Isn't there a way to use two changing the single by categorized but without actually categorizing it?
1 ACCEPTED SOLUTION
dm-p
Super User
Super User

Hi @antalgu,

You can't maintain the simplicity of a single dataViewMapping for multiple measures or columns, as it introduces additional context into the generated DAX query that you will need to handle. single can only support one value in the visual query, and therefore you can only have one dataRole with a single measure in these cases.

Approach 1

The most 'straightforward' way to do this is using categorical and just binding the data roles to the values object (ignoring categories). You will then need to map your data from the appropriate data roles in the dataView.

Here's an example capabilities.json for this idea this using two dataRoles with a single measure in each:

 

{
    "dataRoles": [
        {
            "displayName": "Measure",
            "description": "Add your measure here to display its value in the card.",
            "name": "measure",
            "kind": "Measure"
        },
        {
            "displayName": "Tooltip",
            "description": "Additional measures to display in the tooltip.",
            "name": "tooltip",
            "kind": "Measure"
        }
    ],
    "objects": { },
    "dataViewMappings": [
        {
            "categorical": {
                "values": {
                    "select": [
                        {
                            "bind": {
                                "to": "measure"
                            }
                        },
                        {
                            "bind": {
                                "to": "tooltip"
                            }
                        }
                    ]
                }
            },
            "conditions": [
                { 
                    "measure": { 
                        "max": 1 
                    },
                    "tooltip": {
                        "max": 1
                    }
                }
            ]
        }
    ]
}

 

For the above example, because we know that there will only ever be a maximum of one value per dataRole, one approach to obtain the values from the dataView using known dataRoles would be as follows (in the visual's update function):

 

const 
    measureValue = options.dataViews[0].categorical.values[0]?.values[0],
    tooltipValue = options.dataViews[0].categorical.values[1]?.values[0];

 

The ? after each categorical.values array element prevents the visual from crashing if the element doesn't exist (as trying to access it's values property will cause an error).

A more resilient way (particularly if you might have multiple dataRoles and can't be 100% sure what youre users will be adding - is to assign results based on a find against your categorical.values array by specifying the dataRole's name value, e.g.:

 

const 
    measureValue = options.dataViews[0].categorical.values
        .find(
            c => c.source.roles.measure
        )?.values[0],
    tooltipValue = options.dataViews[0].categorical.values
        .find(
            c => c.source.roles.tooltip
        )?.values[0];

 

Approach 2

Another possibility is to use table. Here's what the capabilities.json would look like here:

 

{
    "dataRoles": [
        {
            "displayName": "Measure",
            "description": "Add your measure here to display its value in the card.",
            "name": "measure",
            "kind": "Measure"
        },
        {
            "displayName": "Tooltip",
            "description": "Additional measures to display in the tooltip.",
            "name": "tooltip",
            "kind": "Measure"
        }
    ],
    "objects": { },
    "dataViewMappings": [
        {
            "table": {
                "rows": {
                    "select": [
                        {
                            "bind": {
                                "to": "measure"
                            }
                        },
                        {
                            "bind": {
                                "to": "tooltip"
                            }
                        }
                    ]
                }
            },
            "conditions": [
                { 
                    "measure": { 
                        "max": 1 
                    },
                    "tooltip": {
                        "max": 1
                    }
                }
            ]
        }
    ]
}

 

The simple way of grabbing the values would change to this:

 

const
    measureValue = options.dataViews[0].table.rows[0][0],
    tooltipValue = options.dataViews[0].table.rows[0][1];

 

Again, this relies on all dataRoles being populated. Otherwise, you'll need to mandate a min of 1 in the conditions or write code when investigating the dataView to check that a dataRole is present in the dataView and  handle it accordingly.


You should be able to adapt either of these patterns, but if you're still struggling, please post a follow-up with the code you've tried and what you're trying to do (expected output would also be helpful) and we'll see if we can get yout here.

Good luck!

Daniel

 




Did I answer your question? Mark my post as a solution!

Proud to be a Super User!


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




View solution in original post

2 REPLIES 2
dm-p
Super User
Super User

Hi @antalgu,

You can't maintain the simplicity of a single dataViewMapping for multiple measures or columns, as it introduces additional context into the generated DAX query that you will need to handle. single can only support one value in the visual query, and therefore you can only have one dataRole with a single measure in these cases.

Approach 1

The most 'straightforward' way to do this is using categorical and just binding the data roles to the values object (ignoring categories). You will then need to map your data from the appropriate data roles in the dataView.

Here's an example capabilities.json for this idea this using two dataRoles with a single measure in each:

 

{
    "dataRoles": [
        {
            "displayName": "Measure",
            "description": "Add your measure here to display its value in the card.",
            "name": "measure",
            "kind": "Measure"
        },
        {
            "displayName": "Tooltip",
            "description": "Additional measures to display in the tooltip.",
            "name": "tooltip",
            "kind": "Measure"
        }
    ],
    "objects": { },
    "dataViewMappings": [
        {
            "categorical": {
                "values": {
                    "select": [
                        {
                            "bind": {
                                "to": "measure"
                            }
                        },
                        {
                            "bind": {
                                "to": "tooltip"
                            }
                        }
                    ]
                }
            },
            "conditions": [
                { 
                    "measure": { 
                        "max": 1 
                    },
                    "tooltip": {
                        "max": 1
                    }
                }
            ]
        }
    ]
}

 

For the above example, because we know that there will only ever be a maximum of one value per dataRole, one approach to obtain the values from the dataView using known dataRoles would be as follows (in the visual's update function):

 

const 
    measureValue = options.dataViews[0].categorical.values[0]?.values[0],
    tooltipValue = options.dataViews[0].categorical.values[1]?.values[0];

 

The ? after each categorical.values array element prevents the visual from crashing if the element doesn't exist (as trying to access it's values property will cause an error).

A more resilient way (particularly if you might have multiple dataRoles and can't be 100% sure what youre users will be adding - is to assign results based on a find against your categorical.values array by specifying the dataRole's name value, e.g.:

 

const 
    measureValue = options.dataViews[0].categorical.values
        .find(
            c => c.source.roles.measure
        )?.values[0],
    tooltipValue = options.dataViews[0].categorical.values
        .find(
            c => c.source.roles.tooltip
        )?.values[0];

 

Approach 2

Another possibility is to use table. Here's what the capabilities.json would look like here:

 

{
    "dataRoles": [
        {
            "displayName": "Measure",
            "description": "Add your measure here to display its value in the card.",
            "name": "measure",
            "kind": "Measure"
        },
        {
            "displayName": "Tooltip",
            "description": "Additional measures to display in the tooltip.",
            "name": "tooltip",
            "kind": "Measure"
        }
    ],
    "objects": { },
    "dataViewMappings": [
        {
            "table": {
                "rows": {
                    "select": [
                        {
                            "bind": {
                                "to": "measure"
                            }
                        },
                        {
                            "bind": {
                                "to": "tooltip"
                            }
                        }
                    ]
                }
            },
            "conditions": [
                { 
                    "measure": { 
                        "max": 1 
                    },
                    "tooltip": {
                        "max": 1
                    }
                }
            ]
        }
    ]
}

 

The simple way of grabbing the values would change to this:

 

const
    measureValue = options.dataViews[0].table.rows[0][0],
    tooltipValue = options.dataViews[0].table.rows[0][1];

 

Again, this relies on all dataRoles being populated. Otherwise, you'll need to mandate a min of 1 in the conditions or write code when investigating the dataView to check that a dataRole is present in the dataView and  handle it accordingly.


You should be able to adapt either of these patterns, but if you're still struggling, please post a follow-up with the code you've tried and what you're trying to do (expected output would also be helpful) and we'll see if we can get yout here.

Good luck!

Daniel

 




Did I answer your question? Mark my post as a solution!

Proud to be a Super User!


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




Hi @dm-p,

 

Thanks for the quick and precise response. Your explanations were great and the code is now working perfectly.

Helpful resources

Announcements
Fabric Data Days Carousel

Fabric Data Days

Advance your Data & AI career with 50 days of live learning, contests, hands-on challenges, study groups & certifications and more!

October Power BI Update Carousel

Power BI Monthly Update - October 2025

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

FabCon Atlanta 2026 carousel

FabCon Atlanta 2026

Join us at FabCon Atlanta, March 16-20, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.