Forum Discussion
Create Custom visuals - Set measure color
Hello All,
I am developing custom visual for a clustered column chart and I am facing issue while implementing the color palette.
I have added the color palette successfully but cannot use the color value for the series.
I can get it to work withCategory, but I need to set the color per the measure. I am having trouble implementing
host.createSelectionIdBuilder().withMeasure()
Once I select a color from palette it returns to the default.
Below is my code snipet for updating the view model and for rendering the Power BI properties.
//set up viewmovdel
//queries power bi to gather data that user is using for the visual
//power bi makes VisualUpdateOptions available with every call to the update method
private getViewModel(options: VisualUpdateOptions): ViewModel {
//option.dataViews stores power bi's query data based on shape (e.g. tabular, matrix, tree, etc.)
//configured in capabilities.json dataViewMappings.categorical
//https://microsoft.github.io/PowerBI-visuals/docs/concepts/dataviewmappings/
let dv = options.dataViews;
//empty viewModel
//The empty viewModel will be returned in the case where there is no data to show
let viewModel: ViewModel = {
dataPoints: []
, maxValue: 0
, minValue: 0
, highlights: false
};
//Check to make sure that all data that is needed is present before attempting to render the visual
if (!dv
|| !dv[0]
|| !dv[0].categorical
|| !dv[0].categorical.categories
|| !dv[0].categorical.categories[0].source
|| !dv[0].categorical.values
|| !dv[0].metadata
)
return viewModel;
//Only accepting one dataview
// The type that we are using is the categorical dataview
let view = dv[0].categorical;
//because we are accepting multiple measure types, iterate through each
for (let i = 0, len = view.values.length; i < len; i++) {
//We are only allowing for one series, so use index of 0 for categories
let categories = view.categories[0];
let values = view.values[i];
let highlights = values.highlights;
let objects = categories.objects;
let metadata = dv[0].metadata;
//metadata values will be used to populate the tooltip
let categoryColumnName = metadata.columns.filter(c => c.roles["category"])[0].displayName;
let measureColumnName = metadata.columns.filter(c => c.roles["measure"])[i].displayName;
//iterate over all category-value pairs by index
for (let j = 0, len = Math.max(categories.values.length, values.values.length); j < len; j++) {
//push datapoints into the view
viewModel.dataPoints.push({
category: <string>categories.values[j],
measure: <string>values.source.displayName,
value: <number>values.values[j],
//Since this property is data-bound, the property value is stored in the objects array
//if objects array exists and the color for
color: objects && objects[j] && dataViewObjects.getFillColor(objects[i], {
objectName: "dataColors"
, propertyName: "fill"
}, null) || this.host.colorPalette.getColor(<string>categories.values[i]).value,
//a new SelectionIdBuilder is needed everytime a new selection id is needed
identity: this.host.createSelectionIdBuilder()
//slice over category column at position i
.withCategory(categories, j)
.createSelectionId(),
measureIdentity: this.host.createSelectionIdBuilder()
.withMeasure(values.source.queryName)
.createSelectionId(),
//checks to see if the highlights array is defined
// then checks to see if any value exists in the same position of the corresponding
// values array, not the metadata array
highlighted: highlights ? highlights[j] ? true : false : false,
//set up the base tooltip for each datapoint
tooltips: [{
displayName: categoryColumnName,
value: <string>categories.values[j]
}, {
displayName: measureColumnName,
//if the value doesn't have a decimal, set value to 0 decimal places, otherwise set it 2 decimal places
value: ((<number>values.values[j]) == Math.floor(<number>values.values[j])) ? (<number>values.values[j]).toFixed(0) : (<number>values.values[j]).toFixed(2)
}]
});
}
}
//populate the single-valued datapoints
viewModel.maxValue = d3.max(viewModel.dataPoints, d => d.value);
viewModel.minValue = d3.min(viewModel.dataPoints, d => d.value);
//boolean to see whether or not there are any highlights
viewModel.highlights = viewModel.dataPoints.filter(d => d.highlighted).length > 0;
return viewModel;
}
//called by power bi everytime it needs to render the properties pane
//for updating a property/property value in the properties pane
//Driven by the object object in capabilities.json
//called one time per property group in object object any time anything is updated in power bi
//* Note - this takes place outside of the update loop
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
let propertyGroupName = options.objectName;
let properties: VisualObjectInstance[] = [];
switch (propertyGroupName) {
case "xAxis":
properties.push({
objectName: propertyGroupName,
properties: {
show: this.settings.axis.x.show.value
},
selector: null
});
break;
case "yAxis":
properties.push({
objectName: propertyGroupName,
properties: {
show: this.settings.axis.y.show.value
, invert: this.settings.axis.y.invert.value
, minimumDomain: this.settings.axis.y.minimumDomain.value
, integers: this.settings.axis.y.integers.value
, gridlines: this.settings.axis.y.gridlines.show.value
},
selector: null
});
break;
case "dataColors":
{
if (this.viewModel) {
//list out all data point properties that are needed
let allColorDatPoints = [];
this.viewModel.dataPoints.forEach(dp => {
allColorDatPoints.push({measureIdentity: dp.measureIdentity, measure: dp.measure, color: dp.color})
})
//get unique list of needed data point properties
let colorDatPoints = [];
for(let i=0; i<allColorDatPoints.length;i++){
let exists = false;
for(let j=0; j<colorDatPoints.length; j++){
if(//allColorDatPoints[i].measureIdentity == colorDatPoints[j].measureIdentity
//&&
allColorDatPoints[i].measure == colorDatPoints[j].measure
&& allColorDatPoints[i].color == colorDatPoints[j].color
) {
exists = true;
}
}
(!exists) ? colorDatPoints.push(allColorDatPoints[i]) : null
}
for (let cdp of colorDatPoints) {
properties.push({
objectName: cdp.measure
, displayName: cdp.measure
, properties: {
fill: cdp.color
},
selector: cdp.measureIdentity.getSelector()
});
}
}
break;
}
}
return properties;
}
}
}
With measures, I've only got it to work by using the queryName property from the measure DataViewMetadataColumn. If you use this rather than getSelector() against a SelectionId it should push it into the data view correctly.
If done right, you should see an objects key underneath your column in the metadata, e.g.:
You should be able to make out the queryName property from this screenshot too.
This should then get persisted into your object enumeration, e.g.:
Hopefully this is enough to get you moving; if not, you might need to share some code so we can have a look at specifics.
Good luck!
Daniel
2 Replies
- NishantjainContinued Contributor
Hi,
Did you manage to resolve this issue? I am having the same problem and can't figure out how to read the measure colorsThanks
Nishant
- dm-pSuper User
With measures, I've only got it to work by using the queryName property from the measure DataViewMetadataColumn. If you use this rather than getSelector() against a SelectionId it should push it into the data view correctly.
If done right, you should see an objects key underneath your column in the metadata, e.g.:
You should be able to make out the queryName property from this screenshot too.
This should then get persisted into your object enumeration, e.g.:
Hopefully this is enough to get you moving; if not, you might need to share some code so we can have a look at specifics.
Good luck!
Daniel