cancel
Showing results for 
Search instead for 
Did you mean: 
Reply
Resolver I
Resolver I

Sliding legend

Hi everyone,

I saw sliding legend in one of the visuals, which are built in Power BI desktop.

How this feature can be recreated in custom visual?

Regards,

Yerkhan

18 REPLIES 18
Super User I
Super User I

Hi @yerkhan_sapiyev,

Do you have an example of where you saw it, or what you are trying to achieve? Is it a particular custom visual? If so, which one? If we can get an idea of what you're aiming for then it might be possible to provide more targeted help.

Thanks,

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




Hi @dm-p ,

 

I meant this type of legend, as in this visual:

Capture.PNG

Legend in this visual slides to right, in case of large legend.

I wanted to use this type of legend in my custom visual.

 

Regards,

Yerkhan

Hi @yerkhan_sapiyev, and thanks for clarifying 🙂

Assuming you're using createLegend in powerbi-visuals-utils-chartutils to manage the creation of your legend, you need to set the isScrollable property to true.

If your legend exceeds the area the function reserves for it in the visual viewport, then you'll get the button to scroll to see the additional items.

Good luck!

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




@dm-p 

 

Could you please provide code of visual, where this legend is implemented? 

I have some difficulties with adding it by github guide.

 

Regards,

Yerkhan

 

I had a look at this, and the documentation of this library is quite out of date, and there's a number of quirks to getting it to work properly, which didn't help.

It took me about 3 hours to re-learn for the new v3 SDK and work out how to avoid breaking it, so I can understand why you're having trouble. I haven't looked at it in quite some time and it definitely used to work better. It's going to be fun to migrate some of my stuff to this version... Smiley Frustrated   I hear the team are working on improving the documentation... I really hope so.

To get this to work, I just created a new visual using pbiviz new and then removed all unnecessary code, but I'll paste my visual.ts below for you.

Things to bear in mind:

  • Chartutils needs to be installed as a dependency:
    npm i powerbi-visuals-utils-chartutils
  • Critical references are the LegendData and  LegendDataPoint  interfaces, in terms of getting your data in there.
  • Despire what the definition says, you need the following properties setting for each LegendDataPoint to ensure it renders without erroring:
    • label
    • color
    • identity - this requires building a Selection ID and doesn't seem to work if you leave it null
    • selected
    • markerShape
  • Invoking the drawLegend method will add a SVG element to the DOM. You will need to manage the sizing changes to your plot area after this is drawn.
    • This can be done with the getMargins / getOrientation methods and adjusting your other elements accordingly.
    • You can refer to the source of the violin plot for some examples of how this has been done, although it uses the older SDK so isn't subject to some of the stuff going on here, but I'd suggest keeping to the 1.2.0 code for reference; 1.3.0 gets a bit weird as it's manipulating the legend post-render.
  • There's no error handling in the update method, so you'll need to ensure that you have a something in the Category field.
  • I've added comments at the appropriare location in the imports to show where I've started to add stuff in.

Here's how it looks for a simple data set:

image.png

And, dragging the viewport across will cause the chart to scroll, e.g.:

image.png

Here's the code from my visual.ts:

"use strict";

import "core-js/stable";
import "./../style/visual.less";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import { VisualSettings } from "./settings";

/** Added to support legend addition */
    import IVisualHost = powerbi.extensibility.visual.IVisualHost;
    import { legendInterfaces, legend } from 'powerbi-visuals-utils-chartutils';
    import createLegend = legend.createLegend;
    import ILegend = legendInterfaces.ILegend;
    import LegendDataPoint = legendInterfaces.LegendDataPoint;
    import LegendPosition = legendInterfaces.LegendPosition;
    import MarkerShape = legendInterfaces.MarkerShape;

export class Visual implements IVisual {
    private target: HTMLElement;
    private settings: VisualSettings;
    private legend: ILegend;
    private host: IVisualHost;

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.target = options.element;

        /** Assign visual host */
            this.host = options.host;

