← Files ClaraARCHIVED FILE

modules/distribution-analysis/vendor/modules/charting/draw_other_charts.py

37.5 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

import copy
import datetime as dt
import math
from typing import Any

import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import polars as pl
from plotly.subplots import make_subplots

from modules.charting.chart_helpers import set_up_tab_for_show_or_download_chart
from modules.charting.chart_primitives import (
    add_message_as_annotation,
    add_title_as_annotation,
    enable_draw_shapes,
    get_color_array,
    get_color_dictionary,
    get_max_and_min_value,
    get_number_prefix,
    get_user_message,
    insert_highlight_color,
    millify_dataframe,
    reset_row_and_column_counters,
    set_other_color_to_grey,
)
from modules.charting.draw_charts_utils import (
    add_cumulated_legends,
    add_labels_to_area_chart,
    get_labels_for_trend_comparison,
    prepare_value_labels_for_timeline,
)
from modules.charting.draw_timeline import add_labels_to_timeline_chart
from modules.charting.make_titles import make_horizontal_waterfall_chart_title
from modules.charting.polars_helpers import to_lists
from modules.charting.setup_fig import (
    add_integrated_legends_to_trend_plot,
    setup_fig_for_actual_vs_previous_year_charts,
)
from modules.charting.update_layouts import update_cy_ac_layout
from modules.data.common_data_utils import (
    clean_column_labels_after_flatten,
    get_cum_sum_dataframe,
    get_month_name,
    order_dataframe_by_month,
)
from modules.utilities.config import (
    get_config_params,
    get_naming_params,
)
from modules.utilities.error_messages import (
    add_empty_dataset_error_message_in_plot_charts_tab,
    add_error_message_in_plot_charts_tab,
)
from modules.utilities.helpers import (
    drop_columns,
    duplicate_dataframe,
    flatten_cols_polars,
    get_periods_array,
    unique,
)
from modules.utilities.utils import (
    ensure_polars_df,
    get_schema_and_column_names,
    is_valid_lazyframe,
)


def adjust_ac_py_plot(
    fig, df, key, metric, title, height, width, paramDict, chartDict, plotWithPins
):
    namingParams = get_naming_params()
    chosenChart = namingParams["chosenChart"]
    chosenChart = chartDict[chosenChart]
    fig = update_cy_ac_layout(fig, height, width, paramDict, chartDict, plotWithPins)
    fig, message = get_user_message(
        fig, chosenChart, metric, key, paramDict, chartDict, df, width, None
    )
    fig = add_message_as_annotation(
        fig, message, None, chosenChart, chartDict, paramDict
    )
    fig = add_title_as_annotation(fig, title, chosenChart, chartDict)
    fig = enable_draw_shapes(fig)
    return fig


# custom function to set fill color
def fillcol(label, chartDict):
    colorDict = get_color_dictionary(chartDict)
    if label >= 1:
        return colorDict["greenColor"]
    else:
        return colorDict["redColor"]


