Skip to content

Commit

Permalink
HeatFrame
Browse files Browse the repository at this point in the history
  • Loading branch information
daizutabi committed Feb 1, 2025
1 parent 47da0be commit 0230807
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 36 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ ignore = [
"PGH003",
"PLR",
"SIM102",
"SIM108",
"TRY003",
]

Expand Down
89 changes: 86 additions & 3 deletions src/xlviews/dataframes/heat_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@

from xlviews.config import rcParams
from xlviews.decorators import turn_off_screen_updating
from xlviews.range.style import set_alignment, set_font
from xlviews.range.formula import aggregate
from xlviews.range.style import set_alignment, set_border, set_color_scale, set_font
from xlviews.utils import rgb

from .sheet_frame import SheetFrame
from .style import set_heat_frame_style

if TYPE_CHECKING:
from xlwings import Range
from xlwings import Range, Sheet


class HeatFrame(SheetFrame):
Expand All @@ -26,11 +29,91 @@ def __init__(
x: str,
y: str,
value: str,
vmin: float | None = None,
vmax: float | None = None,
sheet: Sheet | None = None,
style: bool = True,
autofit: bool = True,
font_size: int | None = None,
**kwargs,
) -> None:
df = data.pivot_table(value, y, x, aggfunc=lambda x: x)
df.index.name = None

super().__init__(*args, data=df, index=True, style=False, **kwargs)
super().__init__(*args, data=df, index=True, sheet=sheet, style=False)

if style:
set_heat_frame_style(self, autofit=autofit, font_size=font_size, **kwargs)

self.set_adjacent_column_width(1, offset=-1)

self.set_extrema(vmin, vmax)
self.set_colorbar()
set_color_scale(self.range(index=False), self.vmin, self.vmax)

self.set_label(value)

if autofit:
self.label.columns.autofit()

@property
def vmin(self) -> Range:
return self.cell.offset(len(self), len(self.columns) + 1)

@property
def vmax(self) -> Range:
return self.cell.offset(1, len(self.columns) + 1)

@property
def label(self) -> Range:
return self.cell.offset(0, len(self.columns) + 1)

def set_extrema(
self,
vmin: float | str | None = None,
vmax: float | str | None = None,
) -> None:
rng = self.range(index=False)

if vmin is None:
vmin = aggregate("min", rng, formula=True)

if vmax is None:
vmax = aggregate("max", rng, formula=True)

self.vmin.value = vmin
self.vmax.value = vmax

def set_colorbar(self) -> None:
vmin = self.vmin.get_address()
vmax = self.vmax.get_address()

col = self.vmax.column
start = self.vmax.row
end = self.vmin.row
n = end - start - 1
for i in range(n):
value = f"={vmax}+{i + 1}*({vmin}-{vmax})/{n + 1}"
self.sheet.range(i + start + 1, col).value = value

rng = self.sheet.range((start, col), (end, col))
set_color_scale(rng, self.vmin, self.vmax)
set_font(rng, color=rgb("white"), size=rcParams["frame.font.size"])
set_alignment(rng, horizontal_alignment="center")
ec = rcParams["frame.gray.border.color"]
set_border(rng, edge_weight=2, edge_color=ec, inside_weight=0)

if n > 0:
rng = self.sheet.range((start + 1, col), (end - 1, col))
set_font(rng, size=4)

def set_label(self, label: str) -> None:
rng = self.label
rng.value = label
set_font(rng, bold=True, size=rcParams["frame.font.size"])
set_alignment(rng, horizontal_alignment="center")

