Utilidades de gráficos

ChartUtils es un conjunto de interfaces y métodos para crear ejes, etiquetas de datos y leyendas en objetos visuales de Power BI.

Instalación

Para instalar el paquete, debe ejecutar el comando siguiente en el directorio con el objeto visual actual:

npm install powerbi-visuals-utils-chartutils --save

Aplicación auxiliar de eje

La aplicación auxiliar de eje (el objeto axis de las utilidades) proporciona funciones para simplificar las manipulaciones con ejes.

El módulo proporciona las siguientes funciones:

getRecommendedNumberOfTicksForXAxis

Esta función devuelve el número recomendado de tics según el ancho del gráfico.

function getRecommendedNumberOfTicksForXAxis(availableWidth: number): number;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.getRecommendedNumberOfTicksForXAxis(1024);

// returns: 8

getRecommendedNumberOfTicksForYAxis

Esta función devuelve el número recomendado de tics según el alto del gráfico.

function getRecommendedNumberOfTicksForYAxis(availableWidth: number);

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.getRecommendedNumberOfTicksForYAxis(100);

// returns: 3

getBestNumberOfTicks

Obtiene el número óptimo de tics en función del valor mínimo, el valor máximo, los metadatos de medida y el número máximo de tics.

function getBestNumberOfTicks(
  min: number,
  max: number,
  valuesMetadata: DataViewMetadataColumn[],
  maxTickCount: number,
  isDateTime?: boolean
): number;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...
var dataViewMetadataColumnWithIntegersOnly: powerbi.DataViewMetadataColumn[] = [
  {
    displayName: "col1",
    isMeasure: true,
    type: ValueType.fromDescriptor({ integer: true })
  },
  {
    displayName: "col2",
    isMeasure: true,
    type: ValueType.fromDescriptor({ integer: true })
  }
];
var actual = axis.getBestNumberOfTicks(
  0,
  3,
  dataViewMetadataColumnWithIntegersOnly,
  6
);

// returns: 4

getTickLabelMargins

Esta función devuelve los márgenes de las etiquetas de tic.

function getTickLabelMargins(
  viewport: IViewport,
  yMarginLimit: number,
  textWidthMeasurer: ITextAsSVGMeasurer,
  textHeightMeasurer: ITextAsSVGMeasurer,
  axes: CartesianAxisProperties,
  bottomMarginLimit: number,
  properties: TextProperties,
  scrollbarVisible?: boolean,
  showOnRight?: boolean,
  renderXAxis?: boolean,
  renderY1Axis?: boolean,
  renderY2Axis?: boolean
): TickLabelMargins;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

axis.getTickLabelMargins(
  plotArea,
  marginLimits.left,
  TextMeasurementService.measureSvgTextWidth,
  TextMeasurementService.estimateSvgTextHeight,
  axes,
  marginLimits.bottom,
  textProperties,
  /*scrolling*/ false,
  showY1OnRight,
  renderXAxis,
  renderY1Axis,
  renderY2Axis
);

// returns:  xMax, yLeft, yRight, stackHeigh;

isOrdinal

Comprueba si una cadena es null, no está definida o está vacía.

function isOrdinal(type: ValueTypeDescriptor): boolean;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...
let type = ValueType.fromDescriptor({ misc: { barcode: true } });
axis.isOrdinal(type);

// returns: true

isDateTime

Comprueba si el valor es de tipo DateTime.

function isDateTime(type: ValueTypeDescriptor): boolean;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

axis.isDateTime(ValueType.fromDescriptor({ dateTime: true }));

// returns: true

getCategoryThickness

Usa la escala D3 para obtener el grosor real de la categoría.

function getCategoryThickness(scale: any): number;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

let range = [0, 100];
let domain = [0, 10];
let scale = d3.scale
  .linear()
  .domain(domain)
  .range(range);
let actualThickness = axis.getCategoryThickness(scale);

invertOrdinalScale