def draw_cy_ac_plotly(
    fig, df, paramDict, title, countRows, countCols, yArray, chartDict
):
    namingParams = get_naming_params()
    periodName = namingParams["periodName"]
    labelName = namingParams["labelName"]
    otherLabelName = namingParams["otherLabelName"]
    colorName = namingParams["colorName"]
    timelineChart = namingParams["timelineChart"]
    plotValuesAsChoice = namingParams["plotValuesAsChoice"]
    absolute = namingParams["absolute"]
    showValueLabelsKey = namingParams["showValueLabels"]
    chosenChart = namingParams["chosenChart"]
    metConditionValue = namingParams["metConditionValue"]
    notMetConditionValue = namingParams["notMetConditionValue"]
    trendComparisonChart = namingParams["trendComparisonChart"]
    trendComparisonByPeriodChart = namingParams["trendComparisonByPeriodChart"]
    plotAsBaselineKey = namingParams["plotAsBaseline"]
    pyName = namingParams["pyName"]
    labelPosition = namingParams["labelPosition"]
    maxValueKey = namingParams["maxValue"]
    minValueKey = namingParams["minValue"]
    plotAsBaseline = notMetConditionValue
    if plotAsBaselineKey in chartDict and chartDict[plotAsBaselineKey]:
        plotAsBaseline = metConditionValue
    chosenChart = chartDict[chosenChart]
    colorDict = get_color_dictionary(chartDict)
    columns, schema = get_schema_and_column_names(df)
    df1 = ensure_polars_df(duplicate_dataframe(df))
    if chosenChart in [trendComparisonByPeriodChart]:
        df1, chartDict = millify_dataframe(df1, maxValueKey, None, labelName, chartDict)
    PYtext = None
    acMode, acMarkerSize, acLineWidth = "markers+lines+text", 10, 2
    showLegend = False
    barWidth = 0.035
    if chosenChart in [trendComparisonChart]:
        acMode, acMarkerSize, acLineWidth = "markers+lines+text", 10, 2
        PYtext = df1[otherLabelName]
        barWidth = 0.1
    df1 = df1.with_columns(pl.lit("top center").alias(labelPosition))
    if plotAsBaseline:
        df1 = df1.with_columns(
            pl.when(pl.col(colorName) == 0)
            .then("bottom center")
            .otherwise(pl.col(labelPosition))
            .alias(labelPosition)
        )

    df_temp = ensure_polars_df(df).with_columns(
        pl.arange(0, pl.len()).alias("__idx"),
        (
            pl.col(colorName).shift().ne(pl.col(colorName)).fill_null(True).cum_sum()
        ).alias("group"),
    )
    dfs = [
        g.sort("__idx").drop("group")
        for _, g in df_temp.group_by("group", maintain_order=True)
    ]
    idx_vals = list(range(df1.height))
    count = 0
    numberOfCicles = dfs.__len__()
    for df in dfs:
        if not plotAsBaseline:
            fig.add_trace(
                go.Scatter(
                    x=idx_vals,
                    y=df1[minValueKey],
                    fill="tozeroy",
                    fillcolor=colorDict["whiteColor"],
                    line=dict(width=0),
                    marker=dict(size=0),
                    mode="none",
                    showlegend=False,
                ),
                row=countRows,
                col=countCols,
            )
        if count == numberOfCicles - 1:
            fig.add_trace(
                go.Scatter(
                    x=idx_vals,
                    y=df1[yArray[0]],
                    line=dict(color=colorDict["almostBlackColor"], width=acLineWidth),
                    marker=dict(
                        size=acMarkerSize,
                        symbol="square",
                        color=colorDict["almostBlackColor"],
                        line=dict(color=colorDict["almostBlackColor"], width=2),
                    ),
                    text=df1[labelName],
                    textposition=df1[labelPosition],
                    # textposition='bottom center',
                    mode=acMode,
                    name=yArray[0],
                    showlegend=showLegend,
                ),
                row=countRows,
                col=countCols,
            )
            pyLineColor = colorDict["almostBlackColor"]
            pyMarkerColor = colorDict["whiteColor"]
            if pyName in columns:
                pyMarkerColor = colorDict["lightGreyColor"]
                pyLineColor = colorDict["lightGreyColor"]
            fig.add_trace(
                go.Scatter(
                    x=idx_vals,
                    y=df1[yArray[1]],
                    line=dict(color=colorDict["greyColor"], dash="dash", width=1),
                    marker=dict(
                        size=acMarkerSize,
                        symbol="square",
                        color=pyMarkerColor,
                        line=dict(color=pyLineColor, width=2),
                    ),
                    text=PYtext,
                    textposition="top center",
                    mode=acMode,
                    name=yArray[1],
                    showlegend=showLegend,
                ),
                row=countRows,
                col=countCols,
            )
        fig.add_trace(
            go.Scatter(
                x=df["__idx"],
                y=df[yArray[0]],
                line=dict(color="rgba(0,0,0,0)"),
                showlegend=False,
            ),
            row=countRows,
            col=countCols,
        )
        fig.add_trace(
            go.Bar(
                x=df["__idx"],
                y=df[minValueKey],
                marker=dict(
                    color=colorDict["whiteColor"],
                ),
                showlegend=False,
                width=barWidth,
            ),
            row=countRows,
            col=countCols,
        )
        fig.add_trace(
            go.Bar(
                x=df["__idx"],
                y=df[maxValueKey] - df[minValueKey],
                marker=dict(color=fillcol(df[colorName][0], chartDict)),
                showlegend=False,
                width=barWidth,
            ),
            row=countRows,
            col=countCols,
        )
        count = count + 1
        showLegend = False
    fig.update_layout(barmode="stack")
    return fig, chartDict


