API de formatação no objeto (visualização)

A formatação no objeto permite que os usuários modifiquem rápida e facilmente o formato dos elementos visuais, selecionando diretamente os elementos que desejam modificar. Quando um elemento é selecionado, o painel de formato navega automaticamente e expande a configuração de formatação específica para o elemento selecionado. Para obter mais informações sobre formatação no objeto, consulte Formatação no objeto no Power BI Desktop.

Para adicionar essas funcionalidades ao seu visual, cada visual precisa fornecer uma opção de estilo de subseleção e atalho para cada região subselecionável.

Nota

  • Os elementos visuais que suportam a formatação no objeto precisam implementar a API getFormattingModel, que está disponível na API versão 5.1.
  • Se você estiver usando powerbi-visuals-utils-formattingmodel, use pelo menos a versão 6.0.0.

Criar uma experiência no objeto

Use o serviço de subseleção quando o usuário selecionar um elemento subselecionável para enviar a subseleção ao Power BI. Forneça os estilos e atalhos de subseleção usando a API de subseção. O auxiliar de subseleção pode ser usado para simplificar o processo.

Modo de formato

O modo de formato é um novo modo onde o usuário pode ativar onObject e desativar a formatação quando estiver no modo de criação. O visual é atualizado com o status do modo de formato nas opções de atualização. As opções de atualização também incluem a subSeleção atualmente subselecionada como CustomVisualSubSelection.

Como implementar a API de formatação no objeto

Arquivo de recursos

No arquivo capabilites.json, adicione as seguintes propriedades para declarar que o visual oferece suporte à formatação no objeto:

{
  "supportsOnObjectFormatting": true,
  "enablePointerEventsFormatMode": true,
}

IVisual interface

O visual precisa implementar a VisualOnObjectFormatting interface como parte da interface IVisual.

VisualOnObjectFormatting contém três métodos:

getSubSelectionStyles

Cada visual é necessário para implementar um getSubSelectionStyles método, que é chamado quando um elemento subselecionável é subselecionado. O getSubSelectionStyles método é fornecido com os elementos subselecionados atuais como uma CustomVisualSubSelection matriz e espera-se que retorne um SubSelectionStyles objeto ou undefined.

Há três categorias de estilos de subseleção que abrangem a maioria dos cenários:

  • Texto
  • Texto numérico
  • Forma

Cada SubSelectionStyles objeto fornece uma experiência diferente para o usuário modificar o estilo de um elemento.

getSubSelectionAtalhos

Para fornecer mais opções para o usuário, o visual deve implementar o getSubSelectionShortcuts método. Esse método retorna ou VisualSubSelectionShortcuts undefined. Além disso, se SubSelectionShortcuts forem fornecidos, um também deve ser fornecido para VisualNavigateSubSelectionShortcut que, quando um usuário subseleciona um elemento e o painel de formato está aberto, o painel rola automaticamente para o cartão apropriado.

Existem vários atalhos de subseleção para modificar o estado visual. Cada um define um item de menu no menu de contexto com o rótulo apropriado.

Menu de desambiguação de subseleção: o menu de desambiguação no objeto fornece um método para os usuários selecionarem a subseleção desejada quando não estiver claro qual elemento visual está sendo subselecionado. Isso geralmente acontece quando o usuário subseleciona o plano de fundo do visual. Para que o menu ambíguo apresente mais subseleções, o visual deve fornecer todas as subseleções através do getSubSelectables método.

getSubSelectables

Para fornecer subseleções para o menu de desambiguação, o visual precisa implementar o getSubSelectables método. Este método é fornecido um argumento opcional filterType , do tipo SubSelectionStylesType e retorna uma matriz de CustomVisualSubSelection ou undefined. Se o HTMLSubSelectionHelper estiver sendo utilizado para criar uma subseleção, o método HTMLSubSelectionHelper.getSubSelectables() poderá ser usado para coletar elementos subselecionáveis do DOM.

Edição direta de texto de subseleção: com a formatação no objeto, você pode clicar duas vezes no texto de um elemento subs-elegível para editá-lo diretamente. Para fornecer o recurso de edição direta, você precisa fornecer um RectangleSubSelectionOutline com a propriedade cVDirectEdit apropriada preenchida com um objeto SubSelectableDirectEdit. A estrutura de tópicos pode ser fornecida como uma estrutura de tópicos personalizada ou, se você estiver usando o atributo HTMLSubSelectionHelper , você pode usar o SubSelectableDirectEdit atributo. (Consulte os atributos fornecidos pelo HTMLSubSelectionHelper)