Esta función invierte la escala ordinal. Si x < scale.range()[0], se devuelve scale.domain()[0]. En caso contario, devuelve el mayor elemento de scale.domain() que sea <= x.

function invertOrdinalScale(scale: d3.scale.Ordinal<any, any>, x: number);

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

let domain: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  pixelSpan: number = 100,
  ordinalScale: d3.scale.ordinal = axis.createOrdinalScale(
    pixelSpan,
    domain,
    0.4
  );

axis.invertOrdinalScale(ordinalScale, 49);

// returns: 4

findClosestXAxisIndex

Esta función busca y devuelve el índice del eje X más cercano.

function findClosestXAxisIndex(
  categoryValue: number,
  categoryAxisValues: AxisHelperCategoryDataPoint[]
): number;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

/**
 * Finds the index of the category of the given x coordinate given.
 * pointX is in non-scaled screen-space, and offsetX is in render-space.
 * offsetX does not need any scaling adjustment.
 * @param {number} pointX The mouse coordinate in screen-space, without scaling applied
 * @param {number} offsetX Any left offset in d3.scale render-space
 * @return {number}
 */
private findIndex(pointX: number, offsetX?: number): number {
    // we are using mouse coordinates that do not know about any potential CSS transform scale
    let xScale = this.scaleDetector.getScale().x;
    if (!Double.equalWithPrecision(xScale, 1.0, 0.00001)) {
        pointX = pointX / xScale;
    }
    if (offsetX) {
        pointX += offsetX;
    }

    let index = axis.invertScale(this.xAxisProperties.scale, pointX);
    if (this.data.isScalar) {
        // When we have scalar data the inverted scale produces a category value, so we need to search for the closest index.
        index = axis.findClosestXAxisIndex(index, this.data.categoryData);
    }

    return index;
}

diffScaled

Esta función calcula y devuelve una diferencia de los valores de la escala.

function diffScaled(
  scale: d3.scale.Linear<any, any>,
  value1: any,
  value2: any
): number;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

var scale: d3.scale.Linear<number, number>,
    range = [0, 999],
    domain = [0, 1, 2, 3, 4, 5, 6, 7, 8, 999];

scale = d3.scale.linear()
    .range(range)
    .domain(domain);

return axis.diffScaled(scale, 0, 0));

// returns: 0

createDomain

Esta función crea un dominio de valores para un eje.

function createDomain(
  data: any[],
  axisType: ValueTypeDescriptor,
  isScalar: boolean,
  forcedScalarDomain: any[],
  ensureDomain?: NumberRange
): number[];

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

var cartesianSeries = [
  {
    data: [
      { categoryValue: 7, value: 11, categoryIndex: 0, seriesIndex: 0 },
      {
        categoryValue: 9,
        value: 9,
        categoryIndex: 1,
        seriesIndex: 0
      },
      {
        categoryValue: 15,
        value: 6,
        categoryIndex: 2,
        seriesIndex: 0
      },
      { categoryValue: 22, value: 7, categoryIndex: 3, seriesIndex: 0 }
    ]
  }
];

var domain = axis.createDomain(
  cartesianSeries,
  ValueType.fromDescriptor({ text: true }),
  false,
  []
);

// returns: [0, 1, 2, 3]

getCategoryValueType

Esta función obtiene el valor ValueType de una columna de categoría. El valor predeterminado es Text si el tipo no está presente.

function getCategoryValueType(
  data: any[],
  axisType: ValueTypeDescriptor,
  isScalar: boolean,
  forcedScalarDomain: any[],
  ensureDomain?: NumberRange
): number[];

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

var cartesianSeries = [
  {
    data: [
      { categoryValue: 7, value: 11, categoryIndex: 0, seriesIndex: 0 },
      {
        categoryValue: 9,
        value: 9,
        categoryIndex: 1,
        seriesIndex: 0
      },
      {
        categoryValue: 15,
        value: 6,
        categoryIndex: 2,
        seriesIndex: 0
      },
      { categoryValue: 22, value: 7, categoryIndex: 3, seriesIndex: 0 }
    ]
  }
];

