public update()

in src/barChart.ts [499:969]


    public update(options: VisualUpdateOptions) {

        // bar chart diagram
        //  ________________________________   _
        //  |                               |  |
        //  |                               |  |  top and bottom padding (calcOuterPadding, outerPadding)
        //  |_______________________        |  _    _
        //  |                       |       |  |    |
        //  |                       |       |  |    |
        //  |_______________________|       |  _    |   x (calcX, xScaledMin, xScaledMax)
        //  |                               |  |    |   It is sum of the bar height
        //  |                               |  |    |   and the space between two bars
        //  |                               |  |    |
        //  |_____________________          |  _    _
        //  |                     |         |  |
        //  |                     |         |  | h
        //  |_____________________|         |  _
        //  |                               |  | padding between bars (barPadding).
        //  |                               |  | This is percent of x that will be
        //  |                               |  | used for padding and th rest will be bar height.
        //  |_________________              |  _
        //  |                 |             |  |
        //  |                 |             |  | h
        //  |_________________|             |  _
        //  |                               |  |
        //  |                               |  |  top and bottom padding are equal
        //  |_______________________________|  _

        this.events.renderingStarted(options);
        let viewModel: IBarChartViewModel = visualTransform(options, this.host);

        let settings = this.IBarChartSettings = viewModel.settings;
        let width = options.viewport.width;
        let height = options.viewport.height;

        // Calculate max height of each bar based on the total height of the visual
        let xScaledMax = height / BarChart.Config.maxHeightScale;
        // Min height is independent of height the bar should be visible
        // irrespective of the number of bars or the height of the visual
        let xScaledMin = BarChart.Config.xScaledMin;
        if (settings.barHeight && settings.barHeight.show) {

            xScaledMin = settings.barHeight.height;
        } else {
            xScaledMin = BarChart.Config.xScaledMin;
        }
        let outerPadding = -0.1;
        // calcX is the calculated height of the bar+inner padding that will be required if we simply
        // distribute the height with the bar count (no scrolling)
        let calcX = height /
            (2 * BarChart.Config.outerPaddingScale - BarChart.Config.xScalePadding + viewModel.dataPoints.length);
        // calcHeight is the height required for the entire bar chart
        // if min allowed bar height is used. (This is needed for setting the scroll height)
        let calcHeight = (-2 * outerPadding - BarChart.Config.xScalePadding + viewModel.dataPoints.length)
             * xScaledMin;
        // The parent element is not directly available to us since we are in a sandbox

        if (calcX > xScaledMax) {
            if (xScaledMax >= xScaledMin) {
                let tempouterPadding = (height - (-BarChart.Config.xScalePadding + viewModel.dataPoints.length)
                     * xScaledMax) /
                    (2 * xScaledMax);
                if (tempouterPadding > 0) {
                    outerPadding = tempouterPadding;
                }
            } else {
                let tempOuterPadding = (height - (-BarChart.Config.xScalePadding + viewModel.dataPoints.length)
                     * xScaledMin) /
                    (2 * xScaledMin);
                if (tempOuterPadding > 0) {
                    outerPadding = tempOuterPadding;
                }
            }
        } else {
            if (calcX < xScaledMin && calcHeight > height) {
                height = calcHeight;
            }
        }
        let h = options.viewport.height + 5;
        let w = options.viewport.width;
        this.divContainer.attr("style", "width:" + w + "px;height:" + h + "px;overflow-y:auto;overflow-x:hidden;");

        this.svg.attr("width", width);
        this.svg.attr("height", height);

        this.xAxis.style("font-size", parseInt(min(<any>[height, width]), 10) * BarChart.Config.xAxisFontMultiplier);
        // this.xAxis.attr("font-size",parseInt(min([height, width])) * BarChart.Config.xAxisFontMultiplier)
        let yScale = scaleBand()
            .domain(viewModel.dataPoints.map((d) => d.category))
            .rangeRound([5, height])
            .padding(BarChart.Config.barPadding)
            .paddingOuter(outerPadding);
        // .rangeBands([5, height], BarChart.Config.barPadding, outerPadding);

        // cap the fontsize between 8.5 and 40 for aesthetics (only when autoscaling font)
        let fontSizeToUse = this.IBarChartSettings.fontParams && this.IBarChartSettings.fontParams.show
            ? this.IBarChartSettings.fontParams.fontSize
            : yScale.bandwidth() / BarChart.Config.fontScaleFactor;
        if (fontSizeToUse < 8.5 && !this.IBarChartSettings.fontParams.show) {
            fontSizeToUse = 8.5;
        }
        if (fontSizeToUse > 40 && !this.IBarChartSettings.fontParams.show) {
            fontSizeToUse = 40;
        }

        // Calculate label size to compute max bar size to use
        //  to leave room for label to be displayed inside the draw area for the .
        // Use the formatted value for the longest bar
        let indexForDataMax = getIndexForDataMax(viewModel.dataPoints);
        let formattedValue = viewModel.dataPoints.length > 0
            ? viewModel.dataPoints[indexForDataMax].formattedValue
            : "";

        let textProperties: ITextProperties = {
            fontFamily: "sans-serif",
            fontSize: fontSizeToUse + "px",
            text: formattedValue,
        };
        let offset = textMeasurementService.textMeasurementService.measureSvgTextWidth(textProperties);
        let xScale = scaleLinear()
            .domain([0, viewModel.dataMax])
            .range([0, width - offset - 40]); // subtracting 40 for padding between the bar and the label

        // empty rect to take full width for clickable area for clearing selection

        let rectContainer = this.barContainer.selectAll("rect.rect-container").data([0]);

        rectContainer
            .enter()
            .append<SVGElement> ("rect")
            .classed("rect-container", true);

        rectContainer.attr("width", width);
        rectContainer.attr("height", height);
        rectContainer.attr("fill", "transparent");

        let bars = this.barContainer
            .selectAll("g.bar")
            .data(viewModel.dataPoints);

        if (viewModel.dataPoints.length === 0) {
            let removeBars = this.barContainer.selectAll("g.bar");
            removeBars.selectAll("rect.bar").remove();
            removeBars.selectAll("rect.overlapBar").remove();
            removeBars.selectAll("circle").remove();
            removeBars.selectAll("line").remove();
            removeBars.selectAll("text.bar-value").remove();
            removeBars.selectAll("text.bar-text").remove();
            removeBars.selectAll("rect.valuesRect").remove();
            removeBars.remove();
        }

        bars
            .enter()
            .append<SVGElement> ("g")
            .classed("bar", true)
            .attr("x", BarChart.Config.xScalePadding)// .merge(bars)
            .attr("y", (d) => yScale(d.category))
            .attr("height", yScale.bandwidth())
            .attr("width", (d) => xScale(<number> d.value))

            .attr("selected", (d) => d.selected);

        bars = this.barContainer
            .selectAll("g.bar")
            .data(viewModel.dataPoints);

        let rects = bars
            .selectAll("rect.bar").data((d) => [d]);

        let mergeElement = rects
            .enter()
            .append<SVGElement> ("rect")
            .classed("bar", true);

        rects
            .merge(mergeElement)
            .attr("x", BarChart.Config.xScalePadding)
            .attr("y", (d) => yScale(d.category))
            .attr("height", yScale.bandwidth() /
                (
                    (settings.barShape.shape === "Line" ||
                     settings.barShape.shape === "Lollipop" ||
                     settings.barShape.shape === "Hammer Head") ? 8 : 1
                ))
            .attr("width", (d) => xScale(<number> d.value))
            .attr("fill", viewModel.settings.generalView.barsColor.solid.color)
            .attr("fill-opacity", viewModel.settings.generalView.opacity / 100)
            .attr("selected", (d) => d.selected);

        rects.exit().remove();

        let overlapRects = bars.selectAll("rect.overlapBar").data((d) => [d]);

        mergeElement = overlapRects
            .enter()
            .append<SVGElement> ("rect")
            .classed("overlapBar", true);

        overlapRects
            .merge(mergeElement)
            // overlapRects
            .attr("x", BarChart.Config.xScalePadding)
            .attr("y", (d) => yScale(d.category))
            .attr("height", yScale.bandwidth() /
                (
                    (
                        settings.barShape.shape === "Line" ||
                        settings.barShape.shape === "Lollipop" ||
                        settings.barShape.shape === "Hammer Head"
                    ) ? 8 : 1))
            .attr("width", (d) => xScale(<number> d.overlapValue)).merge(mergeElement)
            .attr("fill", viewModel.settings.generalView.overlapColor.solid.color)
            .attr("fill-opacity", viewModel.settings.generalView.opacity / 100)
            .attr("selected", (d) => d.selected);

        overlapRects.exit().remove();

        if (settings.barShape.shape === "Lollipop") {
            let circle = bars.selectAll("circle").data((d) => [d]);

            mergeElement = circle.enter()
                .append<SVGElement> ("circle")
                .classed("head", true);

            circle
                .merge(mergeElement)
                .attr("cx", (d) => getHeadPositionX(d.value, d.width) - 2 - yScale.bandwidth() / 8)
                .attr("cy", (d) => yScale(d.category) + yScale.bandwidth() / 16)
                // - textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties) / 4,
                .attr("r", yScale.bandwidth() / 8)
                .attr("fill", viewModel.settings.barShape.headColor.solid.color)
                .attr("fill-opacity", viewModel.settings.generalView.opacity / 100);
            circle.exit().remove();
        } else {
            bars.selectAll("circle").remove();
        }

        if (settings.barShape.shape === "Hammer Head") {
            let line = bars.selectAll("line").data((d) => [d]);

            mergeElement = line.enter()
                .append<SVGElement> ("line")
                .classed("head", true);

            line.merge(mergeElement)
                .attr("x1", (d) => getHeadPositionX(d.value, d.width) - 7 - yScale.bandwidth() / 32)
                .attr("x2", (d) => getHeadPositionX(d.value, d.width) - 7 - yScale.bandwidth() / 32)
                .attr("y1", (d) => yScale(d.category) - yScale.bandwidth() / 16)
                // - textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties) / 4,
                .attr("y2", (d) => yScale(d.category) + yScale.bandwidth() / 16 + yScale.bandwidth() / 8)
                // - textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties) / 4,
                .attr("stroke-width", yScale.bandwidth() / 16)
                .attr("stroke", viewModel.settings.barShape.headColor.solid.color)
                .attr("stroke-opacity", viewModel.settings.generalView.opacity / 100);
            line.exit().remove();
        } else {
            bars.selectAll("line").remove();
        }

        textProperties = {
            fontFamily: "Segoe UI",
            fontSize: fontSizeToUse + "px",
            text: "TEXT for calculating height",
        };

        let texts = bars
            .selectAll("text.bar-text").data((d) => [d]);

        mergeElement = texts
            .enter()
            .append<SVGElement> ("text")
            .classed("bar-text", true);

        texts.merge(mergeElement)
            .attr("height", yScale.bandwidth())
            .attr("y", (d) => yScale(d.category) + yScale.bandwidth() /
                2 + textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties) / 4)
            .attr("x", 5)
            .attr("font-size", fontSizeToUse)
            .attr("fill", viewModel.settings.generalView.textColor.solid.color)

            .text((d) => d.category )
            .each((d) => d.width = xScale(<number> d.value));
        if (this.IBarChartSettings.experimental.show) {
            texts.attr("style", "mix-blend-mode: " + this.IBarChartSettings.experimental.blendMode);
        } else {
            texts.attr("style", "mix-blend-mode: initial");
        }

        texts.exit().remove();

        if (viewModel.settings.showBarLabels.show) {

            let valuesRect = bars.selectAll("rect.valuesRect").data((d) => [d]);

            mergeElement = valuesRect
                .enter()
                .append<SVGElement> ("rect")
                .classed("valuesRect", true);

            valuesRect
                .merge(mergeElement)
                .attr("x", (d) => viewModel.settings.alignBarLabels.show
                    ? getTextPositionX(viewModel.dataMax, d.currTextWidth) - 2
                    : getTextPositionX(d.value, d.currTextWidth) - 2)
                .attr("y", (d) => getTextPositionY(d.category, textProperties) - 3
                    / 4 * textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties))
                .attr("height", textMeasurementService.textMeasurementService.measureSvgTextHeight(textProperties))

                // width is adding 5 for padding around text
                .attr("width", (d) => 5 + textMeasurementService.textMeasurementService.measureSvgTextWidth(
                    {
                        fontFamily: "Segoe UI",
                        fontSize: fontSizeToUse + "px",
                        text: <string> d.formattedValue,
                    }))
                .attr("fill", viewModel.settings.showBarLabels.highlightColor.solid.color)
                .attr("fill-opacity", viewModel.settings.generalView.opacity / 100)
                .attr("rx", 2)
                .attr("ry", 2);

            valuesRect.exit().remove();

            let textValues = bars
                .selectAll("text.bar-value").data((d) => [d]);

            mergeElement = textValues
                .enter()
                .append<SVGElement> ("text")
                .classed("bar-value", true);

            textValues.merge(mergeElement).attr("height", yScale.bandwidth())
                .attr("y", (d) => getTextPositionY(d.category, textProperties))
                .attr("x", (d) => {
                    return viewModel.settings.alignBarLabels.show
                        ? getTextPositionX(viewModel.dataMax, d.currTextWidth)
                        : getTextPositionX(d.value, d.currTextWidth);
                })
                .attr("font-size", fontSizeToUse)
                .attr("fill", viewModel.settings.showBarLabels.textColor.solid.color)
                .text((d) => { return <string> d.formattedValue; });
            textValues.exit().remove();
        } else {
            let valuesRect = bars.selectAll("rect.valuesRect")
            let textValues = bars.selectAll("text.bar-value")
            valuesRect.remove()
            textValues.remove()
        }
        else {
            let valuesRect = bars.selectAll("rect.valuesRect")
            let textValues = bars.selectAll("text.bar-value")
            valuesRect.remove()
            textValues.remove()
        }

        this.tooltipServiceWrapper.addTooltip(this.barContainer.selectAll(".bar"),
            (tooltipEvent: ITooltipEventArgs<IBarChartDataPoint>) => this.getTooltipData(tooltipEvent.data),
            (tooltipEvent: ITooltipEventArgs<IBarChartDataPoint>) => tooltipEvent.data.selectionId,
        );

        this.syncSelectionState(
            bars,
            this.selectionManager.getSelectionIds() as ISelectionId[],
        );
        this.syncSelectionState(
            rects,
            this.selectionManager.getSelectionIds() as ISelectionId[],
        );
        this.syncSelectionState(
            overlapRects,
            this.selectionManager.getSelectionIds() as ISelectionId[],
        );

        let selectionManager = this.selectionManager;
        // this.svg.on("contextmenu", () => {

        //     const mouseEvent: MouseEvent = event as MouseEvent;
        //     const eventTarget: EventTarget = mouseEvent.target;
        //     let dataPoint = select(eventTarget).datum();
        //     selectionManager.showContextMenu(dataPoint ? dataPoint.selectionId : {}, {
        //         x: mouseEvent.clientX,
        //         y: mouseEvent.clientY
        //     });
        //     mouseEvent.preventDefault();
        // });

        // This must be an anonymous function instead of a lambda because
        // d3 uses "this" as the reference to the element that was clicked.

        let area = select("rect.rect-container");
        area.on("click", () => {

            if (self.IBarChartSettings.clearFilters.show && selectionManager.hasSelection()) {
                selectionManager.clear().then(() => {
                    self.syncSelectionState(bars, []);
                    self.syncSelectionState(rects, []);
                    self.syncSelectionState(overlapRects, []);
                });
            }

            bars.attr("fill-opacity", BarChart.Config.solidOpacity);
            rects.attr("fill-opacity", BarChart.Config.solidOpacity);
            overlapRects.attr("fill-opacity", BarChart.Config.solidOpacity);

        });
        let self: this = this;

        bars.on("click", (d) => {

            // set selected property of the attached data to false for all (later mark the one clicked as selected)

            selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {
                self.syncSelectionState(bars, ids);
                self.syncSelectionState(rects, ids);
                self.syncSelectionState(overlapRects, ids);
            });
        });
        bars.exit()
            .remove();

        function getTextPositionX(value: PrimitiveValue, wid: number) {
            if (settings.barShape.shape === "Bar") {
                return xScale(<number> value) > wid ? xScale(<number> value) + 8 : wid + 12;
            } else if (
                settings.barShape.shape === "Line" ||
                settings.barShape.shape === "Lollipop" ||
                settings.barShape.shape === "Hammer Head") {
                if (viewModel.settings.alignBarLabels.show) {
                    return 1.01 * (xScale(<number> value) + 8);
                }
                if (settings.barShape.labelPosition === "Top") {
                    return 1.01 * (xScale(<number> value) + 8);
                } else {
                    return 1.01 * (wid + 8);
                }
            }
        }

        function getTextPositionY(category: string, textProps: TextProperties) {
            if (settings.barShape.shape === "Bar") {
                return yScale(category) + yScale.bandwidth() / 2 +
                    textMeasurementService.textMeasurementService.measureSvgTextHeight(textProps) / 4;
            } else if (settings.barShape.shape === "Line" ||
                    settings.barShape.shape === "Lollipop" ||
                    settings.barShape.shape === "Hammer Head") {
                if (settings.barShape.labelPosition === "Top") {
                    return yScale(category) +
                    yScale.bandwidth() / 16 +
                    textMeasurementService.textMeasurementService.measureSvgTextHeight(textProps) / 4;
                } else {
                    return yScale(category) +
                    yScale.bandwidth() / 2 +
                    textMeasurementService.textMeasurementService.measureSvgTextHeight(textProps) / 4;
                }
            }
        }

        function getHeadPositionX(value: PrimitiveValue, wid: number) {

            if (settings.barShape.shape === "Bar") {
                return xScale(<number> value) > wid ? xScale(<number> value) + 8 : wid + 8;
            } else if (settings.barShape.shape === "Line" ||
                settings.barShape.shape === "Lollipop" ||
                settings.barShape.shape === "Hammer Head") {
                return xScale(<number> value) + 8;
            }
        }

        this.events.renderingFinished(options);
    }