A adição de uma edição direta para um ponto de dados específico (usando seletores) ainda não é suportada.

Interface FormattingId

A interface a seguir é usada para fazer referência aos subSelection atalhos e estilos.

interface FormattingId {
            objectName: string;
            propertyName: string;
            selector?: powerbi.data.Selector;
        }
  • objectName: o nome do objeto conforme declarado no capabilities.json.
  • propertyName: o nome da propriedade de um objeto, conforme declarado no capabilities.json.
  • seletor: se o ponto de dados tiver um selectionId, use selectionId.getSelector(), esse seletor deve ser o mesmo fornecido para a fatia do modelo de formatação.

Exemplos

Neste exemplo, criamos um visual personalizado que tem dois objetos colorSelector e directEdit. Usamos o HTMLSubSelectionHelper from the onobjectFormatting utils, para lidar com a maior parte do trabalho de subseleção. Para obter mais informações, consulte Utils no objeto.

Primeiro, criamos cartões para o painel de formatação e fornecemos subSelectionAtalhos e estilos para cada subselecionável.

Definir os objetos

Defina os objetos e declare que o visual está suportando a formatação OnObject no capabilities.json:

"objects": {
      "directEdit": {
      "properties": {
        "show": {
          "displayName": "Show",
          "type": {
            "bool": true
          }
        },
        "textProperty": {
          "displayName": "Text",
          "type": {
            "text": true
          }
        },
        "fontFamily": {
          "type": {
            "formatting": {
              "fontFamily": true
            }
          }
        },
        "fontSize": {
          "type": {
            "formatting": {
              "fontSize": true
            }
          }
        },
        "bold": {
          "type": {
            "bool": true
          }
        },
        "italic": {
          "type": {
            "bool": true
          }
        },
        "underline": {
          "type": {
            "bool": true
          }
        },
        "fontColor": {
          "displayName": "Font Color",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        },
        "background": {
          "displayName": "Background",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        },
        "position": {
          "displayName": "Position",
          "type": {
            "enumeration": [
              { "displayName": "Left", "value": "Left" }, { "displayName": "Right", "value": "Right" }
            ]
          }
        }
      }
    },
    "colorSelector": {
      "displayName": "Data Colors",
      "properties": {
        "fill": {
          "displayName": "Color",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        }
      }
    },
   },
  "supportsOnObjectFormatting": true,
  "enablePointerEventsFormatMode": true,

Crie os cartões de formatação

Crie seus cartões de formatação usando o formattingModel utils.

Configurações do cartão seletor de cores

class ColorSelectorCardSettings extends Card {
    name: string = "colorSelector";
    displayName: string = "Data Colors";
    slices = [];
}

Adicione um método ao formattingSetting para que possamos preencher as fatias dinamicamente para o objeto colorSelector (nossos pontos de dados).

populateColorSelector(dataPoints: BarChartDataPoint[]) {
        let 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(),
                }));
            });
        }
    }

Passamos o seletor do ponto de dados específico no campo seletor. Este seletor é o usado ao implementar as APIs get do OnObject.

Configurações diretas do cartão de edição