axis.getCategoryValueType(
  cartesianSeries,
  ValueType.fromDescriptor({ text: true }),
  false,
  []
);

// returns: [0, 1, 2, 3]

createAxis

Esta función crea un eje D3 que incluye la escala. Puede ser horizontal o vertical, y de fecha y hora, numérico o texto.

function createAxis(options: CreateAxisOptions): IAxisProperties;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
import { valueFormatter } from "powerbi-visuals-utils-formattingutils";
// ...

var dataPercent = [0.0, 0.33, 0.49];

var formatStringProp: powerbi.DataViewObjectPropertyIdentifier = {
  objectName: "general",
  propertyName: "formatString"
};
let metaDataColumnPercent: powerbi.DataViewMetadataColumn = {
  displayName: "Column",
  type: ValueType.fromDescriptor({ numeric: true }),
  objects: {
    general: {
      formatString: "0 %"
    }
  }
};

var os = axis.createAxis({
  pixelSpan: 100,
  dataDomain: [dataPercent[0], dataPercent[2]],
  metaDataColumn: metaDataColumnPercent,
  formatString: valueFormatter.getFormatString(
    metaDataColumnPercent,
    formatStringProp
  ),
  outerPadding: 0.5,
  isScalar: true,
  isVertical: true
});

applyCustomizedDomain

Esta función establece un dominio personalizado, pero no cambia cuando no se establece nada.

function applyCustomizedDomain(customizedDomain: any[], forcedDomain: any[]): any[];

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

let customizedDomain = [undefined, 20],
  existingDomain = [0, 10];

axis.applyCustomizedDomain(customizedDomain, existingDomain);

// returns: {0:0, 1:20}

combineDomain

Esta función combina el dominio forzado con el dominio real si se ha establecido uno de los valores. El dominio forzado tiene prioridad. Extiende el dominio si algún punto de referencia lo requiere.

function combineDomain(
  forcedDomain: any[],
  domain: any[],
  ensureDomain?: NumberRange
): any[];

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

let forcedYDomain = this.valueAxisProperties
  ? [this.valueAxisProperties["secStart"], this.valueAxisProperties["secEnd"]]
  : null;

let xDomain = [minX, maxX];

axis.combineDomain(forcedYDomain, xDomain, ensureXDomain);

powerOfTen

Esta función indica si el número es una potencia de 10.

function powerOfTen(d: any): boolean;

Ejemplo:

import { axis } from "powerbi-visuals-utils-chartutils";
// ...

axis.powerOfTen(10);

// returns: true

DataLabelManager

DataLabelManager ayuda a crear y mantener etiquetas. Organiza los elementos de etiqueta mediante el punto de anclaje o el rectángulo. Se pueden detectar colisiones automáticamente para cambiar la posición u ocultar los elementos.

La clase DataLabelManager proporciona los métodos siguientes:

hideCollidedLabels

Este método organiza la posición y visibilidad de las etiquetas en el lienzo según los tamaños de etiqueta y la superposición.

function hideCollidedLabels(
  viewport: IViewport,
  data: any[],
  layout: any,
  addTransform: boolean = false
  hideCollidedLabels?: boolean
): LabelEnabledDataPoint[];

Ejemplo:

let dataLabelManager = new DataLabelManager();
let filteredData = dataLabelManager.hideCollidedLabels(
  this.viewport,
  values,
  labelLayout,
  true,
  true
);

IsValid

Este método estático comprueba si el rectángulo proporcionado es válido, es decir, tiene un ancho y alto positivos.

function isValid(rect: IRect): boolean;

Ejemplo:

let rectangle = {
  left: 150,
  top: 130,
  width: 120,
  height: 110
};

DataLabelManager.isValid(rectangle);

// returns: true

DataLabelUtils

DataLabelUtils proporciona utilidades para manipular etiquetas de datos.

El método proporciona las siguientes funciones, interfaces y clases:

getLabelPrecision

Esta función calcula la precisión a partir de un formato especificado.

