~savoy/ade

aa50e506eae3c58bc24b1e4548f9b87124dba1b5 — savoy 3 months ago 340ab47 feature/adhoc_formatting
feature: automatic adhoc pivot formatting

Signed-off-by: savoy <git@liberation.red>

feature changes

Signed-off-by: savoy <git@liberation.red>
2 files changed, 130 insertions(+), 16 deletions(-)

M ade/lib/formatting.py
M ade/lib/remote.py
M ade/lib/formatting.py => ade/lib/formatting.py +102 -3
@@ 16,14 16,113 @@
# You should have received a copy of the GNU General Public License
# along with ade. If not, see <http://www.gnu.org/licenses/>.

from decimal import Decimal

import pandas as pd

from lib import reports
import xlwings as xw
from xlwings.constants import BordersIndex, HAlign, LineStyle


def adhoc_data(sht: xw.main.Sheet, rng: xw.main.Range, data: pd.DataFrame):
    sht.tables.add(rng.expand("table"), table_style_name="TableStyleLight10")
    sht.tables.add(
        source=rng.expand("table"), name="df", table_style_name="TableStyleLight10"
    )
    sht.tables["df"].show_autofilter = True


def adhoc_pivot(
    app: xw.main.App,
    book: xw.main.Book,
    sht: xw.main.Sheet,
    rng: xw.main.Range,
    data: pd.DataFrame,
    pivot_struct: reports.PivotArgs,
):
    sht.select()
    app.api.ActiveWindow.DisplayGridlines = False

    freeze_row = rng.end("down").offset(row_offset=1).row
    sht.range(f"{freeze_row}:{freeze_row}").select()
    app.api.ActiveWindow.FreezePanes = True
    sht.range((freeze_row, 1)).select()

    title = rng.offset(row_offset=-1)
    title.font.size = 16
    title.value = pivot_struct.name

    header_corner = rng.end("down")
    header = header_corner.expand("right")
    header.api.AutoFilter(Field=1)
    header.expand("table").autofit()

    number_of_index_columns = len(data.index[0])
    index_header_end = header_corner.offset(column_offset=(number_of_index_columns - 1))

    index_header = sht.range(header_corner, index_header_end)
    margin_header = sht.range(
        header.last_cell.offset(column_offset=-(len(pivot_struct.aggfunc) - 1)),
        header.last_cell,
    )
    value_header = sht.range(
        (margin_header.row, index_header.last_cell.offset(column_offset=1).column),
        (margin_header.row, margin_header.column - 1),
    )

    value_header.expand("right").api.HorizontalAlignment = HAlign.xlHAlignCenter
    sht.range(title, header.last_cell).font.bold = True

    last_row = index_header.expand("down").last_cell.row

    # subtotal formatting must loop through cell values to determine if its necessary
    gradient_range = (242, 121)
    number_of_subs = len(pivot_struct.subs)
    gradient_step = {5: 20, 10: 10, 20: 5, 0: 1}
    for steps, percent in gradient_step.items():
        if number_of_subs <= steps or steps == 0:
            gradient = int(gradient_range[1] * Decimal(f"0.{percent}"))
            break

    for idx, head_cell in enumerate(reversed(index_header)):
        step = idx + 1
        if head_cell.value in pivot_struct.subs:
            value_range = sht.range(head_cell, (last_row, head_cell.column))
            for value_cell in value_range:
                str_value = str(value_cell.value)
                if " - Total" in str_value:
                    subtotal_range = sht.range(
                        value_cell.address, (value_cell.row, header.last_cell.column)
                    )
                    subtotal_range.color = tuple(
                        [gradient_range[0] - (gradient * step)] * 3
                    )
                    subtotal_range.font.italic = True
                elif value_cell.value == "~All":
                    total_range = sht.range(
                        value_cell.address, (value_cell.row, margin_header.column - 1)
                    )
                    total_range.font.bold = True
                    total_range.api.Borders(
                        BordersIndex.xlEdgeTop
                    ).LineStyle = LineStyle.xlContinuous

def adhoc_pivot(sht: xw.main.Sheet, rng: xw.main.Range, data: pd.DataFrame):
    pass
    try:
        first_margin_range = sht.range(
            (margin_header.offset(row_offset=1).row, margin_header.column),
            (total_range.row, margin_header.column),
        )
    except UnboundLocalError:
        first_margin_range = sht.range(
            (margin_header.offset(row_offset=1).row, margin_header.column),
            (index_header.expand("down").row, margin_header.column),
        )
    finally:
        first_margin_range.font.bold = True
        first_margin_border = sht.range(
            first_margin_range.address.split(":")[0],
            (first_margin_range.last_cell.row - 1, first_margin_range.column),
        )
        first_margin_border.api.Borders(
            BordersIndex.xlEdgeLeft
        ).LineStyle = LineStyle.xlContinuous