        /** Reserve element for legend */
            this.legend = createLegend(
                this.target,
                false,
                null,
                true, /* scrollable */
                LegendPosition.Top
            );
    }

    public update(options: VisualUpdateOptions) {
        this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
        console.log('Visual update', options);

        /** THIS ASSUMES THAT YOUR DATA VIEW WORKS, SO THERE'S NO ERROR HANDLING. YOU WILL AT LEAST 
         *  NEED A VALUE IN THE CATEGORY DATA ROLE TO CONTINUE */
            let categoryRole = options.dataViews[0].categorical.categories[0];

        /** We're now going to create an array of LegendDataPoint objects to feed into our drawLegend
         *  method below.
         *  We're arbitrarily assigning a colour from the host's palette (theme) as it's required. You 
         *  might want to sub this out with your own from your view model */
            let myLegendDataPoints: LegendDataPoint[] = categoryRole.values.map( 
                (c, i) => (
                    {
                        label: <string>c,
                        color: this.host.colorPalette.getColor(<string>c).value,
                        identity: this.host.createSelectionIdBuilder()
                            .withCategory(categoryRole, i)
                            .createSelectionId(),
                        selected: false,
                        markerShape: MarkerShape.circle
                    }
                )
            );

        /** This actually draws the legend; note that although font settings aren't required, it will look 
         *  'wrong' if they're not supplied */
            this.legend.drawLegend(
                {
                    title: 'Test Legend',
                    fontSize: 10,
                    dataPoints: myLegendDataPoints
                },
                options.viewport
            );
    }

    private static parseSettings(dataView: DataView): VisualSettings {
        return VisualSettings.parse(dataView) as VisualSettings;
    }

    /**
     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
     * objects and properties you want to expose to the users in the property pane.
     *
     */
    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
        return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
    }
}

Hopefully this helps in some way.

Good luck,

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




@dm-p 

 

Thank you, Daniel, for such detailed explanation.

However, I still get error. Probably, because I'm using pbiviz version 2.3.0.

Main issue, I installed chartutils, but I got error at import. 

Will it work without updating pbiviz version?

Regards,

Yerkhan

Not in this form, as it's written for v3 of the SDK. I perhaps made the wrong assumption, as the majority of questions are pertaining to that, so apologies for not checking this first.

The source code I linked for the violin plot uses 2.5, which will also work for 2.3 if you're using that. You'll need to downgrade chartutils as well as the latest version has been updated to support v3 of the SDK.

I can re-write the above but will need to do it tomorrow as I'm away from my desk until then and will need to reinstall 2.x to confirm the solution for you.




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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




Hi @yerkhan_sapiyev,

I've re-done the above in 2.3 for you. The process is closer to the install guide but there are still some issues in getting it to work. Here's the run-down:

Make sure that if you're running pbiviz start, then you've stopped it prior to installation as you'll get inconsistent results after package installation.

Package Installation

I had to downgrade until I could find one that will work. 1.7.0 will run, but colour seemed to stop working after 1.5, so run the following command to import:

npm i powerbi-visuals-utils-chartutils@1.5.1

The installed packages have a dependency on d3, which it does install, but make sure that the typings are installed for it:

npm i @types/d3@3.5 --save-dev

To confirm, after instantiating a new visual and installing packages, my package.json looks like this, so you could just copy/paste and then run npm i to sync:

{
  "name": "visual",
  "dependencies": {
    "powerbi-visuals-utils-chartutils": "^1.5.1",
    "powerbi-visuals-utils-dataviewutils": "1.2.0"
  },
  "devDependencies": {
    "@types/d3": "^3.5.42"
  }
}

tsconfig.json

Add the following to the files key in tsconfig.json before settings.ts:

    "node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts",

The files key looks like this in mine:

  "files": [
    ".api/v2.5.0/PowerBI-visuals.d.ts",
    "node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
    "node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts",
    "src/settings.ts",
    "src/visual.ts"
  ]

pbiviz.json

Add the following to the externalJS key in pbiviz.json:

    "node_modules/d3/d3.min.js",
    "node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-chartutils/lib/index.js"

The externalJS key looks like this in mine:

  "externalJS": [
    "node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
    "node_modules/d3/d3.min.js",
    "node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
    "node_modules/powerbi-visuals-utils-chartutils/lib/index.js"
  ]

visual.less

It shouldn't matter for this scenario, but to mirror the installation guide, make sure the following are present at the top of visual.less:

@import (less) "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.css";
@import (less) "node_modules/powerbi-visuals-utils-chartutils/lib/index.css";

visual.ts