function getLabelPrecision(precision: number, format: string): number;

getLabelFormattedText

Esta función devuelve la precisión del formato a partir del formato especificado.

function getLabelFormattedText(options: LabelFormattedTextOptions): string;

Ejemplo:

import { dataLabelUtils } from "powerbi-visuals-utils-chartutils";
// ...

let options: LabelFormattedTextOptions = {
  text: "some text",
  fontFamily: "sans",
  fontSize: "15",
  fontWeight: "normal"
};

dataLabelUtils.getLabelFormattedText(options);

enumerateDataLabels

Esta función devuelve VisualObjectInstance para las etiquetas de datos.

function enumerateDataLabels(
  options: VisualDataLabelsSettingsOptions
): VisualObjectInstance;

enumerateCategoryLabels

Esta función agrega VisualObjectInstance para las etiquetas de datos Category al objeto de enumeración.

function enumerateCategoryLabels(
  enumeration: VisualObjectInstanceEnumerationObject,
  dataLabelsSettings: VisualDataLabelsSettings,
  withFill: boolean,
  isShowCategory: boolean = false,
  fontSize?: number
): void;

createColumnFormatterCacheManager

Esta función devuelve el administrador de caché que proporciona acceso rápido a las etiquetas con formato.

function createColumnFormatterCacheManager(): IColumnFormatterCacheManager;

Ejemplo:

import { dataLabelUtils } from "powerbi-visuals-utils-chartutils";
// ...

let value: number = 200000;

labelSettings.displayUnits = 1000000;
labelSettings.precision = 1;

let formattersCache = DataLabelUtils.createColumnFormatterCacheManager();
let formatter = formattersCache.getOrCreate(null, labelSettings);
let formattedValue = formatter.format(value);

// formattedValue == "0.2M"

Servicio Leyenda

El servicio Legend proporciona interfaces auxiliares para crear y administrar las leyendas de Power BI para los objetos visuales de Power BI.

El módulo proporciona las siguientes funciones e interfaces:

createLegend

Esta función auxiliar simplifica la creación de leyendas para los objetos visuales personalizados de Power BI.

function createLegend(
  legendParentElement: HTMLElement, // top visual element, container in which legend will be created
  isScrollable: boolean = false, // indicates that legend could be scrollable or not
  legendPosition: LegendPosition = LegendPosition.Top // Position of the legend inside of legendParentElement container
): ILegend;

Ejemplo:

public constructor(options: VisualConstructorOptions) {
    this.visualInitOptions = options;
    this.layers = [];

    var element = this.element = options.element;
    var viewport = this.currentViewport = options.viewport;
    var hostServices = options.host;

    //... some other init calls

    this.legend = createLegend(
        element,
        true);
}

ILegend

Esta interfaz implementa todos los métodos necesarios para la creación de leyendas.

export interface ILegend {
  getMargins(): IViewport;
  isVisible(): boolean;
  changeOrientation(orientation: LegendPosition): void; // processing legend orientation
  getOrientation(): LegendPosition; // get information about current legend orientation
  drawLegend(data: LegendData, viewport: IViewport); // all legend rendering code is placing here
  /**
   * Reset the legend by clearing it
   */
  reset(): void;
}

drawLegend

Esta función mide el alto del texto con las propiedades de texto SVG indicadas.

function drawLegend(data: LegendData, viewport: IViewport): void;

Ejemplo:

private renderLegend(): void {
    if (!this.isInteractive) {
        let legendObjectProperties = this.data.legendObjectProperties;
        if (legendObjectProperties) {
            let legendData = this.data.legendData;
            LegendData.update(legendData, legendObjectProperties);
            let position = <string>legendObjectProperties[legendProps.position];
            if (position)
                this.legend.changeOrientation(LegendPosition[position]);

            this.legend.drawLegend(legendData, this.parentViewport);
        } else {
            this.legend.changeOrientation(LegendPosition.Top);
            this.legend.drawLegend({ dataPoints: [] }, this.parentViewport);
        }
    }
}