Earn a 50% discount on the DP-600 certification exam by completing the Fabric 30 Days to Learn It challenge.
Hello
I have an issue with creating a custom visual in which I need to be able to set 2 different font sizes from the property pane. The issue is that the first font size control is a slider which allows values from 8-40 whereas subsequent controls allow 0-100 percent.
I can show this using the basic new visualisation
pbiviz new fontVisual
npm install
Then edit capabilities.json to include a second font size property (in this case "fontSize1")
}, "fontSize": { "displayName": "Text Size", "type": { "formatting": { "fontSize": true } } }, "fontSize1": { "displayName": "Text Size 1", "type": { "formatting": { "fontSize": true } } }
Update settings.ts to include the new property
// Text Size public fontSize: number = 12; public fontSize1: number = 12;
Then build and run
pbiviz start
This is the result
Is this the expected behaviour? Or is the second font size meant to be a percentage of the first? If not, Is there any way I can make them the same control?
Thanks
Solved! Go to Solution.
Hello @saviourofdp
The fontSize is predefined Power BI capabilities name.
You might try to rename the second fontSize property to textSize, titleFontSize, secFontSize, secTitleFontSize.
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
Why does the font size property in the properry pane get rendered as a numeric(integer %) up/down control under its label and not a slider on the same line as the label, when not using the settings.ts file?
Not sure. Please share your code sample.
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
{ "dataRoles": [ { "displayName": "Values", "name": "values", "kind": "Grouping" } ], "dataViewMappings": [ { "conditions": [ { "values": { "max": 1 } } ], "categorical": { "categories": { "for": { "in": "values" }, "dataReductionAlgorithm": { "top": { "count": 100000 } } } } } ], "objects": { "defaults": { "displayName": "Defaults", "properties": { "defaultId": { "type": { "integer": true }, "displayName": "Default Item Index", "description": "Select default item index" } } }, "labels": { "displayName": "Labels", "properties": { "vertical": { "displayName": "Vertical", "description": "If true, the radio buttons will be displayed vertically.", "type": { "bool": true } }, "backgroundColor": { "type": { "fill": { "solid": { "color": true } } }, "displayName": "Background Color", "description": "Select background color" }, "fontSize": { "type": { "formatting": { "fontSize": true } }, "displayName": "Font Size", "description": "Select font size" }, "fontColor": { "type": { "fill": { "solid": { "color": true } } }, "displayName": "Font Color", "description": "Select font color" }, "fontFamily": { "displayName": "Font family", "description": "Select font family", "type": { "enumeration": [ { "displayName": "Default", "description": "helvetica, arial, sans-serif", "value": "helvetica, arial, sans-serif" }, { "displayName": "Arial", "value": "Arial" }, { "displayName": "Arial Black", "value": "\"Arial Black\"" }, { "displayName": "Arial Unicode MS", "value": "\"Arial Unicode MS\"" }, { "displayName": "Calibri", "value": "Calibri" }, { "displayName": "Cambria", "value": "Cambria" }, { "displayName": "Cambria Math", "value": "\"Cambria Math\"" }, { "displayName": "Candara", "value": "Candara" }, { "displayName": "Comic Sans MS", "value": "\"Comic Sans MS\"" }, { "displayName": "Consolas", "value": "Consolas" }, { "displayName": "Constantia", "value": "Constantia" }, { "displayName": "Corbel", "value": "Corbel" }, { "displayName": "Courier New", "value": "\"Courier New\"" }, { "displayName": "Georgia", "value": "Georgia" }, { "displayName": "Lucida Sans Unicode", "value": "\"Lucida Sans Unicode\"" }, { "displayName": "Segoe (Bold)", "value": "\"Segoe UI Bold\", wf_segoe-ui_bold, helvetica, arial, sans-serif" }, { "displayName": "Segoe UI", "value": "\"Segoe UI\", wf_segoe-ui_normal, helvetica, arial, sans-serif" }, { "displayName": "Segoe UI Light", "value": "\"Segoe UI Light\", wf_segoe-ui_bold, helvetica, arial, sans-serif" }, { "displayName": "Symbol", "value": "Symbol" }, { "displayName": "Tahoma", "value": "Tahoma" }, { "displayName": "Times New Roman", "value": "\"Times New Roman\"" }, { "displayName": "Trebuchet MS", "value": "\"Trebuchet MS\"" }, { "displayName": "Verdana", "value": "Verdana" }, { "displayName": "Wingdings", "value": "Wingdings" } ] } }, "btnSize": { "type": { "integer": true }, "displayName": "Button Size", "description": "Select button size" } } } } }
visual.ts:
/* * Power BI Visual CLI * * Copyright (c) Microsoft Corporation * All rights reserved. * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the ""Software""), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ module powerbi.extensibility.visual { "use strict"; interface FlexiSlicerViewModel { vertical: boolean; defaultId: number; numberCats: number; fontSize: number; fontFamily: string; fontColor: string; backgroundColor: string; radioBtnSize: number; }; function visualTransform(options: VisualUpdateOptions, host: IVisualHost): FlexiSlicerViewModel { let dataViews = options.dataViews; let viewModel: FlexiSlicerViewModel = { vertical: true, defaultId: 0, numberCats: 0, fontSize: 12, fontFamily: "Arial", fontColor: "blue", backgroundColor: "white", radioBtnSize: 12 }; if (!dataViews || !dataViews[0] || !dataViews[0].categorical || !dataViews[0].categorical.categories || !dataViews[0].categorical.categories[0] ) return viewModel; let category = options.dataViews[0].categorical.categories[0]; var numCats = category.values.length; let dvobjs = dataViews[0].metadata.objects; try{ let style: FlexiSlicerViewModel = { vertical: getValue<boolean>(dvobjs, 'labels', 'vertical', true), defaultId: getValue<number>(dvobjs, 'defaults', 'defaultId', 0), numberCats: numCats, fontSize: getValue<number>(dvobjs, 'labels', 'fontSize', 12), fontFamily: getValue<string>(dvobjs, 'labels', 'fontFamily', "Arial"), fontColor: getFill(dataViews[0], 'labels', 'fontColor', "blue"), backgroundColor: getFill(dataViews[0], 'labels', 'backgroundColor', "white"), radioBtnSize: getValue<number>(dvobjs, 'labels', 'btnSize', 12) }; return style; } catch(e){ return viewModel; } } export class Visual implements IVisual { private target: HTMLElement; private flexiSlicerViewModel: FlexiSlicerViewModel; //private numberCats: number; private selectionManager: ISelectionManager; private selectionIds: any = {}; private host: IVisualHost; private isEventUpdate: boolean = false; private lastSelectedValue: any; constructor(options: VisualConstructorOptions) { this.target = options.element; this.host = options.host; this.selectionManager = options.host.createSelectionManager(); } public update(options: VisualUpdateOptions) { this.flexiSlicerViewModel = visualTransform(options, this.host); if (this.flexiSlicerViewModel && !this.isEventUpdate){ this.init(options); //update if default item is set if(this.flexiSlicerViewModel.defaultId > 0){ this.selectionManager.clear(); // Clean up previous filter before applying another one. // Find the selectionId and select it this.selectionManager.select(this.lastSelectedValue).then((ids: ISelectionId[]) => { }); // This call applys the previously selected selectionId this.selectionManager.applySelectionFilter(); } } } public init(options: VisualUpdateOptions) { // Return if we don't have a category if (!options || !options.dataViews || !options.dataViews[0] || !options.dataViews[0].categorical || !options.dataViews[0].categorical.categories || !options.dataViews[0].categorical.categories[0]) { return; } let viewmodel = this.flexiSlicerViewModel; // remove any children from previous renders while (this.target.firstChild) { this.target.removeChild(this.target.firstChild); } // clear out any previous selection ids this.selectionIds = {}; // get the category data. let category = options.dataViews[0].categorical.categories[0]; let values = category.values; // build selection ids to be used by filtering capabilities later var itemctr: number = 0; let scroller = document.createElement("div"); scroller.className="container"; scroller.style.width = options.viewport.width.toString() +"px"; scroller.style.height = options.viewport.height.toString() +"px"; scroller.style.backgroundColor = viewmodel.backgroundColor; values.forEach((item: number, index: number) => { itemctr++; // create an in-memory version of the selection id so it can be used in onclick event. this.selectionIds[item] = this.host.createSelectionIdBuilder() .withCategory(category, index) .createSelectionId(); let value = item.toString(); let radio = document.createElement("input"); radio.type = "radio"; radio.value = value; radio.name = "values"; radio.id = itemctr.toString(); radio.style.height= viewmodel.radioBtnSize.toString()+"px";// viewmodel.fontSize.toString() + "px"; radio.style.width= viewmodel.radioBtnSize.toString()+"px";//viewmodel.fontSize.toString() + "px"; //set default checked item if(itemctr == viewmodel.defaultId ){ radio.checked = true; this.lastSelectedValue = this.selectionIds[value]; } radio.onclick = function (ev) { this.isEventUpdate = true; // This is checked in the update method. If true it won't re-render, this prevents an infinite loop this.selectionManager.clear(); // Clean up previous filter before applying another one. // select saved selectionid this.selectionManager.select(this.selectionIds[value]).then((ids: ISelectionId[]) => { }); // This call applys the previously saved selectionId this.selectionManager.applySelectionFilter(); }.bind(this); let label = document.createElement("label"); label.innerHTML += value; label.style.fontFamily= viewmodel.fontFamily; label.style.color = viewmodel.fontColor; label.style.fontSize = viewmodel.fontSize.toString() + "px"; label.htmlFor=itemctr.toString(); scroller.appendChild(radio); scroller.appendChild(label); if (viewmodel.vertical == true && itemctr < values.length ) { scroller.appendChild(document.createElement("br")); } }); this.target.appendChild(scroller); } /** * Enumerates through the objects defined in the capabilities and adds the properties to the format pane * * @function * @param {EnumerateVisualObjectInstancesOptions} options - Map of defined objects */ //@logExceptions() public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration { let objectName = options.objectName; let objectEnumeration: VisualObjectInstance[] = []; let viewModel = this.flexiSlicerViewModel; switch (objectName) { case 'defaults': var id: number = 0; if(viewModel.defaultId > viewModel.numberCats) id= 0; else id= viewModel.defaultId; objectEnumeration.push({ objectName: objectName, properties: { defaultId: id }, validValues: { defaultId: { numberRange: { min: 0, max: viewModel.numberCats } } }, selector: null }); break; case 'labels': objectEnumeration.push({ objectName: objectName, properties: { vertical: viewModel.vertical, backgroundColor: viewModel.backgroundColor, fontSize: viewModel.fontSize, fontFamily: viewModel.fontFamily, fontColor: viewModel.fontColor, btnSize: viewModel.radioBtnSize }, validValues: { btnSize: { numberRange: { min: 1, max: 40 } } }, selector: null }); break; }; this.isEventUpdate = false; return objectEnumeration; } } }
Can you share full source code as a zip file?
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
Hello @saviourofdp
The fontSize is predefined Power BI capabilities name.
You might try to rename the second fontSize property to textSize, titleFontSize, secFontSize, secTitleFontSize.
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
Is there an up to date list of the reserved names?
This is a full list of properties that I know about:
weight
angle
textSize
fontSize
titleFontSize
secFontSize
secTitleFontSize
outlineWeight
gridVerticalWeight
gridHorizontalWeight
barWeight
rowPadding
cardPadding
imageHeight
steppedLayoutIndentation
borderThickness
preferredCategoryWidth
strokeWidth
gridlineThickness
markerSize
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
Do you have a list of actual values that work?
Can you share a whole capabilities.json file to figure out a root of this issue?
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals
ok, thanks Ignat that works. To conclude, I have to use one of
to get the absolute value slider. Otherwise, I get a percentage slider.
This then begs further questions:
Thanks for the help
using secFontSize results in errors:
info JSON change detected. Rebuilding...
error JSON capabilities.json : instance.objects.dataPoint.properties.d1FontSize2.type.formatting additionalProperty "secFontSize" exists in instance when not allowed
error JSON capabilities.json : instance.objects.dataPoint.properties.d1FontSize2.type.formatting is not exactly one from [subschema 0],[subschema 1],[subschema 2]
error JSON capabilities.json : instance.objects.dataPoint.properties.fontSize.type.formatting additionalProperty "textSize" exists in instance when not allowed
error JSON capabilities.json : instance.objects.dataPoint.properties.fontSize.type.formatting is not exactly one from [subschema 0],[subschema 1],[subschema 2]
Regarding your questions:
Ignat Vilesov,
Software Engineer
Microsoft Power BI Custom Visuals