class DirectEditSettings extends Card {
    displayName = 'Direct Edit';
    name = 'directEdit';
    private minFontSize: number = 8;
    private defaultFontSize: number = 11;
    show = new formattingSettings.ToggleSwitch({
        name: "show",
        displayName: undefined,
        value: true,
    });
    topLevelSlice = this.show;
    textProperty = new formattingSettings.TextInput({
        displayName: "Text Property",
        name: "textProperty",
        value: "What is your quest?",
        placeholder: ""
    });
    position = new formattingSettings.ItemDropdown({
        name: 'position',
        items: [{ displayName: 'Left', value: 'Left' }, { displayName: 'Right', value: 'Right' }],
        value: { displayName: 'Right', value: 'Right' }
    });
    font = new formattingSettings.FontControl({
        name: "font",
        displayName: 'Font',
        fontFamily: new formattingSettings.FontPicker({
            name: "fontFamily",
            displayName: "Font Family",
            value: "Segoe UI, wf_segoe-ui_normal, helvetica, arial, sans-serif"
        }),
        fontSize: new formattingSettings.NumUpDown({
            name: "fontSize",
            displayName: "Font Size",
            value: this.defaultFontSize,
            options: {
                minValue: {
                    type: powerbi.visuals.ValidatorType.Min,
                    value: this.minFontSize,
                }
            }
        }),
        bold: new formattingSettings.ToggleSwitch({
            name: 'bold',
            displayName: "Font Size",
            value: true
        }),
        italic: new formattingSettings.ToggleSwitch({
            name: 'italic',
            displayName: "Font Size",
            value: true
        }),
        underline: new formattingSettings.ToggleSwitch({
            name: 'underline',
            displayName: "Font Size",
            value: true
        })
    });
    fontColor = new formattingSettings.ColorPicker({
        name: "fontColor",
        displayName: "Color",
        value: { value: "#000000" }
    });
    background = new formattingSettings.ColorPicker({
        name: "background",
        displayName: "Color",
        value: { value: "#FFFFFF" }
    });
    slices = [this.show, this.textProperty, this.font, this.fontColor, this.background, this.position];
}

Usar atributos auxiliares de subseleção

Adicione os HTMLSubSelectionHelper atributos aos nossos objetos. Para ver quais atributos o HTMLSubSelectionHelper fornece, verifique a documentação on object utils.

  • Para o atributo directEdit:

    import {
       HtmlSubSelectableClass, HtmlSubSelectionHelper, SubSelectableDirectEdit as SubSelectableDirectEditAttr,
       SubSelectableDisplayNameAttribute, SubSelectableObjectNameAttribute, SubSelectableTypeAttribute 
    } from 'powerbi-visuals-utils-onobjectutils';
    
    const DirectEdit: powerbi.visuals.SubSelectableDirectEdit = {
        reference: {
            objectName: 'directEdit',
            propertyName: 'textProperty'
        },
        style: SubSelectableDirectEditStyle.Outline,
    };
    private visualDirectEditSubSelection = JSON.stringify(DirectEdit);
    
    this.directEditElement
                .classed('direct-edit', true)
                .classed('hidden', !this.formattingSettings.directEditSettings.show.value)
                .classed(HtmlSubSelectableClass, options.formatMode && this.formattingSettings.directEditSettings.show.value)
                .attr(SubSelectableObjectNameAttribute, 'directEdit')
                .attr(SubSelectableDisplayNameAttribute, 'Direct Edit')
                .attr(SubSelectableDirectEditAttr, this.visualDirectEditSubSelection)
    

    O HTMLSubSelectionHelper usa o SubSelectableDirectEditAttr atributo para fornecer a referência directEdit da estrutura de tópicos directEdit, portanto, uma edição direta é iniciada quando um usuário clica duas vezes no elemento.

    Screenshot showing how the subselection helper works.

  • Para o colorSelector:

    barSelectionMerged
              .attr(SubSelectableObjectNameAttribute, 'colorSelector')
              .attr(SubSelectableDisplayNameAttribute, (dataPoint: BarChartDataPoint) => this.formattingSettings.colorSelector.slices[dataPoint.index].displayName)
              .attr(SubSelectableTypeAttribute, powerbi.visuals.SubSelectionStylesType.Shape)
              .classed(HtmlSubSelectableClass, options.formatMode)
    
    

Definir referências

Defina a seguinte interface para simplificar os exemplos:

Nota

O cardUid fornecimento deve ser o mesmo fornecido para a API getFormattingModel. Por exemplo, se você estiver usando powerbi-visuals-utils-formattingmodel, forneça o cardUid como Visual-cardName-card, onde cardName é o nome atribuído a esse cartão nas configurações do modelo de formatação. Caso contrário, forneça-o como o Visual-cardUid que você atribuiu a este cartão.

interface References {
    cardUid?: string;
    groupUid?: string;
    fill?: FormattingId;
    font?: FormattingId;
    fontColor?: FormattingId;
    show?: FormattingId;
    fontFamily?: FormattingId;
    bold?: FormattingId;
    italic?: FormattingId;
    underline?: FormattingId;
    fontSize?: FormattingId;
    position?: FormattingId;
    textProperty?: FormattingId;
}