def draw_area_chart(
    dfCopy,
    paramDict,
    chosenDimension,
    metric,
    xColumn,
    chartDict,
    count,
    uniqueItems,
    aggregateOtherItemsName,
):
    """Draw an area chart with aggregation and pivoting handled in Polars."""
    namingParams = get_naming_params()
    configParams = get_config_params()
    labelName = namingParams["labelName"]
    yShiftName = namingParams["yShiftName"]
    xShiftName = namingParams["xShiftName"]
    separatorString = namingParams["separatorString"]
    chosenChart = namingParams["chosenChart"]
    totalName = namingParams["totalName"]
    plotValuesAsChoice = namingParams["plotValuesAsChoice"]
    showValueLabelsKey = namingParams["showValueLabels"]
    absolute = namingParams["absolute"]
    periodOrder = [False]
    colorDict = get_color_dictionary(chartDict)
    colorArray = get_color_array(colorDict, chartDict)
    chosenChart = chartDict[chosenChart]
    if len(uniqueItems) > 1:
        dfCopy = dfCopy.with_columns(pl.col(chosenDimension).cast(pl.Categorical)).sort(
            by=[chosenDimension, xColumn]
        )
        colorArray = set_other_color_to_grey(
            uniqueItems, aggregateOtherItemsName, colorArray, chartDict, 0
        )
        colorArray = insert_highlight_color(
            chosenDimension, uniqueItems, colorArray, paramDict, chartDict
        )
    df = duplicate_dataframe(dfCopy)
    numberOfRows = 1
    numberOfCols = 1
    sharedXaxes = "all"
    sharedYaxes = None
    verticalSpacing = 0
    horizontalSpacing = 0
    countRows = 1
    countCols = 1
    subplotTitles = []
    labelArray = []
    yShiftArray = []
    xShiftArray = []
    show_value_labels = True
    if showValueLabelsKey in chartDict:
        show_value_labels = bool(chartDict[showValueLabelsKey])
    hovertemplate = "{text:,.3s}"
    if is_valid_lazyframe(df):
        value_cols = [metric]
        group_cols = [xColumn, chosenDimension]
        df = (
            (df.lazy() if isinstance(df, pl.DataFrame) else df)
            .group_by(group_cols)
            .agg([pl.col(col).sum() for col in value_cols])
            .collect()
            .pivot(
                chosenDimension,
                index=xColumn,
                values=value_cols,
                aggregate_function="sum",
            )
            .sort(xColumn)
        )
        df = flatten_cols_polars(df, "")
        df, newCols = clean_column_labels_after_flatten(df, [metric])
        if uniqueItems:
            max_df = df.select(
                pl.max_horizontal([pl.col(col) for col in uniqueItems])
                .max()
                .alias("maxValue")
            )
            prefix, chartDict, decimals = get_number_prefix(
                max_df.lazy(), "maxValue", chartDict, None
            )
        dropCols = [col for col in uniqueItems if abs(float(df[col].sum())) < 0.0001]
        if dropCols:
            df = drop_columns(df, dropCols)
            uniqueItems = [u for u in uniqueItems if u not in dropCols]
        count = 0
        fig = make_subplots(
            rows=numberOfRows,
            cols=numberOfCols,
            shared_xaxes=sharedXaxes,
            shared_yaxes=sharedYaxes,
            vertical_spacing=verticalSpacing,
            horizontal_spacing=horizontalSpacing,
            subplot_titles=subplotTitles,
        )
        groupnorm = ""
        if (
            plotValuesAsChoice in chartDict
            and chartDict[plotValuesAsChoice] != absolute
        ):
            groupnorm = "percent"
            row_sum = pl.sum_horizontal([pl.col(c) for c in uniqueItems])
            df = df.with_columns(
                [(pl.col(c) / row_sum * 100).round(1).alias(c) for c in uniqueItems]
            )
        dfCumSum = None
        dfNegative = None
        message = None
        if show_value_labels:
            dfCumSum, dfNegative, message = get_cum_sum_dataframe(
                df, chosenChart, [None], ""
            )
        for element in uniqueItems:
            labelArray.append(element + separatorString + labelName)
            yShiftArray.append(element + separatorString + yShiftName)
            xShiftArray.append(element + separatorString + xShiftName)
            fig.add_trace(
                go.Scatter(
                    x=df[xColumn],
                    y=df[uniqueItems[count]].round(1),
                    line=dict(color=colorArray[count]),
                    showlegend=False,
                    mode="lines",
                    hovertext=element,
                    groupnorm=groupnorm,
                    stackgroup="one",
                ),
                row=countRows,
                col=countCols,
            )
            count = count + 1
        if show_value_labels:
            count = 0
            for column in uniqueItems:
                df = prepare_value_labels_for_timeline(
                    df,
                    chosenChart,
                    column,
                    labelArray,
                    yShiftArray,
                    xShiftArray,
                    chartDict,
                    count,
                )
                count = count + 1
            df = df.with_columns(
                pl.sum_horizontal([pl.col(c) for c in uniqueItems]).alias(totalName)
            )
            totalLabelArray = [totalName + separatorString + labelName]
            totalYShiftArray = [totalName + separatorString + yShiftName]
            totalXShiftArray = [totalName + separatorString + xShiftName]
            df = prepare_value_labels_for_timeline(
                df,
                chosenChart,
                totalName,
                totalLabelArray,
                totalYShiftArray,
                totalXShiftArray,
                chartDict,
                0,
            )
            if groupnorm == "percent":
                last_idx = df.height - 1
                df = (
                    df.with_columns(
                        pl.lit(100).alias(totalName),
                        pl.lit("").alias(totalLabelArray[0]),
                        pl.lit(0).alias(totalYShiftArray[0]),
                    )
                    .with_row_index("__idx")
                    .with_columns(
                        pl.when(pl.col("__idx") == last_idx)
                        .then(pl.lit("100%"))
                        .otherwise(pl.col(totalLabelArray[0]))
                        .alias(totalLabelArray[0]),
                        pl.when(pl.col("__idx") == last_idx)
                        .then(0)
                        .otherwise(pl.col(totalYShiftArray[0]))
                        .alias(totalYShiftArray[0]),
                    )
                    .drop("__idx")
                )
            cols, schema = get_schema_and_column_names(df)
            lists = to_lists(df.lazy(), cols)
            fig = add_labels_to_timeline_chart(
                fig,
                df.lazy(),
                element,
                chosenChart,
                totalName,
                lists.get(totalLabelArray[0], []),
                lists.get(totalYShiftArray[0], []),
                lists.get(
                    totalXShiftArray[0], [0] * len(lists.get(totalLabelArray[0], []))
                ),
                countRows,
                countCols,
            )
            count = 0
            for element in labelArray:
                if len(uniqueItems) > 1:
                    fig = add_cumulated_legends(
                        fig,
                        df,
                        dfCumSum,
                        numberOfCols,
                        value_cols,
                        uniqueItems,
                        count,
                        chartDict,
                    )
                    fig = add_labels_to_area_chart(
                        fig,
                        df,
                        dfCumSum,
                        element,
                        uniqueItems,
                        labelArray,
                        yShiftArray,
                        xShiftArray,
                        count,
                        countRows,
                        countCols,
                    )
                count = count + 1
    else:
        paramDict = add_empty_dataset_error_message_in_plot_charts_tab(paramDict)
    return fig, df