The solution in the previous post is largely unchanged, except for:

  • The way dependencies are imported, e.g.:
        import createLegend = powerbi.extensibility.utils.chart.legend.createLegend;
        import ILegend = powerbi.extensibility.utils.chart.legend.ILegend;
        import Legend = powerbi.extensibility.utils.chart.legend;
        import LegendData = powerbi.extensibility.utils.chart.legend.LegendData;
        import LegendIcon = powerbi.extensibility.utils.chart.legend.LegendIcon;
        import LegendPosition = powerbi.extensibility.utils.chart.legend.LegendPosition;
        import LegendDataPoint = powerbi.extensibility.utils.chart.legend.LegendDataPoint;
  • LegendDataPoint doesn't use markerShape as a property and this is icon in this version, so double-check this part of the code if you're amending your local version (the compiler will error if not correct), e.g.:
    {
        label: <string>c,
        color: this.host.colorPalette.getColor(<string>c).value,
        identity: this.host.createSelectionIdBuilder()
            .withCategory(categoryRole, i)
            .createSelectionId(),
        selected: false,
        icon: LegendIcon.Circle
    }

Again just to be sure, here's the full visual.ts:

/** powerbi.extensibility.utils.chart.legend */
    import createLegend = powerbi.extensibility.utils.chart.legend.createLegend;
    import ILegend = powerbi.extensibility.utils.chart.legend.ILegend;
    import Legend = powerbi.extensibility.utils.chart.legend;
    import LegendData = powerbi.extensibility.utils.chart.legend.LegendData;
    import LegendIcon = powerbi.extensibility.utils.chart.legend.LegendIcon;
    import LegendPosition = powerbi.extensibility.utils.chart.legend.LegendPosition;
    import LegendDataPoint = powerbi.extensibility.utils.chart.legend.LegendDataPoint;

module powerbi.extensibility.visual {
    "use strict";
    export class Visual implements IVisual {
        private target: HTMLElement;
        private settings: VisualSettings;
        private legend: ILegend;
        private host: IVisualHost;

        constructor(options: VisualConstructorOptions) {
            console.log('Visual constructor', options);
            this.target = options.element;
    
            /** Assign visual host */
                this.host = options.host;
    
            /** Reserve element for legend */
                this.legend = createLegend(
                    this.target,
                    false,
                    null,
                    true, /* scrollable */
                    LegendPosition.Top
                );
        }

        public update(options: VisualUpdateOptions) {
            this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
            console.log('Visual update', options);
    
            /** THIS ASSUMES THAT YOUR DATA VIEW WORKS, SO THERE'S NO ERROR HANDLING. YOU WILL AT LEAST 
             *  NEED A VALUE IN THE CATEGORY DATA ROLE TO CONTINUE */
                let categoryRole = options.dataViews[0].categorical.categories[0];
    
            /** We're now going to create an array of LegendDataPoint objects to feed into our drawLegend
             *  method below.
             *  We're arbitrarily assigning a colour from the host's palette (theme) as it's required. You 
             *  might want to sub this out with your own from your view model */
                let myLegendDataPoints: LegendDataPoint[] = categoryRole.values.map( 
                    (c, i) => (
                        {
                            label: <string>c,
                            color: this.host.colorPalette.getColor(<string>c).value,
                            identity: this.host.createSelectionIdBuilder()
                                .withCategory(categoryRole, i)
                                .createSelectionId(),
                            selected: false,
                            icon: LegendIcon.Circle
                        }
                    )
                );
    
            /** This actually draws the legend; note that although font settings aren't required, it will look 
             *  'wrong' if they're not supplied */
                this.legend.drawLegend(
                    {
                        title: 'Test Legend',
                        fontSize: 10,
                        dataPoints: myLegendDataPoints
                    },
                    options.viewport
                );
        }

        private static parseSettings(dataView: DataView): VisualSettings {
            return VisualSettings.parse(dataView) as VisualSettings;
        }

        /** 
         * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the 
         * objects and properties you want to expose to the users in the property pane.
         * 
         */
        public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
            return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
        }
    }
}

At this point you can run pbiviz start and this will work as for the v3 example above.

Good luck!

Daniel

 





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




@dm-p 

 

Also, when I click on arrow in scrollable legend in bar chart, text color of legend changes. By default it was black. I changed it to white using visual.less. When I scroll legend, it changes again to black.