Para este exemplo, crie um enum para os nomes dos objetos:

const enum BarChartObjectNames {
    ColorSelector = 'colorSelector',
    DirectEdit = 'directEdit'
}
  • Referências para o directEdit objeto:
const directEditReferences: References = {
    cardUid: 'Visual-directEdit-card',
    groupUid: 'directEdit-group',
    fontFamily: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'fontFamily'
    },
    bold: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'bold'
    },
    italic: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'italic'
    },
    underline: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'underline'
    },
    fontSize: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'fontSize'
    },
    fontColor: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'fontColor'
    },
    show: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'show'
    },
    position: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'position'
    },
    textProperty: {
        objectName: BarChartObjectNames.DirectEdit,
        propertyName: 'textProperty'
    }
};
  • Para o colorSelector:
const colorSelectorReferences: References = {
    cardUid: 'Visual-colorSelector-card',
    groupUid: 'colorSelector-group',
    fill: {
        objectName: BarChartObjectNames.ColorSelector,
        propertyName: 'fill'
    }
};

Implementar APIs

Agora vamos implementar as APIs get para a formatação onObject e fornecê-las no visualOnObjectFormatting:

  1. No código do construtor, forneça os métodos get no visualOnObjectFormatting:

    public visualOnObjectFormatting: powerbi.extensibility.visual.VisualOnObjectFormatting;
    constructor(options: VisualConstructorOptions) {
            this.subSelectionHelper = HtmlSubSelectionHelper.createHtmlSubselectionHelper({
                     hostElement: options.element,
                     subSelectionService: options.host.subSelectionService,
                     selectionIdCallback: (e) => this.selectionIdCallback(e),
                });
    
     this.visualOnObjectFormatting = {
                    getSubSelectionStyles: (subSelections) => this.getSubSelectionStyles(subSelections),
                    getSubSelectionShortcuts: (subSelections, filter) => this.getSubSelectionShortcuts(subSelections, filter),
                    getSubSelectables: (filter) => this. getSubSelectables(filter)
                }
       }
    
    private getSubSelectionStyles(subSelections: CustomVisualSubSelection[]): powerbi.visuals.SubSelectionStyles | undefined {
            const visualObject = subSelections[0]?.customVisualObjects[0];
            if (visualObject) {
                switch (visualObject.objectName) {
                    case BarChartObjectNames.ColorSelector:
                        return this.getColorSelectorStyles(subSelections);
                     case BarChartObjectNames.DirectEdit:
                        return this.getDirectEditStyles();
                }
            }
        }
    
    private getSubSelectionShortcuts(subSelections: CustomVisualSubSelection[], filter: SubSelectionShortcutsKey | undefined):    VisualSubSelectionShortcuts | undefined {
            const visualObject = subSelections[0]?.  customVisualObjects[0];
            if (visualObject) {
                switch (visualObject.objectName) {
                    case BarChartObjectNames.ColorSelector:
                        return this.getColorSelectorShortcuts(subSelections);
                    case BarChartObjectNames.DirectEdit:
                        return this.getDirectEditShortcuts();
                }
            }
        }
    
  2. Implemente os atalhos e o estilo getSubSelection para o colorSelector:

    private getColorSelectorShortcuts(subSelections:  CustomVisualSubSelection[]): VisualSubSelectionShortcuts   {
            const selector = subSelections[0].customVisualObjects[0].selectionId?.getSelector();
            return [
                {
                    type: VisualShortcutType.Reset,
                    relatedResetFormattingIds: [{
                        ...colorSelectorReferences.fill,
                        selector
                    }],
                },
                {
                    type: VisualShortcutType.Navigate,
                    destinationInfo: { cardUid: colorSelectorReferences.cardUid },
                    label: 'Color'
                }
            ];
        }
    

    O atalho acima retorna o item de menu relevante no menu de contexto e adiciona as seguintes funcionalidades:

    • VisualShortcutType.Navigate: quando um usuário seleciona em uma das barras (ponto de dados) e o painel de formatação está aberto, o painel de formatação rola até o cartão seletor de cores e o abre
    • VisualShortcutType.Reset: adiciona um atalho de redefinição ao menu de contexto. É ativado se a cor de preenchimento tiver sido alterada.
    private getColorSelectorStyles(subSelections: CustomVisualSubSelection[]): SubSelectionStyles {
            const selector = subSelections[0].customVisualObjects[0].selectionId?.getSelector();
            return {
                type: SubSelectionStylesType.Shape,
                fill: {
                    label: 'Fill',
                    reference: {
                        ...colorSelectorReferences.fill,
                     selector
                    },
                },
            };
        }
    

