Forum Discussion
Sliding legend
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... :smileyfrustrated: 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:
And, dragging the viewport across will cause the chart to scroll, e.g.:
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
- Anonymous7 years agoNot applicable
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
- dm-p7 years agoSuper UserNot 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.- dm-p7 years agoSuper User
Hi Anonymous,
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 [email protected]
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/[email protected] --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
- The way dependencies are imported, e.g.: