private static createDataPoints()

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;
    }