Skip to content

Commit

Permalink
Adding OnObject Formatting (#125)
Browse files Browse the repository at this point in the history
* Add on object formatting support

* Update Bar chart to use formattingId

* Update barChartSettingsModel.ts

* Update capabilities.json

* Update package.json

* use onobjectutils package

* comments

* lint fixes and comments

* iupdate tutorials

* update onobject tutorial

---------

Co-authored-by: Shafeeq <[email protected]>
  • Loading branch information
shafeeqz and Shafeeq authored Mar 19, 2024
1 parent 4d39741 commit ac2dbaf
Show file tree
Hide file tree
Showing 21 changed files with 2,154 additions and 1,401 deletions.
52 changes: 42 additions & 10 deletions Tutorial/ColorPalette.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,48 @@ interface BarChartDataPoint {
`colorPalette` is a service that manages the colors used on your visual. An instance of it is available on `IVisualHost`.

## Assigning Color to Data Points
We defined `visualTransform` as a construct to convert `dataView` to a view model Bar Chart can use.
Since we iterate through the data points in `visualTransform` it is also the ideal place to assign colors.
We defined `createSelectorDataPoints` as a construct to convert options `dataView` to Bar Chart data points that will be used in visual view.
Since we iterate through the data points in `createSelectorDataPoints` it is also the ideal place to assign colors.

```typescript
let colorPalette: IColorPalette = host.colorPalette; // host: IVisualHost
for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {
barChartDataPoints.push({
category: category.values[i],
value: dataValue.values[i],
color: colorPalette.getColor(category.values[i]).value,
});

function createSelectorDataPoints(options: VisualUpdateOptions, host: IVisualHost): BarChartDataPoint[] {
let barChartDataPoints: BarChartDataPoint[] = []
const dataViews = options.dataViews;
if (!dataViews
|| !dataViews[0]
|| !dataViews[0].categorical
|| !dataViews[0].categorical.categories
|| !dataViews[0].categorical.categories[0].source
|| !dataViews[0].categorical.values
) {
return barChartDataPoints;
}

const categorical = dataViews[0].categorical;
const category = categorical.categories[0];
const dataValue = categorical.values[0];

const colorPalette: ISandboxExtendedColorPalette = host.colorPalette;
const strokeColor: string = getColumnStrokeColor(colorPalette);
const strokeWidth: number = getColumnStrokeWidth(colorPalette.isHighContrast);

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {
const color: string = getColumnColorByIndex(category, i, colorPalette);

const selectionId: ISelectionId = host.createSelectionIdBuilder()
.withCategory(category, i)
.createSelectionId();

barChartDataPoints.push({
color,
strokeColor,
strokeWidth,
selectionId,
value: dataValue.values[i],
category: `${category.values[i]}`,
});
}
return barChartDataPoints;
}
```
```
19 changes: 8 additions & 11 deletions Tutorial/ConditionalFormatting.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Adding conditional formatting to your Visual
[Conditional formatting](https://docs.microsoft.com/en-us/power-bi/visuals/service-tips-and-tricks-for-color-formatting#conditional-formatting-for-visualizations) of custom properties is supported by updating `VisualObjectInstance` object's properties as enumerated under `enumerateObjectInstances` method.

See [commit](https://github.com/microsoft/powerbi-visuals-api/commit/8fe88399c5ba82feeec4541ce5bf8e02a3ecd15a) for what was added at this step.
[Conditional formatting](https://docs.microsoft.com/en-us/power-bi/visuals/service-tips-and-tricks-for-color-formatting#conditional-formatting-for-visualizations) of custom formatting properties is supported by setting formatting property `instanceKind` in `getFormattingModel` method.
For more info on conditional formatting click [here](https://learn.microsoft.com/en-us/power-bi/developer/visuals/conditional-format?tabs=getFormattingModel)

Conditional formatting can only be applied to the following property types:
* Color
Expand All @@ -10,25 +9,23 @@ Conditional formatting can only be applied to the following property types:
* Web URL

## Add a conditional color formatting entry in the format pane
To add the conditional color formatting button in the format pane for the desired object, under the `enumerateObjectInstances` method, make the following change:
To add the conditional color formatting button in the format pane for the desired object, under the `getFormattingModel` method, make the following change:

Via `propertyInstanceKind` property of enumerated `VisualObjectInstance`, list all the properties that you'd like to have the conditional formatting entry applied to in the format pane.
Define `instanceKind` property of required formatting property `descriptor` with the appropriate value.
Use `VisualEnumerationInstanceKinds` enum to declare the type of the desired format (constant, rule or both).

```typescript
// List your conditional formatting properties
propertyInstanceKind: {
fill: VisualEnumerationInstanceKinds.ConstantOrRule
}
instanceKind: powerbi.VisualEnumerationInstanceKinds.ConstantOrRule
```
![](images/ConditionalFormattingEntry.png)

## Define how conditional formatting behaves
Using `createDataViewWildcardSelector` declared under `powerbi-visuals-utils-dataviewutils`, specify whether conditional formatting will be applied to instances, totals, or both. For more information, see [DataViewWildcard](https://docs.microsoft.com/en-us/power-bi/developer/visuals/utils-dataview#dataviewwildcard).

In `enumerateObjectInstances`, make the following changes to the objects you want to apply conditional formatting to:
In `BarChartFormattingSettingsModel`, make the following changes to the formatting properties you want to apply conditional formatting to:

* Replace the `VisualObjectInstance`'s `selector` value with a `dataViewWildcard.createDataViewWildcardSelector()` call. Specify the desired option from `DataViewWildcardMatchingOption` enum to define whether conditional formatting is applied to instances, totals, or both.
* Replace the formatting property `descriptor`'s `selector` value with a `dataViewWildcard.createDataViewWildcardSelector()` call. Specify the desired option from `DataViewWildcardMatchingOption` enum to define whether conditional formatting is applied to instances, totals, or both.

* Add the `altConstantValueSelector` property having the value previously defined for the `selector` property.

Expand All @@ -39,6 +36,6 @@ selector: dataViewWildcard.createDataViewWildcardSelector(dataViewWildcard.DataV
// Add this property with the value previously defined for the selector property
altConstantValueSelector: barDataPoint.selectionId.getSelector()
```
See [commit](https://github.com/Microsoft/PowerBI-visuals-sampleBarChart/commit/956923b641bb1eacb613bf55a91f77725bc42431) for how conditional formatting was applied to sample bar chart.
See [commit](https://github.com/Microsoft/PowerBI-visuals-sampleBarChart) for how conditional formatting was applied to sample bar chart.

![](images/CondFormatSupport.png)
13 changes: 6 additions & 7 deletions Tutorial/DataBinding.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,25 @@ Use the internal `name` defined in your dataRoles to reference each field.

For more information, see the section about [Data View Mapping](https://github.com/Microsoft/PowerBI-visuals/blob/master/Capabilities/DataViewMappings.md).

## Defining and Using `visualTransform`
## Defining and Using `createSelectorDataPoints`
DataView is the structure that PowerBI provides to your visual and it contains the queried data to be visualized.
However, DataView provides your data in different forms such as categorical and table forms. In this instance we're building a categorical visual and we will only need the use the categorical property on the DataView.

Defining visualTransform will allow you to convert DataView into a view model your visual will use.
Defining `createSelectorDataPoints` will allow you to convert options dataView into bar chart data points your visual will use.
IVisualHost is required because when defining individual data points, you will want to assign colors and selection to them.

```typescript
/**
* Function that converts queried data into a view model that will be used by the visual
* Function that converts queried data into bar chart data points that will be used by the visual
*
* @function
* @param {VisualUpdateOptions} options - Contains references to the size of the container
* and the dataView which contains all the data
* the visual had queried.
* @param {IVisualHost} host - Contains references to the host which contains services
*/
function visualTransform(options: VisualUpdateOptions, host: IVisualHost): BarChartViewModel {
/*Convert dataView to your viewModel*/
function createSelectorDataPoints(options: VisualUpdateOptions, host: IVisualHost): BarChartDataPoint[] {
/*Convert dataView to bar chart data points*/
}

```
See [commit](https://github.com/Microsoft/PowerBI-visuals-sampleBarChart/commit/3c6e8186436b63bf0cf97d2cdd5dde8aa8d08709) for what was added to visualTransform
```
102 changes: 45 additions & 57 deletions Tutorial/DataBoundObjects.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ See [commit](https://github.com/Microsoft/PowerBI-visuals-sampleBarChart/commit/
## Define Object in Capabilities
Similar to static objects, we will define another object in the capabilities
`colorSelector` is the internal name that will be referenced in the `dataView`.
`displayName` is the name that will be shown on the property pane.

`fill` is a `StructuralObjectValue` and is not associated with a primitive type.

```typescript
"colorSelector": {
"displayName": "Data Colors",
"properties": {
"fill": {
"displayName": "Color",
"type": {
"fill": {
"solid": {
Expand Down Expand Up @@ -69,74 +66,65 @@ export function getCategoricalObjectValue<T>(category: DataViewCategoryColumn, i
See [objectEnumerationUtility.ts](https://github.com/Microsoft/PowerBI-visuals-sampleBarChart/blob/master/src/objectEnumerationUtility.ts) for source code.

## Defining Default Color and Retrieving Categorical Object from DataView
Each color is now associated with each category inside `dataView`. We will set each data point to its cooresponding color.
Each color is now associated with each category inside options dataView. We will set each data point to its corresponding color.

```typescript
const strokeWidth: number = getColumnStrokeWidth(colorPalette.isHighContrast);

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {
let defaultColor: Fill = {
solid: {
color: colorPalette.getColor(category.values[i]).value
}
}
const color: string = getColumnColorByIndex(category, i, colorPalette);

const selectionId: ISelectionId = host.createSelectionIdBuilder()
.withCategory(category, i)
.createSelectionId();

barChartDataPoints.push({
category: category.values[i],
color,
strokeColor,
strokeWidth,
selectionId,
value: dataValue.values[i],
color: getCategoricalObjectValue<Fill>(category, i, 'colorSelector', 'fill', defaultColor).solid.color,
selectionId: host.createSelectionIdBuilder()
.withCategory(category, i)
.createSelectionId()
category: `${category.values[i]}`,
});
}
```

## Populate Property Pane with `enumerateObjectInstances`
`enumerateObjectInstances` is used to populate the property pane with objects.
## Populate Property Pane with `getFormattingModel`
`getFormattingModel` is used to populate the property pane with objects.
For more information [here](https://learn.microsoft.com/en-us/power-bi/developer/visuals/format-pane)

For this instance, we would like a color picker per category we have. Each category be rendered on the property pane.
We will do this by adding a populate method `populateColorSelector` to create corresponding bar chart data points color selector in format pane after building the data points in `update` method. This `populateColorSelector` method iterate through each data point with the associated color.

We will do this by adding an additional case to the switch statement for `colorSelector` and iterate through each data point with the associated color.
Selection is required to associate the color with a data point.
In visual class:
```typescript
public update(options: VisualUpdateOptions) {
this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(BarChartSettingsModel, options.dataViews);
this.barDataPoints = createSelectorDataPoints(options, this.host);
this.formattingSettings.populateColorSelector(this.barDataPoints);

Selection is required to associate the color with a datapoint.
// ...
}
```

In formatting settings model:
```typescript
/**
* Enumerates through the objects defined in the capabilities and adds the properties to the format pane
*
* @function
* @param {EnumerateVisualObjectInstancesOptions} options - Map of defined objects
/**
* populate colorSelector object categories formatting properties
* @param dataPoints
*/
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
let objectName = options.objectName;
let objectEnumeration: VisualObjectInstance[] = [];

switch(objectName) {
case 'enableAxis':
objectEnumeration.push({
objectName: objectName,
properties: {
show: this.barChartSettings.enableAxis.show,
},
selector: null
populateColorSelector(dataPoints: BarChartDataPoint[]) {
const slices: formattingSettings.ColorPicker[] = this.colorSelector.slices;
if (dataPoints) {
dataPoints.forEach(dataPoint => {
slices.push(new formattingSettings.ColorPicker({
name: "fill",
displayName: dataPoint.category,
value: { value: dataPoint.color },
selector: dataPoint.selectionId.getSelector(),
}));
});
break;
case 'colorSelector':
for(let barDataPoint of this.barDataPoints) {
objectEnumeration.push({
objectName: objectName,
displayName: barDataPoint.category,
properties: {
fill: {
solid: {
color: barDataPoint.color
}
}
},
selector: barDataPoint.selectionId.getSelector()
});
}
break;
};

return objectEnumeration;
}
```
}
}
```
4 changes: 3 additions & 1 deletion Tutorial/ExtensibilityUtils.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ PowerBI provides several tools that help to cover the main needs to build your o
5. [InteractivityUtils](https://www.npmjs.com/package/powerbi-visuals-utils-interactivityutils) is a set of functions and classes for implementation of cross-selection and cross-filtering for PowerBI custom visuals.
6. [FormattingUtils](https://www.npmjs.com/package/powerbi-visuals-utils-formattingutils) are interfaces for creating PowerBI custom visuals.
7. [SVGUtils](https://www.npmjs.com/package/powerbi-visuals-utils-svgutils) is a tool for SVG manipulations for PowerBI custom visuals.
8. [FormattingModelUtils](https://github.com/microsoft/powerbi-visuals-utils-formattingmodel) is a set of classes, interfaces and method help building format pane easily.
9. [OnObjectUtils](https://github.com/microsoft/powerbi-visuals-utils-onobjectutils) provides an easy way for your Power BI custom visual to emit subselections to Power BI, get and render outlines.

### How to install
To install the package you should run the following command in the directory with your current custom visual:
Expand Down Expand Up @@ -47,4 +49,4 @@ After that user can use all available module methods
.call(xAxis);
```

To get more information about SVGItils package, please check the following [documentation](https://github.com/Microsoft/powerbi-visuals-utils-svgutils/)
To get more information about SVGItils package, please check the following [documentation](https://github.com/Microsoft/powerbi-visuals-utils-svgutils/)
2 changes: 1 addition & 1 deletion Tutorial/ExternalLibraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ Add the library to your `tsconfig.json` file.
}
```

Refer to [this](Typings.md) if you'd like to add typings for your JS file to get intellisense and compile time safety on them.
Refer to [this](Typings.md) if you'd like to add typings for your JS file to get intellisense and compile time safety on them.
14 changes: 7 additions & 7 deletions Tutorial/HighContrastSupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ In Sample Bar Chart, for example, all bars are drawn with 2 pixels thick foregro
![Sample Bar Chart using *Dark #2* color theme](images/HC_sampleBarChart_dark2.png)
![Sample Bar Chart using *White* color theme](images/HC_sampleBarChart_white.png)
Here is one place in the `visualTransform` function that was changed to support high-contrast, it is called as part of rendering during `update`:
Here is one place in the `createSelectorDataPoints` function that was changed to support high-contrast, it is called as part of rendering during `update`:
**before**
```typescript
Expand All @@ -89,6 +89,11 @@ Here is one place in the `visualTransform` function that was changed to support
**after**
```typescript

const colorPalette: ISandboxExtendedColorPalette = host.colorPalette;
const strokeColor: string = getColumnStrokeColor(colorPalette);
const strokeWidth: number = getColumnStrokeWidth(colorPalette.isHighContrast);

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {
const color: string = getColumnColorByIndex(category, i, colorPalette);

Expand Down Expand Up @@ -125,9 +130,4 @@ Here is one place in the `visualTransform` function that was changed to support

return getCategoricalObjectValue<Fill>(category, index, 'colorSelector', 'fill', defaultColor).solid.color;
}
```
```
Loading

0 comments on commit ac2dbaf

Please sign in to comment.