How it can be changed?

 

Regards,

Yerkhan

From the looks of things, there is something that happens with re-drawing the nav buttons when clicked that seems to ignore any styling or changes applied. I made some attempts in one of my visuals to do something similar in the past (to change the shape of the icons), but found that it was more trouble that it was worth and abandoned my efforts.

If you want to do this in a maintainable way, then I think you'll need to create an idea with the custom visuals team to add support. You can refer to this post in the Custom Visuals Development Discussion Forum for more details.


Regarding your other question, if you can't write DAX in-model, I would suggest requesting the owners of the OLAP cube add in such a measure for you - these tools are designed for such functionality and it is minimal work for them to manage and support. If the OLAP cube is SSAS, then SSAS Tabular uses DAX for its measures; SSAS Multidimensional can achieve the same via MDX. Solving in the semantic model is going to be the way to go.

Not to discourage learning how to develop custom visuals at all - I would personally love to have more people to collaborate with and learn from - but this is a very heavy approach to solving a problem that can be resolved upstream and is going to be several of hours' work from my side to document how such a solution could conceivably work (and much more from yours to develop). I'm a big subscriber to working smarter rather than harder. As it's outside the scope of the original question, I think it probably warrants its own thread if you specifically want to go through this.

However, if I were to do this, then I'd start by working with the standard categorical data mapping that comes with a new visual project, and set my view model hierarchy as measure[] > category[] > value[]. Once I've mapped my distinct measures and categories, as I iterate through the value array, I'd add to the previous value so that each subsequent value in the value array for each measure/category is a cumulative total.





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




@dm-p ,

 

Hi again,

How can I make sliding legend for visual with table dataview mapping? 

I got error at identity, using getSelectionIds.

Regards,

Yerkhan

@dm-p ,

 

Firstly, I tried adding cumulative value to OLAP cube, however, it didn't result in required field.

Therefore, I started building custom visual. I managed to overcome issue with legend text color and changing position of legend.

view model hierarchy as measure[] > category[] > value[]

Could explain this a bit more? 

 

Regards,

Yerkhan

@dm-p  Daniel,

 

Thanks for detailed answer! It was very helpful. 

I have some questions regarding it. I added it to my line chart, which has categorical dataview mapping, and can I pass to legend grouping of categorical data view? I tried using this:

let categoryRole = options.dataViews[0].categorical.values.grouped();

But it gave me error. 

Also, I don't want to use selection id, if I leave it empty, I get error. 

And finally, even though scrollable parameter is true, I can't scroll:

Capture.PNG

When I click on arrow, it doesn't work.

 

Regards,

Yerkhan

Hi @yerkhan_sapiyev,

I mentioned in my earlier post that you will need to have a selection ID for each entry, otherwise the legend utility will not work. Unfortunately for your case you cannot provide a null value here so will need to provide something valid. It's highly likely that this is causing your legend to break.

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




Hi @dm-p ,

 

Got it regarding selection id. How does regular line chart works? It has date as axis, category as grouping for data, and legend is built based on category. I wanted to recreate it, but with cumulative values.

Regards,

Yerkhan

Hi @yerkhan_sapiyev,

I'm not entirely sure I follow - you can manage cumulative measures with the core line chart and DAX. Is there a specific feature you're trying to manage in your custom visual?

Thanks,

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


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




@dm-p ,

 

I can't use DAX since I'm using live connection to OLAP cube. I wanted to recreate line chart, but make cumulative calculations inside of visual. However, by legend doesn't work, as I mentioned before.

 

Regards,

Yerkhan

@dm-p 

 

Thanks! Will try this approach.

 

Regards,

Yerkhan

Helpful resources

Announcements
secondImage

Congratulations!

We are excited to announce the Power BI Super Users!

Wave Release 2

Check out the updates in Power BI.

Overview of Power BI 2020 release wave 2!

Microsoft Ignite

Microsoft Ignite

Join digitally, March 2–4, 2021 to explore new tech that's ready to implement. Experience the keynote in mixed reality through AltspaceVR!

secondImage

The largest Power BI virtual conference

100+ sessions, 100+ speakers, Product managers, MVPs, and experts. All about Power BI. Attend online or watch the recordings.

Top Solution Authors
Top Kudoed Authors