# Copyright (c) 2010-2024 fastpyxl
from __future__ import annotations
from fastpyxl.typed_serialisable.base import Serialisable
from fastpyxl.typed_serialisable.errors import FieldValidationError
from fastpyxl.typed_serialisable.fields import AliasField, Field
from fastpyxl.descriptors.excel import ExtensionList, _explicit_none
from fastpyxl.xml.constants import CHART_NS
from .data_source import NumFmt
from .descriptors import num_fmt_from_value
from .layout import Layout
from .text import Text, RichText
from .shapes import GraphicalProperties
from .title import Title, title_from_value
def _coerce_tick_mark(v):
if v is None or v == "none":
return None
allowed = frozenset({"cross", "in", "out"})
if v not in allowed:
raise FieldValidationError(f"tick mark rejected value {v!r}")
return v
def _nested_set_only(allowed: frozenset, field_name: str):
def _c(v):
if v is None:
return None
if v not in allowed:
raise FieldValidationError(f"{field_name} rejected value {v!r}")
return v
return _c
def _none_set(allowed: frozenset, field_name: str):
def _c(v):
if v is None or v == "none":
return None
if v not in allowed:
raise FieldValidationError(f"{field_name} rejected value {v!r}")
return v
return _c
def _chart_lbl_offset_minmax(value, *, field_name: str, min_v: int, max_v: int):
if value is None:
return None
try:
n = int(value)
except (TypeError, ValueError) as exc:
raise FieldValidationError(f"{field_name} rejected value {value!r}") from exc
if n < min_v or n > max_v:
raise FieldValidationError(f"{field_name} rejected value {value!r}")
return n
[docs]
class ChartLines(Serialisable):
tagname = "chartLines"
spPr: GraphicalProperties | None = Field.element(
expected_type=GraphicalProperties, allow_none=True, default=None
)
graphicalProperties = AliasField("spPr", default=None)
def __init__(self, spPr=None):
self.spPr = spPr
[docs]
class Scaling(Serialisable):
tagname = "scaling"
logBase: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
orientation: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_nested_set_only(frozenset({"maxMin", "minMax"}), "orientation"), default=None,
)
max: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
min: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
xml_order = ("logBase", "orientation", "max", "min")
def __init__(
self,
logBase=None,
orientation="minMax",
max=None,
min=None,
extLst=None,
):
self.logBase = logBase
self.orientation = orientation
self.max = max
self.min = min
self.extLst = extLst
class _BaseAxis(Serialisable):
axId: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
scaling: Scaling | None = Field.element(expected_type=Scaling, allow_none=True, default=None)
delete: bool | None = Field.nested_bool(allow_none=True, default=None)
axPos: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_nested_set_only(frozenset({"b", "l", "r", "t"}), "axPos"), default=None,
)
majorGridlines: ChartLines | None = Field.element(
expected_type=ChartLines, allow_none=True, default=None
)
minorGridlines: ChartLines | None = Field.element(
expected_type=ChartLines, allow_none=True, default=None
)
title: Title | None = Field.element(
expected_type=Title,
allow_none=True,
converter=title_from_value, default=None,
)
numFmt: NumFmt | None = Field.element(
expected_type=NumFmt,
allow_none=True,
converter=num_fmt_from_value, default=None,
)
number_format = AliasField("numFmt", default=None)
majorTickMark: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_coerce_tick_mark,
renderer=_explicit_none, default=None,
)
minorTickMark: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_coerce_tick_mark,
renderer=_explicit_none, default=None,
)
tickLblPos: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"high", "low", "nextTo"}), "tickLblPos"), default=None,
)
spPr: GraphicalProperties | None = Field.element(
expected_type=GraphicalProperties, allow_none=True, default=None
)
graphicalProperties = AliasField("spPr", default=None)
txPr: RichText | None = Field.element(expected_type=RichText, allow_none=True, default=None)
textProperties = AliasField("txPr", default=None)
crossAx: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
crosses: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(
frozenset({"autoZero", "max", "min"}),
"crosses",
), default=None,
)
crossesAt: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
xml_order = (
"axId",
"scaling",
"delete",
"axPos",
"majorGridlines",
"minorGridlines",
"title",
"numFmt",
"majorTickMark",
"minorTickMark",
"tickLblPos",
"spPr",
"txPr",
"crossAx",
"crosses",
"crossesAt",
)
def __init__(
self,
axId=None,
scaling=None,
delete=None,
axPos="l",
majorGridlines=None,
minorGridlines=None,
title=None,
numFmt=None,
majorTickMark=None,
minorTickMark=None,
tickLblPos=None,
spPr=None,
txPr=None,
crossAx=None,
crosses=None,
crossesAt=None,
):
self.axId = axId
if scaling is None:
scaling = Scaling()
self.scaling = scaling
self.delete = delete
self.axPos = axPos
self.majorGridlines = majorGridlines
self.minorGridlines = minorGridlines
self.title = title_from_value(title) if title is not None else None
self.numFmt = num_fmt_from_value(numFmt) if numFmt is not None else None
self.majorTickMark = majorTickMark
self.minorTickMark = minorTickMark
self.tickLblPos = tickLblPos
self.spPr = spPr
self.txPr = txPr
self.crossAx = crossAx
self.crosses = crosses
self.crossesAt = crossesAt
[docs]
class DisplayUnitsLabel(Serialisable):
tagname = "dispUnitsLbl"
layout: Layout | None = Field.element(expected_type=Layout, allow_none=True, default=None)
tx: Text | None = Field.element(expected_type=Text, allow_none=True, default=None)
text = AliasField("tx", default=None)
spPr: GraphicalProperties | None = Field.element(
expected_type=GraphicalProperties, allow_none=True, default=None
)
graphicalProperties = AliasField("spPr", default=None)
txPr: RichText | None = Field.element(expected_type=RichText, allow_none=True, default=None)
textPropertes = AliasField("txPr", default=None)
xml_order = ("layout", "tx", "spPr", "txPr")
def __init__(
self,
layout=None,
tx=None,
spPr=None,
txPr=None,
):
self.layout = layout
self.tx = tx
self.spPr = spPr
self.txPr = txPr
[docs]
class DisplayUnitsLabelList(Serialisable):
tagname = "dispUnits"
custUnit: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
builtInUnit: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(
frozenset(
{
"hundreds",
"thousands",
"tenThousands",
"hundredThousands",
"millions",
"tenMillions",
"hundredMillions",
"billions",
"trillions",
}
),
"builtInUnit",
), default=None,
)
dispUnitsLbl: DisplayUnitsLabel | None = Field.element(
expected_type=DisplayUnitsLabel, allow_none=True, default=None
)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
xml_order = ("custUnit", "builtInUnit", "dispUnitsLbl")
def __init__(
self,
custUnit=None,
builtInUnit=None,
dispUnitsLbl=None,
extLst=None,
):
self.custUnit = custUnit
self.builtInUnit = builtInUnit
self.dispUnitsLbl = dispUnitsLbl
self.extLst = extLst
[docs]
class NumericAxis(_BaseAxis):
tagname = "valAx"
crossBetween: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"between", "midCat"}), "crossBetween"), default=None,
)
majorUnit: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
minorUnit: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
dispUnits: DisplayUnitsLabelList | None = Field.element(
expected_type=DisplayUnitsLabelList, allow_none=True, default=None
)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
xml_order = _BaseAxis.xml_order + (
"crossBetween",
"majorUnit",
"minorUnit",
"dispUnits",
)
def __init__(
self,
crossBetween=None,
majorUnit=None,
minorUnit=None,
dispUnits=None,
extLst=None,
**kw,
):
self.crossBetween = crossBetween
self.majorUnit = majorUnit
self.minorUnit = minorUnit
self.dispUnits = dispUnits
self.extLst = extLst
kw.setdefault("majorGridlines", ChartLines())
kw.setdefault("axId", 100)
kw.setdefault("crossAx", 10)
super().__init__(**kw)
[docs]
@classmethod
def from_tree(cls, node):
self = super().from_tree(node)
gridlines = node.find("{%s}majorGridlines" % CHART_NS)
if gridlines is None:
self.majorGridlines = None
return self
[docs]
class TextAxis(_BaseAxis):
tagname = "catAx"
auto: bool | None = Field.nested_bool(allow_none=True, default=None)
lblAlgn: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"ctr", "l", "r"}), "lblAlgn"), default=None,
)
lblOffset: int | None = Field.nested_value(
expected_type=int,
allow_none=True,
converter=lambda v: _chart_lbl_offset_minmax(
v, field_name="lblOffset", min_v=0, max_v=1000
), default=None,
)
tickLblSkip: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
tickMarkSkip: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
noMultiLvlLbl: bool | None = Field.nested_bool(allow_none=True, default=None)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
xml_order = _BaseAxis.xml_order + (
"auto",
"lblAlgn",
"lblOffset",
"tickLblSkip",
"tickMarkSkip",
"noMultiLvlLbl",
)
def __init__(
self,
auto=None,
lblAlgn=None,
lblOffset=100,
tickLblSkip=None,
tickMarkSkip=None,
noMultiLvlLbl=None,
extLst=None,
**kw,
):
self.auto = auto
self.lblAlgn = lblAlgn
self.lblOffset = lblOffset
self.tickLblSkip = tickLblSkip
self.tickMarkSkip = tickMarkSkip
self.noMultiLvlLbl = noMultiLvlLbl
self.extLst = extLst
kw.setdefault("axId", 10)
kw.setdefault("crossAx", 100)
super().__init__(**kw)
[docs]
class DateAxis(TextAxis):
tagname = "dateAx"
lblOffset: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
baseTimeUnit: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"days", "months", "years"}), "baseTimeUnit"), default=None,
)
majorUnit: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
majorTimeUnit: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"days", "months", "years"}), "majorTimeUnit"), default=None,
)
minorUnit: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None)
minorTimeUnit: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=_none_set(frozenset({"days", "months", "years"}), "minorTimeUnit"), default=None,
)
xml_order = (
"axId",
"scaling",
"delete",
"axPos",
"majorGridlines",
"minorGridlines",
"title",
"numFmt",
"majorTickMark",
"minorTickMark",
"tickLblPos",
"spPr",
"txPr",
"crossAx",
"crosses",
"crossesAt",
"auto",
"lblOffset",
"baseTimeUnit",
"majorUnit",
"majorTimeUnit",
"minorUnit",
"minorTimeUnit",
)
def __init__(
self,
auto=None,
lblOffset=None,
baseTimeUnit=None,
majorUnit=None,
majorTimeUnit=None,
minorUnit=None,
minorTimeUnit=None,
extLst=None,
**kw,
):
self.baseTimeUnit = baseTimeUnit
self.majorUnit = majorUnit
self.majorTimeUnit = majorTimeUnit
self.minorUnit = minorUnit
self.minorTimeUnit = minorTimeUnit
kw.setdefault("axId", 500)
kw.setdefault("lblOffset", lblOffset)
super().__init__(auto=auto, extLst=extLst, **kw)
[docs]
class SeriesAxis(_BaseAxis):
tagname = "serAx"
tickLblSkip: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
tickMarkSkip: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
xml_order = _BaseAxis.xml_order + ("tickLblSkip", "tickMarkSkip")
def __init__(
self,
tickLblSkip=None,
tickMarkSkip=None,
extLst=None,
**kw,
):
self.tickLblSkip = tickLblSkip
self.tickMarkSkip = tickMarkSkip
self.extLst = extLst
kw.setdefault("axId", 1000)
kw.setdefault("crossAx", 10)
super().__init__(**kw)