M ade/lib/remote.py => ade/lib/remote.py +28 -13
@@ 85,6 85,7 @@ class DataStructure:
    structure: SheetStructure
    data: pd.DataFrame
    output: Output
    pivot: Optional[rep.PivotArgs]


class _Parameters:


@@ 2048,7 2049,10 @@ class Compile:
            if include_data or not (self.report.pivot or pivot_args):
                self.data = {
                    "df": DataStructure(
                        SheetStructure("Sheet1", "A1"), self.output.df, self.output
                        SheetStructure("Sheet1", "A1"),
                        self.output.df,
                        self.output,
                        None,
                    )
                }



@@ 2116,6 2120,7 @@ class Compile:
                        margins=collection.margins,
                    ),
                    self.output,
                    collection,
                )
            else:
                self.data[collection.name] = DataStructure(


@@ 2128,6 2133,7 @@ class Compile:
                        margins=collection.margins,
                    ),
                    self.output,
                    collection,
                )

    def equipment_report(


@@ 2158,7 2164,10 @@ class Compile:
        # Sets the structure of where data gets pasted in the appropriate template
        data = {
            "df": DataStructure(
                SheetStructure("Summary", "A8"), self.output.df.copy(), self.output
                SheetStructure("Summary", "A8"),
                self.output.df.copy(),
                self.output,
                None,
            )
        }



@@ 2381,7 2390,7 @@ class Compile:

        data = {
            "pivot": DataStructure(
                SheetStructure("ABI Data", "D1"), pivot, self.output
                SheetStructure("ABI Data", "D1"), pivot, self.output, None
            ),
        }



@@ 2453,16 2462,16 @@ class Compile:

        data = {
            "revenue_location": DataStructure(
                SheetStructure("RevenueByLocation", "A1"), rev_lc, self.output
                SheetStructure("RevenueByLocation", "A1"), rev_lc, self.output, None
            ),
            "revenue_product": DataStructure(
                SheetStructure("RevenueByProduct", "A1"), rev_pr, self.output
                SheetStructure("RevenueByProduct", "A1"), rev_pr, self.output, None
            ),
            "dtp_location": DataStructure(
                SheetStructure("DaysToPay", "A1"), dtp_lc, self.output
                SheetStructure("DaysToPay", "A1"), dtp_lc, self.output, None
            ),
            "revenue_type": DataStructure(
                SheetStructure("RevenueByType", "A1"), rev_tp, self.output
                SheetStructure("RevenueByType", "A1"), rev_tp, self.output, None
            ),
        }



@@ 2550,7 2559,7 @@ class Compile:
        ).reset_index(drop=True)
        df.index.name = "Item"

        data = {"df": DataStructure(SheetStructure("ade", "B3"), df, self.output)}
        data = {"df": DataStructure(SheetStructure("ade", "B3"), df, self.output, None)}

        return data



@@ 2609,10 2618,10 @@ class Compile:

        data = {
            "listing": DataStructure(
                SheetStructure("Summary", "A2"), listing, self.output
                SheetStructure("Summary", "A2"), listing, self.output, None
            ),
            "values": DataStructure(
                SheetStructure("Summary", "I1"), values, self.output
                SheetStructure("Summary", "I1"), values, self.output, None
            ),
        }



@@ 2758,15 2767,21 @@ class File:
            if not data_struct.data.index.name:
                data_struct.data.index.name = "index"

            sht = book.sheets[data_struct.structure.sheet]
            sht_name = data_struct.structure.sheet
            if sht_name not in [i.name for i in book.sheets]:
                book.sheets.add(sht_name, after=book.sheets[-1])
            sht = book.sheets[sht_name]

            rng = sht.range(data_struct.structure.cell)
            # full.expand("table").clear_contents()
            rng.value = data_struct.data
            # full.api.AutoFilter()

            if self.report.formatting:
                if self.report.pivot:
                    formatting.adhoc_pivot(sht, rng, data_struct.data)
                if data_struct.pivot:
                    formatting.adhoc_pivot(
                        app, book, sht, rng, data_struct.data, data_struct.pivot
                    )
                else:
                    formatting.adhoc_data(sht, rng, data_struct.data)