def set_adjacent_column_width(self, width: float, offset: int = 1) -> None:
"""Set the width of the adjacent empty column."""
column = self.label.column + offset
self.sheet.range(1, column).column_width = width
5 changes: 2 additions & 3 deletions src/xlviews/dataframes/sheet_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,6 @@ def set_data(
if style:
self.set_style(gray=gray, autofit=autofit, font_size=font_size, **kwargs)

if self.head is None:
self.set_adjacent_column_width(1)

if self.name:
book = self.sheet.book
refers_to = "=" + self.cell.get_address(include_sheetname=True)
Expand Down Expand Up @@ -1043,6 +1040,8 @@ def delete(self, direction: str = "up", *, entire: bool = False) -> None:
def dist_frame(self, *args, **kwargs) -> DistFrame:
from .dist_frame import DistFrame

self.set_adjacent_column_width(1)

self.dist = DistFrame(self, *args, **kwargs)
return self.dist

Expand Down
63 changes: 63 additions & 0 deletions src/xlviews/dataframes/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
if TYPE_CHECKING:
from xlwings import Range

from .heat_frame import HeatFrame
from .sheet_frame import SheetFrame
from .table import Table

Expand Down Expand Up @@ -215,3 +216,65 @@ def set_table_style(
style.TableStyleElements(even_type).Interior.Color = even_color

table.api.TableStyle = style


@turn_off_screen_updating
def set_heat_frame_style(
sf: HeatFrame,
*,
autofit: bool = False,
alignment: str | None = "center",
border: bool = True,
font: bool = True,
fill: bool = True,
font_size: int | None = None,
) -> None:
"""Set style of SheetFrame.
Args:
sf: The SheetFrame object.
autofit: Whether to autofit the frame.
alignment: The alignment of the frame.
border: Whether to draw the border.
font: Whether to specify the font.
fill: Whether to fill the frame.
font_size: The font size to specify directly.
"""
cell = sf.cell
sheet = sf.sheet

set_style = partial(
_set_style,
border=border,
font=font,
fill=fill,
gray=False,
font_size=font_size,
)

index_level = sf.index_level
columns_level = sf.columns_level
length = len(sf)

if index_level > 0:
start = cell.offset(columns_level, 0)
end = cell.offset(columns_level + length - 1, index_level - 1)
set_style(start, end, "index")

width = len(sf.value_columns)

start = cell.offset(columns_level - 1, index_level)
end = cell.offset(columns_level - 1, index_level + width - 1)
set_style(start, end, "index")

start = cell.offset(columns_level, index_level)
end = cell.offset(columns_level + length - 1, index_level + width - 1)
set_style(start, end, "values")

rng = sheet.range(cell, end)

if autofit:
rng.columns.autofit()

if alignment:
set_alignment(rng, alignment)
47 changes: 43 additions & 4 deletions src/xlviews/range/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@

from typing import TYPE_CHECKING

import xlwings as xw
from xlwings import Range, Sheet
from xlwings.constants import BordersIndex, FormatConditionType, LineStyle
from xlwings.constants import (
BordersIndex,
ConditionValueTypes,
FormatConditionType,
LineStyle,
)

from xlviews.config import rcParams
from xlviews.utils import constant, rgb, set_font_api
Expand Down Expand Up @@ -83,10 +89,14 @@ def set_fill(rng: Range | RangeCollection, color: int | str | None = None) -> No
def set_font(
rng: Range | RangeCollection,
name: str | None = None,
**kwargs,
*,
size: float | None = None,
bold: bool | None = None,
italic: bool | None = None,
color: int | str | None = None,
) -> None:
name = name or rcParams["frame.font.name"]
set_font_api(rng.api, name, **kwargs)
set_font_api(rng.api, name, size=size, bold=bold, italic=italic, color=color)


def set_alignment(
Expand Down Expand Up @@ -177,5 +187,34 @@ def address(r: Range) -> str:
condition.Font.Italic = True


def hide_gridlines(sheet: Sheet) -> None:
def hide_gridlines(sheet: Sheet | None = None) -> None:
sheet = sheet or xw.sheets.active
sheet.book.app.api.ActiveWindow.DisplayGridlines = False


def set_color_condition(rng: Range, values: list[str], colors: list[int]) -> None:
condition = rng.api.FormatConditions.AddColorScale(len(values))
condition.SetFirstPriority()

for k, (value, color) in enumerate(zip(values, colors, strict=True)):
criteria = condition.ColorScaleCriteria(k + 1)
criteria.Type = ConditionValueTypes.xlConditionValueNumber
criteria.Value = value
criteria.FormatColor.Color = color


def set_color_scale(
rng: Range,
vmin: float | str | Range,
vmax: float | str | Range,
) -> None:
if isinstance(vmin, Range):
vmin = vmin.get_address()

if isinstance(vmax, Range):
vmax = vmax.get_address()

values = [f"={vmin}", f"=({vmin} + {vmax}) / 2", f"={vmax}"]
colors = [rgb(130, 130, 255), rgb(80, 185, 80), rgb(255, 130, 130)]

set_color_condition(rng, values, colors)
2 changes: 1 addition & 1 deletion tests/chart/axes/test_position.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@pytest.mark.parametrize(
("pos", "left", "top"),
[("right", 269.5, 18), ("inside", 134, 66), ("bottom", 52, 90)],
[("right", 312, 18), ("inside", 134, 66), ("bottom", 52, 90)],
)
def test_set_first_position(sheet: Sheet, pos: str, left: float, top: float):
from xlviews.chart.axes import (
Expand Down
25 changes: 0 additions & 25 deletions tests/dataframes/heat_frame/__init__.py

This file was deleted.

Loading

0 comments on commit 0230807

Please sign in to comment.