in src/columnChart/baseColumnChart.ts [627:1127]
private static createDataPoints(
visualHost: IVisualHost,
dataViewCat: DataViewCategorical,
categories: any[],
categoryIdentities: CustomVisualOpaqueIdentity[],
legend: MekkoLegendDataPoint[],
seriesObjectsList: powerbi.DataViewObjects[][],
converterStrategy: BaseConverterStrategy,
defaultLabelSettings: VisualDataLabelsSettings,
is100PercentStacked: boolean = false,
isScalar: boolean = false,
supportsOverflow: boolean = false,
localizationManager: ILocalizationManager,
isCategoryAlsoSeries?: boolean,
categoryObjectsList?: powerbi.DataViewObjects[],
defaultDataPointColor?: string,
chartType?: MekkoVisualChartType,
categoryMetadata?: DataViewMetadataColumn): MekkoDataPoints {
const grouped: DataViewValueColumnGroup[] = dataViewCat && dataViewCat.values
? dataViewCat.values.grouped()
: undefined;
const categoryCount = categories.length,
seriesCount = legend.length,
columnSeries: MekkoChartSeries[] = [];
if (seriesCount < 1
|| categoryCount < 1
|| (categories[0] === null && categories[1] === undefined)) {
return {
series: columnSeries,
hasHighlights: false,
hasDynamicSeries: false,
categoriesWidth: [],
};
}
const dvCategories: DataViewCategoryColumn[] = dataViewCat.categories;
categoryMetadata = (dvCategories && dvCategories.length > 0)
? dvCategories[0].source
: null;
const categoryType: ValueType = AxisHelper.getCategoryValueType(categoryMetadata),
isDateTime: boolean = AxisHelper.isDateTime(categoryType),
baseValuesPos: number[] = [],
baseValuesNeg: number[] = [],
rawHighlightValues: number[][] = [],
hasDynamicSeries = !!(dataViewCat.values && dataViewCat.values.source),
widthColumns: number[] = [];
let rawValues: number[][] = [],
widthIndex: number = -1;
let highlightsOverflow: boolean = false,
hasHighlights: boolean = converterStrategy.hasHighlightValues(0);
for (let seriesIndex: number = 0; seriesIndex < dataViewCat.values.length; seriesIndex++) {
if (dataViewCat.values[seriesIndex].source.roles
&& dataViewCat.values[seriesIndex].source.roles[RoleNames.width]
&& !dataViewCat.values[seriesIndex].source.roles[RoleNames.y]) {
widthIndex = seriesIndex;
const widthValues: number[] = dataViewCat.values[seriesIndex].values as number[];
for (let i: number = 0; i < widthValues.length; i++) {
widthColumns[i] = sum([
0,
widthColumns[i],
widthValues[i]
]);
}
continue;
}
const seriesValues: number[] = [],
seriesHighlightValues: number[] = [];
for (let categoryIndex: number = 0; categoryIndex < categoryCount; categoryIndex++) {
const value: number = converterStrategy.getValueBySeriesAndCategory(
seriesIndex,
categoryIndex);
seriesValues[categoryIndex] = value;
if (hasHighlights) {
const highlightValue: number = converterStrategy.getHighlightBySeriesAndCategory(
seriesIndex,
categoryIndex);
seriesHighlightValues[categoryIndex] = highlightValue;
// There are two cases where we don't use overflow logic; if all are false, use overflow logic appropriate for the chart.
if (!((value >= 0 && highlightValue >= 0 && value >= highlightValue) || // Both positive; value greater than highlight
(value <= 0 && highlightValue <= 0 && value <= highlightValue))) { // Both negative; value less than highlight
highlightsOverflow = true;
}
}
}
rawValues.push(seriesValues);
if (hasHighlights) {
rawHighlightValues.push(seriesHighlightValues);
}
}
if (highlightsOverflow && !supportsOverflow) {
highlightsOverflow = false;
hasHighlights = false;
rawValues = rawHighlightValues;
}
if (widthColumns.length < 1) {
for (let seriesIndex: number = 0; seriesIndex < dataViewCat.values.length; seriesIndex++) {
if (dataViewCat.values[seriesIndex].source.roles
&& dataViewCat.values[seriesIndex].source.roles[RoleNames.width]) {
widthIndex = seriesIndex;
const widthValues: number[] = dataViewCat.values[seriesIndex].values as number[];
for (let i: number = 0; i < widthValues.length; i++) {
widthColumns[i] = sum([
0,
widthColumns[i],
widthValues[i]
]);
}
continue;
}
}
}
if (widthColumns.length < 1) {
for (let seriesIndex: number = 0; seriesIndex < categoryCount; seriesIndex++) {
widthColumns.push(1);
}
}
const totalSum: number = sum(widthColumns),
linearScale: LinearScale<number, number> = scaleLinear()
.domain([0, totalSum])
.range([0, 1]);
const columnStartX: number[] = [0],
columnWidth: number[] = [];
for (let seriesIndex: number = 0; seriesIndex < (categoryCount - 1); seriesIndex++) {
const stepWidth: number = columnStartX[columnStartX.length - 1]
+ (widthColumns[seriesIndex] || 0);
columnStartX.push(stepWidth);
}
for (let seriesIndex: number = 0; seriesIndex < categoryCount; seriesIndex++) {
columnStartX[seriesIndex] = linearScale(columnStartX[seriesIndex]);
columnWidth[seriesIndex] = linearScale(widthColumns[seriesIndex]);
}
let dataPointObjects: powerbi.DataViewObjects[] = categoryObjectsList;
let multipliersAllData: ValueMultiplers = BaseColumnChart.getStackedMultiplierForAllDataSet(rawValues, seriesCount, categoryCount);
for (let seriesIndex: number = 0; seriesIndex < seriesCount; seriesIndex++) {
let seriesDataPoints: MekkoChartColumnDataPoint[] = [],
legendItem: MekkoLegendDataPoint = legend[seriesIndex],
seriesLabelSettings: VisualDataLabelsSettings;
if (!hasDynamicSeries) {
const labelsSeriesGroup: DataViewValueColumn = grouped
&& grouped.length > 0
&& grouped[0].values
? grouped[0].values[seriesIndex]
: null;
const labelObjects: DataLabelObject = labelsSeriesGroup
&& labelsSeriesGroup.source
&& labelsSeriesGroup.source.objects
? labelsSeriesGroup.source.objects["labels"] as any
: null;
if (labelObjects) {
seriesLabelSettings = Prototype.inherit(defaultLabelSettings);
dataLabelUtils.updateLabelSettingsFromLabelsObject(
labelObjects,
seriesLabelSettings);
}
}
const series: MekkoChartSeries = {
displayName: legendItem.label,
key: `series${seriesIndex}`,
index: seriesIndex,
data: seriesDataPoints,
identity: legendItem.identity as ISelectionId,
color: legendItem.color,
labelSettings: seriesLabelSettings,
selected: false
};
if (seriesCount > 1) {
dataPointObjects = seriesObjectsList[seriesIndex];
}
const metadata: DataViewMetadataColumn = dataViewCat.values[seriesIndex].source;
for (let categoryIndex: number = 0; categoryIndex < categoryCount; categoryIndex++) {
if (seriesIndex === 0) {
baseValuesPos.push(0);
baseValuesNeg.push(0);
}
let value: number = AxisHelper.normalizeNonFiniteNumber(
rawValues[seriesIndex][categoryIndex]);
if (value == null && seriesIndex > 0) {
continue;
}
let originalValue: number = value,
categoryValue: any = categories[categoryIndex];
if (isDateTime && categoryValue) {
categoryValue = categoryValue.getTime();
}
if (isScalar && (categoryValue == null || isNaN(categoryValue))) {
continue;
}
let multipliers: ValueMultiplers;
if (is100PercentStacked) {
multipliers = BaseColumnChart.getStackedMultiplier(
rawValues,
categoryIndex,
seriesCount,
categoryCount);
}
let unadjustedValue: number = value,
isNegative: boolean = value < 0;
if (multipliers) {
if (isNegative) {
value *= multipliers.neg;
} else {
value *= multipliers.pos;
}
}
let valueByAllData = originalValue;
if (multipliersAllData) {
if (isNegative) {
valueByAllData *= multipliersAllData.neg;
} else {
valueByAllData *= multipliersAllData.pos;
}
}
let valueAbsolute: number = Math.abs(value);
let position: number;
let valueAbsoluteByAllData: number = Math.abs(valueByAllData);
if (isNegative) {
position = baseValuesNeg[categoryIndex];
if (!isNaN(valueAbsolute)) {
baseValuesNeg[categoryIndex] -= valueAbsolute;
}
}
else {
if (!isNaN(valueAbsolute)) {
baseValuesPos[categoryIndex] += valueAbsolute;
}
position = baseValuesPos[categoryIndex];
}
const columnGroup: DataViewValueColumnGroup = grouped
&& grouped.length > seriesIndex
&& grouped[seriesIndex].values
? grouped[seriesIndex]
: null;
const category: DataViewCategoryColumn = dataViewCat.categories
&& dataViewCat.categories.length > 0
? dataViewCat.categories[0]
: null;
const identity: ISelectionId = visualHost.createSelectionIdBuilder()
.withCategory(category, categoryIndex)
.withSeries(dataViewCat.values, columnGroup)
.withMeasure(converterStrategy.getMeasureNameByIndex(seriesIndex))
.createSelectionId();
let color: string = BaseColumnChart.getDataPointColor(
legendItem,
categoryIndex,
dataPointObjects
);
const seriesData: tooltip.TooltipSeriesDataItem[] = [];
if (columnGroup) {
const seriesValueColumn: DataViewValueColumn = {
values: [],
source: dataViewCat.values.source,
};
seriesData.push({
value: columnGroup.name,
metadata: seriesValueColumn,
});
for (let columnIndex: number = 0; columnIndex < columnGroup.values.length; columnIndex++) {
const columnValues: DataViewValueColumn = columnGroup.values[columnIndex];
seriesData.push({
value: columnValues.values[categoryIndex],
metadata: columnValues,
});
}
}
let rawCategoryValue: any = categories[categoryIndex];
let tooltipInfo: VisualTooltipDataItem[] = tooltip.createTooltipInfo(
null,
rawCategoryValue,
localizationManager,
originalValue,
[category],
seriesData,
null,
categoryIndex);
const dataPointLabelSettings: VisualDataLabelsSettings = series && series.labelSettings
? series.labelSettings
: defaultLabelSettings;
let labelColor: string = dataPointLabelSettings.labelColor,
lastValue: boolean = undefined;
// Stacked column/bar label color is white by default (except last series)
if ((EnumExtensions.hasFlag(chartType, flagStacked))) {
lastValue = this.getStackedLabelColor(
isNegative,
seriesIndex,
seriesCount,
categoryIndex,
rawValues);
labelColor = lastValue || (seriesIndex === seriesCount - 1 && !isNegative)
? labelColor
: dataLabelUtils.defaultInsideLabelColor;
}
value = columnWidth[categoryIndex];
let originalPosition: number = columnStartX[categoryIndex],
dataPoint: MekkoChartColumnDataPoint = {
categoryValue,
value,
position,
valueAbsolute,
categoryIndex,
color,
seriesIndex,
chartType,
identity,
tooltipInfo,
originalPosition,
valueOriginal: unadjustedValue,
labelSettings: dataPointLabelSettings,
selected: false,
originalValue: value,
originalValueAbsolute: valueAbsolute,
originalValueAbsoluteByAlLData: valueAbsoluteByAllData,
key: identity.getKey(),
labelFill: labelColor,
labelFormatString: metadata.format,
lastSeries: lastValue
};
seriesDataPoints.push(dataPoint);
if (hasHighlights) {
let valueHighlight: number = rawHighlightValues[seriesIndex][categoryIndex],
unadjustedValueHighlight: number = valueHighlight;
let highlightedTooltip: boolean = true;
if (valueHighlight === null) {
valueHighlight = 0;
highlightedTooltip = false;
}
if (is100PercentStacked) {
valueHighlight *= multipliers.pos;
}
let absoluteValueHighlight: number = Math.abs(valueHighlight),
highlightPosition: number = position;
if (valueHighlight > 0) {
highlightPosition -= valueAbsolute - absoluteValueHighlight;
}
else if (valueHighlight === 0 && value > 0) {
highlightPosition -= valueAbsolute;
}
rawCategoryValue = categories[categoryIndex];
let highlightedValue: number = highlightedTooltip
? valueHighlight
: undefined;
tooltipInfo = tooltip.createTooltipInfo(
dataViewCat,
rawCategoryValue,
localizationManager,
originalValue,
null,
null,
seriesIndex,
categoryIndex,
highlightedValue);
if (highlightedTooltip) {
dataPoint.tooltipInfo = tooltipInfo;
}
const highlightDataPoint: MekkoChartColumnDataPoint = {
categoryValue,
value,
seriesIndex,
categoryIndex,
color,
originalPosition,
identity,
chartType,
tooltipInfo,
position: highlightPosition,
valueAbsolute: absoluteValueHighlight,
valueOriginal: unadjustedValueHighlight,
labelSettings: dataPointLabelSettings,
selected: false,
highlight: true,
originalValue: value,
originalValueAbsolute: valueAbsolute,
drawThinner: highlightsOverflow,
key: `${identity.getKey()}${BaseColumnChart.HighlightedKeyPostfix}`,
labelFormatString: metadata.format,
labelFill: labelColor,
lastSeries: lastValue
};
seriesDataPoints.push(highlightDataPoint);
}
}
columnSeries.push(series);
}
let result: MekkoDataPoints = {
series: columnSeries,
categoriesWidth: columnWidth,
hasHighlights: hasHighlights,
hasDynamicSeries: hasDynamicSeries
};
let categoryProperties: MekkoCategoryProperties[] = [];
result.series.forEach((series) => {
if (series.data.length !== 1) {
return;
}
if (categoryProperties[series.data[0].categoryIndex] === undefined) {
categoryProperties[series.data[0].categoryIndex] = {
valueAbsolute: 0
};
}
if (series.data[0] !== undefined && series.data[0].valueAbsolute > categoryProperties[series.data[0].categoryIndex].valueAbsolute) {
categoryProperties[series.data[0].categoryIndex].valueAbsolute = series.data[0].valueAbsolute;
categoryProperties[series.data[0].categoryIndex].color = series.data[0].color;
categoryProperties[series.data[0].categoryIndex].name = (series.data[0].categoryValue || "").toString();
categoryProperties[series.data[0].categoryIndex].series = series;
categoryProperties[series.data[0].categoryIndex].identity = series.identity;
}
});
result.categoryProperties = categoryProperties;
return result;
}