def modify_labels_for_baseline(df, yArray, chartDict):
    namingParams = get_naming_params()
    plotAsBaselineKey = namingParams["plotAsBaseline"]
    hyphenName = namingParams["hyphenName"]
    deltaName = namingParams["deltaName"]
    averageName = namingParams["averageName"]
    compareToAverageKey = namingParams["compareToAverage"]
    if plotAsBaselineKey in chartDict and chartDict[plotAsBaselineKey]:
        if compareToAverageKey in chartDict and chartDict[compareToAverageKey]:
            referencePeriodName = deltaName + averageName
            otherPeriodName = averageName
        # referencePeriodName=yArray[0]+hyphenName+yArray[1]
        else:
            referencePeriodName = deltaName + yArray[1]
            otherPeriodName = yArray[1]
        # Polars: rename with mapping only (no axis argument)
        df = df.rename({yArray[0]: referencePeriodName, yArray[1]: otherPeriodName})
        yArray[0] = referencePeriodName
        yArray[1] = otherPeriodName
    return df, yArray


def modify_dataframe_for_baseline(
    df: pl.DataFrame | pl.LazyFrame,
    yArray: list[str],
    metric: str,
    chartDict: dict[str, Any],
) -> pl.DataFrame | pl.LazyFrame:
    """Return ``df`` modified for baseline comparison."""

    namingParams = get_naming_params()
    plotAsBaselineKey = namingParams["plotAsBaseline"]
    separatorString = namingParams["separatorString"]
    compareToAverageKey = namingParams["compareToAverage"]

    if plotAsBaselineKey in chartDict and chartDict[plotAsBaselineKey]:
        columns, _ = get_schema_and_column_names(df)
        basePeriodName = metric + separatorString + yArray[0]
        otherPeriodName = metric + separatorString + yArray[1]

        use_lazy = isinstance(df, pl.LazyFrame)
        lf = df

        if compareToAverageKey in chartDict and chartDict[compareToAverageKey]:
            lf = lf.with_columns(
                (pl.col(basePeriodName) - pl.col(basePeriodName).mean()).alias(
                    basePeriodName
                ),
                pl.lit(0).alias(otherPeriodName),
            )
        else:
            if basePeriodName in columns and otherPeriodName in columns:
                lf = lf.with_columns(
                    (pl.col(basePeriodName) - pl.col(otherPeriodName)).alias(
                        basePeriodName
                    ),
                    pl.lit(0).alias(otherPeriodName),
                )

        df = lf

    return df


