Chart utils
ChartUtils is a set of interfaces and methods for creating axis, data labels, and legends in Power BI Visuals.
Installation
To install the package, you should run the following command in the directory with your current visual:
npm install powerbi-visuals-utils-chartutils --save
Axis Helper
The axis helper (axis
object in utils) provides functions to simplify manipulations that have an axis.
The module provides the following functions:
getRecommendedNumberOfTicksForXAxis
This function returns the recommended number of ticks according to the width of chart.
function getRecommendedNumberOfTicksForXAxis(availableWidth: number): number;
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.getRecommendedNumberOfTicksForXAxis(1024);
// returns: 8
getRecommendedNumberOfTicksForYAxis
This function returns the recommended number of ticks according to the height of chart.
function getRecommendedNumberOfTicksForYAxis(availableWidth: number);
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.getRecommendedNumberOfTicksForYAxis(100);
// returns: 3
getBestNumberOfTicks
Gets the optimal number of ticks based on minimum value, maximum value, measure metadata, and max tick count.
function getBestNumberOfTicks(
min: number,
max: number,
valuesMetadata: DataViewMetadataColumn[],
maxTickCount: number,
isDateTime?: boolean
): number;
Example:
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
This function returns the margins for tick labels.
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;
Example:
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
Checks if a string is null, undefined or empty.
function isOrdinal(type: ValueTypeDescriptor): boolean;
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
let type = ValueType.fromDescriptor({ misc: { barcode: true } });
axis.isOrdinal(type);
// returns: true
isDateTime
Checks if a value is of the DateTime type.
function isDateTime(type: ValueTypeDescriptor): boolean;
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.isDateTime(ValueType.fromDescriptor({ dateTime: true }));
// returns: true
getCategoryThickness
Uses the D3 scale to get the actual category thickness.
function getCategoryThickness(scale: any): number;
Example:
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
This function inverts the ordinal scale. If x < scale.range()[0], then scale.domain()[0] is returned. Otherwise, it returns the greatest item in scale.domain() that's <= x.
function invertOrdinalScale(scale: d3.scale.Ordinal<any, any>, x: number);
Example:
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
This function finds and returns the closest x-axis index.
function findClosestXAxisIndex(
categoryValue: number,
categoryAxisValues: AxisHelperCategoryDataPoint[]
): number;
Example:
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
This function computes and returns a diff of values in the scale.
function diffScaled(
scale: d3.scale.Linear<any, any>,
value1: any,
value2: any
): number;
Example:
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
This function creates a domain of values for an axis.
function createDomain(
data: any[],
axisType: ValueTypeDescriptor,
isScalar: boolean,
forcedScalarDomain: any[],
ensureDomain?: NumberRange
): number[];
Example:
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
This function gets the ValueType
of a category column. Default is Text
if the type isn't present.
function getCategoryValueType(
data: any[],
axisType: ValueTypeDescriptor,
isScalar: boolean,
forcedScalarDomain: any[],
ensureDomain?: NumberRange
): number[];
Example:
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
This function creates a D3 axis including scale. Can be vertical or horizontal, and either datetime, numeric, or text.
function createAxis(options: CreateAxisOptions): IAxisProperties;
Example:
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
This function sets a customized domain, but it doesn't change when nothing is set.
function applyCustomizedDomain(customizedDomain: any[], forcedDomain: any[]): any[];
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
let customizedDomain = [undefined, 20],
existingDomain = [0, 10];
axis.applyCustomizedDomain(customizedDomain, existingDomain);
// returns: {0:0, 1:20}
combineDomain
This function combines the forced domain with the actual domain if one of the values was set. The forcedDomain is in first priority. Extends the domain if any reference point requires it.
function combineDomain(
forcedDomain: any[],
domain: any[],
ensureDomain?: NumberRange
): any[];
Example:
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
This function indicates whether the number is power of 10.
function powerOfTen(d: any): boolean;
Example:
import { axis } from "powerbi-visuals-utils-chartutils";
// ...
axis.powerOfTen(10);
// returns: true
DataLabelManager
The DataLabelManager
helps to create and maintain labels. It arranges label elements using the anchor point or rectangle. Collisions can be automatically detected to reposition or hide elements.
The DataLabelManager
class provides the following methods:
hideCollidedLabels
This method arranges the labels position and visibility on the canvas according to label sizes and overlapping.
function hideCollidedLabels(
viewport: IViewport,
data: any[],
layout: any,
addTransform: boolean = false
hideCollidedLabels?: boolean
): LabelEnabledDataPoint[];
Example:
let dataLabelManager = new DataLabelManager();
let filteredData = dataLabelManager.hideCollidedLabels(
this.viewport,
values,
labelLayout,
true,
true
);
IsValid
This static method checks if the provided rectangle is valid, that is, it has positive width and height.
function isValid(rect: IRect): boolean;
Example:
let rectangle = {
left: 150,
top: 130,
width: 120,
height: 110
};
DataLabelManager.isValid(rectangle);
// returns: true
DataLabelUtils
The DataLabelUtils
provides utils to manipulate data labels.
The method provides the following functions, interfaces, and classes:
getLabelPrecision
This function calculates precision from a provided format.
function getLabelPrecision(precision: number, format: string): number;
getLabelFormattedText
This function returns format precision from the provided format.
function getLabelFormattedText(options: LabelFormattedTextOptions): string;
Example:
import { dataLabelUtils } from "powerbi-visuals-utils-chartutils";
// ...
let options: LabelFormattedTextOptions = {
text: "some text",
fontFamily: "sans",
fontSize: "15",
fontWeight: "normal"
};
dataLabelUtils.getLabelFormattedText(options);
enumerateDataLabels
This function returns VisualObjectInstance for data labels.
function enumerateDataLabels(
options: VisualDataLabelsSettingsOptions
): VisualObjectInstance;
enumerateCategoryLabels
This function adds VisualObjectInstance for Category data labels to an enumeration object.
function enumerateCategoryLabels(
enumeration: VisualObjectInstanceEnumerationObject,
dataLabelsSettings: VisualDataLabelsSettings,
withFill: boolean,
isShowCategory: boolean = false,
fontSize?: number
): void;
createColumnFormatterCacheManager
This function returns the Cache Manager that provides quick access to formatted labels.
function createColumnFormatterCacheManager(): IColumnFormatterCacheManager;
Example:
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"
Legend service
The Legend
service provides helper interfaces for creating and managing Power BI legends for Power BI visuals.
The module provides the following functions and interfaces:
createLegend
This helper function simplifies Power BI Custom Visual legend creation.
function createLegend(
legendParentElement: HTMLElement, // top visual element, container in which legend will be created
interactive: boolean, // indicates that legend should be interactive
interactivityService: IInteractivityService, // reference to IInteractivityService interface which need to create legend click events
isScrollable: boolean = false, // indicates that legend could be scrollable or not
legendPosition: LegendPosition = LegendPosition.Top // Position of the legend inside of legendParentElement container
): ILegend;
Example:
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
if (this.behavior) {
this.interactivityService = createInteractivityService(hostServices);
}
this.legend = createLegend(
element,
options.interactivity && options.interactivity.isInteractiveLegend,
this.interactivityService,
true);
}
ILegend
This Interface implements all methods necessary for legend creation.
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
This function measures the height of the text with the given SVG text properties.
function drawLegend(data: LegendData, viewport: IViewport): void;
Example:
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);
}
}
}
Related content
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for