Quando um usuário clica com o botão direito do mouse em uma barra, o seguinte aparece:

Screenshot of user interface when a user right-clicks on a bar.

Ao alterar a cor:

Screenshot of changing color.

Atalhos da subsecção

Para implementar os atalhos e estilos de subSelection para o directEdit:

private getDirectEditShortcuts(): VisualSubSelectionShortcuts {
        return [
            {
                type: VisualShortcutType.Reset,
                relatedResetFormattingIds: [
                    directEditReferences.bold,
                    directEditReferences.fontFamily,
                    directEditReferences.fontSize,
                    directEditReferences.italic,
                    directEditReferences.underline,
                    directEditReferences.fontColor,
                    directEditReferences.textProperty
                ]
            },
            {
                type: VisualShortcutType.Toggle,
                relatedToggledFormattingIds: [{
                    ...directEditReferences.show,
                }],
                ...directEditReferences.show,
                disabledLabel: 'Delete',
            },
            {
                type: VisualShortcutType.Picker,
                ...directEditReferences.position,
                label: 'Position'
            },
            {
                type: VisualShortcutType.Navigate,
                destinationInfo: { cardUid: directEditReferences.cardUid },
                label: 'Direct edit'
            }
        ];
    }

Este atalho adiciona um item de menu relevante no menu de contexto e adiciona as seguintes funcionalidades:

  • VisualShortcutType.Reset: adiciona uma redefinição ao item padrão no menu de contexto, quando uma das propriedades fornecidas na matriz relatedResetFormattingIds é alterada.
  • VisualShortcutType.Toggle: adiciona opções de Exclusão ao menu de contexto. Quando clicado, o interruptor de alternância para a placa directEdit é desligado.
  • VisualShortcutType.Picker: Adiciona uma opção no menu de contexto para escolher entre Direita e Esquerda, uma vez que adicionamos a fatia de posição no cartão de formatação para o directEdit.
  • VisualShortcutType.Navigate: Quando o painel de formato é aberto e o usuário seleciona o elemento directEdit , o painel de formato rola e abre o cartão directEdit .
private getDirectEditStyles(): SubSelectionStyles {
        return {
            type: powerbi.visuals.SubSelectionStylesType.Text,
            fontFamily: {
                reference: {
                    ...directEditReferences.fontFamily
                },
                label: 'font family'
            },
            bold: {
                reference: {
                    ...directEditReferences.bold
                },
                label: 'bold'
            },
            italic: {
                reference: {
                    ...directEditReferences.italic
                },
                label: 'italic'
            },
            underline: {
                reference: {
                    ...directEditReferences.underline
                },
                label: 'underline'
            },
            fontSize: {
                reference: {
                    ...directEditReferences.fontSize
                },
                label: 'font size'
            },
            fontColor: {
                reference: {
                    ...directEditReferences.fontColor
                },
                label: 'font color'
            },
            background: {
                reference: {
                    objectName: 'directEdit',
                    propertyName: 'background'
                },
                label: 'background'
            }
        }
    }

Fornecemos as propriedades relevantes à medida que as adicionamos nas formattingSettings.

A imagem a seguir ilustra a aparência da interface do usuário ao clicar com o botão direito do mouse no elemento directEdit:

Screenshot of the direct edit interface.

Localização

O visual deve manipular a localização e fornecer cadeias de caracteres localizadas.

Recursos do GitHub

  • Todas as interfaces de formatação de objetos podem ser encontradas em (link a ser fornecido assim que a API for lançada) em on-object-formatting-api.d.ts
  • Recomendamos o uso do [on object utils], que inclui o [HTMLSubSelectionHelper](link a ser fornecido assim que a API for lançada)
  • Você pode encontrar um exemplo de um SampleBarChart visual personalizado que usa a API versão 5.8.0 e implementa o suporte para a formatação no objeto usando o on object utils at (link a ser fornecido assim que a API for lançada)