def make_ac_py_difference_dataframe(df, element, chartDict, paramDict):
    """
    we need to make a dataframe in which the values are side by side so we can compute the difference
    """
    namingParams = get_naming_params()
    periodName = namingParams["periodName"]
    selectedPeriods = namingParams["selectedPeriods"]
    dateName = namingParams["dateName"]
    acpyName = namingParams["acpyName"]
    separatorString = namingParams["separatorString"]
    minValue = namingParams["minValue"]
    maxValue = namingParams["maxValue"]
    labelName = namingParams["labelName"]
    otherLabelName = namingParams["otherLabelName"]
    acName = namingParams["acName"]
    pyName = namingParams["pyName"]
    plName = namingParams["plName"]
    colorName = namingParams["colorName"]
    filterDates = namingParams["filterDates"]
    timelineChart = namingParams["timelineChart"]
    areaChart = namingParams["areaChart"]
    trendComparisonChart = namingParams["trendComparisonChart"]
    multitierColumnChart = namingParams["multitierColumnChart"]
    horizontalWaterfallChart = namingParams["horizontalWaterfallChart"]
    differenceInValue = namingParams["differenceInValue"]
    chosenChart = namingParams["chosenChart"]
    chosenChart = chartDict[chosenChart]
    renameDict = {
        "Last 52": "Year",
        "Last 26": "Semester",
        "Last 13": "Quarter",
        "Last 4": "Month",
    }
    periodOrder = ["Year", "Semester", "Quarter", "Month"]
    yArray = []
    keepCols, colorMetric, indexMetric = (
        [periodName, acpyName, element],
        acpyName,
        periodName,
    )
    if chosenChart in [trendComparisonChart, multitierColumnChart]:
        keepCols, colorMetric, indexMetric = (
            [dateName, periodName, element],
            periodName,
            dateName,
        )
        df = get_month_name(df)
    lf = df.lazy() if isinstance(df, pl.DataFrame) else df
    frame = (
        lf.select(keepCols)
        .group_by([indexMetric, colorMetric])
        .agg(pl.col(element).sum())
    )
    pivot_values = [acName, pyName, plName]
    exprs = [
        pl.when(pl.col(colorMetric) == val)
        .then(pl.col(element))
        .otherwise(0)
        .sum()
        .alias(f"{element}_{val}")
        for val in pivot_values
    ]
    frame = frame.group_by(indexMetric).agg(exprs)

    df = frame
    if chosenChart in [
        trendComparisonChart
    ]:  # and filterDates in chartDict and chartDict[filterDates]:
        if filterDates in chartDict and chartDict[filterDates]:
            yArray = [acName, plName]
        else:
            yArray = [acName, pyName]
        df = modify_dataframe_for_baseline(df, yArray, element, chartDict)
        df, periodsArray = get_max_and_min_value(
            df, element, chartDict, paramDict, yArray
        )
        df = df.sort(dateName)
        df = order_dataframe_by_month(df, paramDict, True, yArray)
        df, chartDict = get_labels_for_trend_comparison(df, yArray, element, chartDict)
        keepCols = yArray + [minValue, maxValue, labelName, otherLabelName, colorName]
        df = df.select(keepCols)
    else:
        yArray = [acName, pyName]
        columns, schema = get_schema_and_column_names(df)
        df, periodsArray = get_max_and_min_value(
            df, element, chartDict, paramDict, yArray
        )
        df = df.with_columns(
            pl.col(acName).cast(pl.Utf8).alias(labelName),
            pl.col(periodName).replace(renameDict).alias(periodName),
        )
        order_map = {val: idx for idx, val in enumerate(periodOrder)}
        # Polars: use replace with mapping, then cast to integer for ordering
        df = (
            df.with_columns(
                pl.col(periodName).replace(order_map).cast(pl.Int32).alias("_order")
            )
            .sort("_order")
            .drop("_order")
        )
        keepCols = yArray + [minValue, maxValue, labelName, colorName]
        df = df.select(keepCols)

    if isinstance(df, pl.LazyFrame):
        df = df.collect(engine="streaming")
    return df, yArray, chartDict


def make_difference_dataframe_and_draw_plot(
    dfCopy,
    element,
    fig,
    paramDict,
    count,
    countCols,
    countRows,
    numberOfCols,
    numberOfRows,
    height,
    chartDict,
    uniqueItems,
    metricArray,
):
    """
    putting functions together
    """
    namingParams = get_naming_params()
    trendComparisonChart = namingParams["trendComparisonChart"]
    dateName = namingParams["dateName"]
    trendComparisonByPeriodChart = namingParams["trendComparisonByPeriodChart"]
    chosenChart = namingParams["chosenChart"]
    periodName = namingParams["periodName"]
    chosenChart = chartDict[chosenChart]
    df = duplicate_dataframe(dfCopy)
    df, yArray, chartDict = make_ac_py_difference_dataframe(
        df, element, chartDict, paramDict
    )
    if chosenChart in [trendComparisonChart, trendComparisonByPeriodChart]:
        df, yArray = modify_labels_for_baseline(df, yArray, chartDict)
        fig, chartDict = draw_cy_ac_plotly(
            fig, df, paramDict, element, countRows, countCols, yArray, chartDict
        )
        fig = add_integrated_legends_to_trend_plot(
            fig, df, countRows, countCols, yArray
        )
    totalHeight = height * (countRows)
    count, countRows, countCols, chartDict = reset_row_and_column_counters(
        count, countCols, countRows, numberOfCols, numberOfRows, chartDict
    )
    return fig, count, countRows, countCols, totalHeight, chartDict, df


def draw_actual_vs_previous_year_chart(
    dfCopy, chosenDimension, metricArray, repeatArray, paramDict, chartDict
):
    """
    in order to show green where it goes better and red where worse, e need to build a dataframe
    with the differences
    """
    if isinstance(dfCopy, pl.LazyFrame):
        dfCopy = dfCopy.collect()
    namingParams = get_naming_params()
    configParams = get_config_params()
    fontSize = configParams[namingParams["fontSizeText"]]
    font = configParams[namingParams["fontChoice"]]
    trendComparisonByPeriodChart = namingParams["trendComparisonByPeriodChart"]
    numberOfPlots = namingParams["numberOfPlots"]
    chosenChart = namingParams["chosenChart"]
    periodName = namingParams["periodName"]
    plName = namingParams["plName"]
    pyName = namingParams["pyName"]
    acName = namingParams["acName"]
    plotSmallMultiplesKey = namingParams["plotSmallMultiplesOtherCharts"]
    canPlotYearToYearKey = namingParams["canPlotYearToYear"]
    setTimePeriodTabLabel = namingParams["setTimePeriodTabLabel"]
    selectDimensionsToPlot = namingParams["selectDimensionsToPlot"]
    chosenChart = chartDict[chosenChart]
    configPlotlyDict = configParams["configPlotlyDict"]
    configPlotlyDict = configPlotlyDict[chosenChart]
    exportDataArray = []
    key = None
    canPlotYearToYear = True
    plotWithPins = False
    numberOfMetrics = len(metricArray)
    if chosenDimension == None and numberOfMetrics == 1:
        plotWithPins = True
    if canPlotYearToYearKey in chartDict:
        canPlotYearToYear = chartDict[canPlotYearToYearKey]
    if is_valid_lazyframe(dfCopy) and canPlotYearToYear:
        repeatArrayToPlot = []
        for element in repeatArray:
            repeatArrayToPlot.append(element)
        if plotSmallMultiplesKey in chartDict and chartDict[plotSmallMultiplesKey]:
            fig, height, width, numberOfCols, numberOfRows = (
                setup_fig_for_actual_vs_previous_year_charts(
                    repeatArrayToPlot,
                    chosenDimension,
                    paramDict,
                    chartDict,
                    plotWithPins,
                )
            )
        count, countRows, countCols = 1, 1, 1
        columns, schema = get_schema_and_column_names(dfCopy)
        if chosenDimension in columns:
            paramDict[numberOfPlots] = len(repeatArray)
            # fullFig=False
            # metricType=False
            # same scale does not work here because Other Rank > is plotted as last
            for column in repeatArray:
                df = duplicate_dataframe(dfCopy)
                periodsArray = get_periods_array(df)
                df = df.filter(pl.col(chosenDimension) == column)
                if plName in periodsArray:
                    pyName = plName
                df = drop_columns(df, [chosenDimension])
                for metric in metricArray:
                    if (
                        plotSmallMultiplesKey not in chartDict
                        or not chartDict[plotSmallMultiplesKey]
                    ):
                        fig, height, width, numberOfCols, numberOfRows = (
                            setup_fig_for_actual_vs_previous_year_charts(
                                repeatArrayToPlot,
                                chosenDimension,
                                paramDict,
                                chartDict,
                                plotWithPins,
                            )
                        )
                    fig, count, countRows, countCols, totalHeight, chartDict, df = (
                        make_difference_dataframe_and_draw_plot(
                            df,
                            metric,
                            fig,
                            paramDict,
                            count,
                            countCols,
                            countRows,
                            numberOfCols,
                            numberOfRows,
                            height,
                            chartDict,
                            repeatArray,
                            metricArray,
                        )
                    )
                    exportDataArray.append(df)
                    fig.update_annotations(font=dict(size=fontSize, family=font))
                    if (
                        plotSmallMultiplesKey not in chartDict
                        or not chartDict[plotSmallMultiplesKey]
                    ):
                        key = chosenDimension + column
                        titleColumn = chosenDimension + ": " + column
                        title, paramDict, chartDict = (
                            make_horizontal_waterfall_chart_title(
                                df,
                                chosenChart,
                                paramDict,
                                titleColumn,
                                metric,
                                chartDict,
                                pyName,
                                acName,
                            )
                        )
                        # fig,fullFig,metricType=keep_same_scale_for_all_plots(fig,metric,metricType,fullFig,"Y")
                        # same scale does not work here because Other Rank > is plotted as last
                        fig = adjust_ac_py_plot(
                            fig,
                            dfCopy,
                            key,
                            metric,
                            title,
                            height,
                            width,
                            paramDict,
                            chartDict,
                            plotWithPins,
                        )
                        paramDict = set_up_tab_for_show_or_download_chart(
                            df,
                            fig,
                            configPlotlyDict,
                            chartDict,
                            chosenDimension + metric,
                            False,
                            None,
                            chosenDimension,
                            paramDict,
                        )
        else:
            paramDict[numberOfPlots] = len(metricArray)
            periodsArray = dfCopy[periodName].unique().to_list()
            if plName in periodsArray:
                pyName = plName
            for metric in metricArray:
                df = duplicate_dataframe(dfCopy)
                if (
                    plotSmallMultiplesKey not in chartDict
                    or not chartDict[plotSmallMultiplesKey]
                ):
                    fig, height, width, numberOfCols, numberOfRows = (
                        setup_fig_for_actual_vs_previous_year_charts(
                            repeatArrayToPlot,
                            chosenDimension,
                            paramDict,
                            chartDict,
                            plotWithPins,
                        )
                    )
                fig, count, countRows, countCols, totalHeight, chartDict, df = (
                    make_difference_dataframe_and_draw_plot(
                        df,
                        metric,
                        fig,
                        paramDict,
                        count,
                        countCols,
                        countRows,
                        numberOfCols,
                        numberOfRows,
                        height,
                        chartDict,
                        repeatArrayToPlot,
                        metricArray,
                    )
                )
                fig.update_annotations(font=dict(size=fontSize, family=font))
                if (
                    plotSmallMultiplesKey not in chartDict
                    or not chartDict[plotSmallMultiplesKey]
                ):
                    title, paramDict, chartDict = make_horizontal_waterfall_chart_title(
                        df,
                        chosenChart,
                        paramDict,
                        "",
                        metric,
                        chartDict,
                        pyName,
                        acName,
                    )
                    fig = adjust_ac_py_plot(
                        fig,
                        df,
                        key,
                        metric,
                        title,
                        height,
                        width,
                        paramDict,
                        chartDict,
                        plotWithPins,
                    )
                    key = metric
                    if chosenDimension:
                        key = chosenDimension + metric
                    paramDict = set_up_tab_for_show_or_download_chart(
                        df,
                        fig,
                        configPlotlyDict,
                        chartDict,
                        key,
                        False,
                        None,
                        chosenDimension,
                        paramDict,
                    )
        if plotSmallMultiplesKey in chartDict and chartDict[plotSmallMultiplesKey]:
            key = chosenDimension
            title, paramDict, chartDict = make_horizontal_waterfall_chart_title(
                dfCopy, chosenChart, paramDict, key, metric, chartDict, pyName, acName
            )
            fig = adjust_ac_py_plot(
                fig,
                dfCopy,
                key,
                metric,
                title,
                height,
                width,
                paramDict,
                chartDict,
                plotWithPins,
            )
            key = metric
            if chosenDimension:
                key = chosenDimension + metric
                chartDict[selectDimensionsToPlot] = chosenDimension
                if len(exportDataArray) > 1:
                    df = pl.concat(exportDataArray, how="vertical")
            paramDict = set_up_tab_for_show_or_download_chart(
                df,
                fig,
                configPlotlyDict,
                chartDict,
                key,
                False,
                None,
                chosenDimension,
                paramDict,
            )
    elif not canPlotYearToYear:
        message = (
            chosenChart
            + " must be plotted over 12 months and the most recent month in dataset is not December. Set 'Compare with period to date' to 'False' and 'Compare with rolling period' to 'True' in the "
            + setTimePeriodTabLabel
            + " tab."
        )
        paramDict = add_error_message_in_plot_charts_tab(paramDict, message)
    else:
        paramDict = add_empty_dataset_error_message_in_plot_charts_tab(paramDict)
    return paramDict


def draw_alternative_combination_chart_plotly(df, paramDict, chartDict):
    """
    actually draws bubble chart
    """
    namingParams = get_naming_params()
    configParams = get_config_params()
    font = configParams[namingParams["fontChoice"]]
    fontSize = configParams[namingParams["fontSizeText"]]
    varianceAmount = namingParams["varianceAmountName"]
    dimension = namingParams["dimensionName"]
    filterName = namingParams["filterName"]
    annotationDict = configParams[namingParams["annotationDict"]]
    colorDict = get_color_dictionary(chartDict)
    chartFormat = ",.0f"
    textColor = colorDict["blackColor"]
    maxLabelLength = 22
    df = ensure_polars_df(df)
    if isinstance(df, pl.LazyFrame):
        df = df.collect()
    fig = make_subplots()
    fig.add_trace(
        go.Scatter(
            x=df[varianceAmount],
            y=df[dimension],
            text=df[filterName],
            marker=dict(
                color=colorDict["veryLightGreyColor"],
                size=60,
                line=dict(
                    width=0.5,
                    color=colorDict["lightGreyColor"],
                ),
                opacity=0.8,
            ),
            mode="markers",
        )
    )
    cols, schema = get_schema_and_column_names(df)
    var_idx = cols.index(varianceAmount)
    dim_idx = cols.index(dimension)
    filter_idx = cols.index(filterName)

    xValue = df.row(0)[var_idx]
    fig.add_vline(
        x=xValue,
        layer="below",
        opacity=1,
        line_width=0.5,
        line_color="lightgrey",
        xref="x",
    )
    for idx in range(df.height):
        row = df.row(idx)
        text = row[filter_idx][:maxLabelLength]
        text = text.replace(" ", "<br>")
        text = text.replace("/", "<br>")
        fig.add_annotation(
            text=text,
            showarrow=False,
            align="center",
            yshift=0,
            x=row[var_idx],
            yref="y",
            y=row[dim_idx],
            ax=0,
            xshift=0,
            xref="x",
            font=dict(
                color=textColor,
            ),
        )
    return fig

SHA-256: 4a9a5f0cd900619047d1f923859decc99b16a5839b74ad4b896d0cd39b7b4e23