axis.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. # Copyright (c) 2010-2024 openpyxl
  2. from openpyxl.descriptors.serialisable import Serialisable
  3. from openpyxl.descriptors import (
  4. Typed,
  5. Float,
  6. NoneSet,
  7. Bool,
  8. Integer,
  9. MinMax,
  10. NoneSet,
  11. Set,
  12. String,
  13. Alias,
  14. )
  15. from openpyxl.descriptors.excel import (
  16. ExtensionList,
  17. Percentage,
  18. _explicit_none,
  19. )
  20. from openpyxl.descriptors.nested import (
  21. NestedValue,
  22. NestedSet,
  23. NestedBool,
  24. NestedNoneSet,
  25. NestedFloat,
  26. NestedInteger,
  27. NestedMinMax,
  28. )
  29. from openpyxl.xml.constants import CHART_NS
  30. from .descriptors import NumberFormatDescriptor
  31. from .layout import Layout
  32. from .text import Text, RichText
  33. from .shapes import GraphicalProperties
  34. from .title import Title, TitleDescriptor
  35. class ChartLines(Serialisable):
  36. tagname = "chartLines"
  37. spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
  38. graphicalProperties = Alias('spPr')
  39. def __init__(self, spPr=None):
  40. self.spPr = spPr
  41. class Scaling(Serialisable):
  42. tagname = "scaling"
  43. logBase = NestedFloat(allow_none=True)
  44. orientation = NestedSet(values=(['maxMin', 'minMax']))
  45. max = NestedFloat(allow_none=True)
  46. min = NestedFloat(allow_none=True)
  47. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  48. __elements__ = ('logBase', 'orientation', 'max', 'min',)
  49. def __init__(self,
  50. logBase=None,
  51. orientation="minMax",
  52. max=None,
  53. min=None,
  54. extLst=None,
  55. ):
  56. self.logBase = logBase
  57. self.orientation = orientation
  58. self.max = max
  59. self.min = min
  60. class _BaseAxis(Serialisable):
  61. axId = NestedInteger(expected_type=int)
  62. scaling = Typed(expected_type=Scaling)
  63. delete = NestedBool(allow_none=True)
  64. axPos = NestedSet(values=(['b', 'l', 'r', 't']))
  65. majorGridlines = Typed(expected_type=ChartLines, allow_none=True)
  66. minorGridlines = Typed(expected_type=ChartLines, allow_none=True)
  67. title = TitleDescriptor()
  68. numFmt = NumberFormatDescriptor()
  69. number_format = Alias("numFmt")
  70. majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
  71. minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
  72. tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo']))
  73. spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
  74. graphicalProperties = Alias('spPr')
  75. txPr = Typed(expected_type=RichText, allow_none=True)
  76. textProperties = Alias('txPr')
  77. crossAx = NestedInteger(expected_type=int) # references other axis
  78. crosses = NestedNoneSet(values=(['autoZero', 'max', 'min']))
  79. crossesAt = NestedFloat(allow_none=True)
  80. # crosses & crossesAt are mutually exclusive
  81. __elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines',
  82. 'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark',
  83. 'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt')
  84. def __init__(self,
  85. axId=None,
  86. scaling=None,
  87. delete=None,
  88. axPos='l',
  89. majorGridlines=None,
  90. minorGridlines=None,
  91. title=None,
  92. numFmt=None,
  93. majorTickMark=None,
  94. minorTickMark=None,
  95. tickLblPos=None,
  96. spPr=None,
  97. txPr= None,
  98. crossAx=None,
  99. crosses=None,
  100. crossesAt=None,
  101. ):
  102. self.axId = axId
  103. if scaling is None:
  104. scaling = Scaling()
  105. self.scaling = scaling
  106. self.delete = delete
  107. self.axPos = axPos
  108. self.majorGridlines = majorGridlines
  109. self.minorGridlines = minorGridlines
  110. self.title = title
  111. self.numFmt = numFmt
  112. self.majorTickMark = majorTickMark
  113. self.minorTickMark = minorTickMark
  114. self.tickLblPos = tickLblPos
  115. self.spPr = spPr
  116. self.txPr = txPr
  117. self.crossAx = crossAx
  118. self.crosses = crosses
  119. self.crossesAt = crossesAt
  120. class DisplayUnitsLabel(Serialisable):
  121. tagname = "dispUnitsLbl"
  122. layout = Typed(expected_type=Layout, allow_none=True)
  123. tx = Typed(expected_type=Text, allow_none=True)
  124. text = Alias("tx")
  125. spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
  126. graphicalProperties = Alias("spPr")
  127. txPr = Typed(expected_type=RichText, allow_none=True)
  128. textPropertes = Alias("txPr")
  129. __elements__ = ('layout', 'tx', 'spPr', 'txPr')
  130. def __init__(self,
  131. layout=None,
  132. tx=None,
  133. spPr=None,
  134. txPr=None,
  135. ):
  136. self.layout = layout
  137. self.tx = tx
  138. self.spPr = spPr
  139. self.txPr = txPr
  140. class DisplayUnitsLabelList(Serialisable):
  141. tagname = "dispUnits"
  142. custUnit = NestedFloat(allow_none=True)
  143. builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands',
  144. 'tenThousands', 'hundredThousands', 'millions', 'tenMillions',
  145. 'hundredMillions', 'billions', 'trillions']))
  146. dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True)
  147. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  148. __elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',)
  149. def __init__(self,
  150. custUnit=None,
  151. builtInUnit=None,
  152. dispUnitsLbl=None,
  153. extLst=None,
  154. ):
  155. self.custUnit = custUnit
  156. self.builtInUnit = builtInUnit
  157. self.dispUnitsLbl = dispUnitsLbl
  158. class NumericAxis(_BaseAxis):
  159. tagname = "valAx"
  160. axId = _BaseAxis.axId
  161. scaling = _BaseAxis.scaling
  162. delete = _BaseAxis.delete
  163. axPos = _BaseAxis.axPos
  164. majorGridlines = _BaseAxis.majorGridlines
  165. minorGridlines = _BaseAxis.minorGridlines
  166. title = _BaseAxis.title
  167. numFmt = _BaseAxis.numFmt
  168. majorTickMark = _BaseAxis.majorTickMark
  169. minorTickMark = _BaseAxis.minorTickMark
  170. tickLblPos = _BaseAxis.tickLblPos
  171. spPr = _BaseAxis.spPr
  172. txPr = _BaseAxis.txPr
  173. crossAx = _BaseAxis.crossAx
  174. crosses = _BaseAxis.crosses
  175. crossesAt = _BaseAxis.crossesAt
  176. crossBetween = NestedNoneSet(values=(['between', 'midCat']))
  177. majorUnit = NestedFloat(allow_none=True)
  178. minorUnit = NestedFloat(allow_none=True)
  179. dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
  180. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  181. __elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
  182. 'minorUnit', 'dispUnits',)
  183. def __init__(self,
  184. crossBetween=None,
  185. majorUnit=None,
  186. minorUnit=None,
  187. dispUnits=None,
  188. extLst=None,
  189. **kw
  190. ):
  191. self.crossBetween = crossBetween
  192. self.majorUnit = majorUnit
  193. self.minorUnit = minorUnit
  194. self.dispUnits = dispUnits
  195. kw.setdefault('majorGridlines', ChartLines())
  196. kw.setdefault('axId', 100)
  197. kw.setdefault('crossAx', 10)
  198. super().__init__(**kw)
  199. @classmethod
  200. def from_tree(cls, node):
  201. """
  202. Special case value axes with no gridlines
  203. """
  204. self = super().from_tree(node)
  205. gridlines = node.find("{%s}majorGridlines" % CHART_NS)
  206. if gridlines is None:
  207. self.majorGridlines = None
  208. return self
  209. class TextAxis(_BaseAxis):
  210. tagname = "catAx"
  211. axId = _BaseAxis.axId
  212. scaling = _BaseAxis.scaling
  213. delete = _BaseAxis.delete
  214. axPos = _BaseAxis.axPos
  215. majorGridlines = _BaseAxis.majorGridlines
  216. minorGridlines = _BaseAxis.minorGridlines
  217. title = _BaseAxis.title
  218. numFmt = _BaseAxis.numFmt
  219. majorTickMark = _BaseAxis.majorTickMark
  220. minorTickMark = _BaseAxis.minorTickMark
  221. tickLblPos = _BaseAxis.tickLblPos
  222. spPr = _BaseAxis.spPr
  223. txPr = _BaseAxis.txPr
  224. crossAx = _BaseAxis.crossAx
  225. crosses = _BaseAxis.crosses
  226. crossesAt = _BaseAxis.crossesAt
  227. auto = NestedBool(allow_none=True)
  228. lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r']))
  229. lblOffset = NestedMinMax(min=0, max=1000)
  230. tickLblSkip = NestedInteger(allow_none=True)
  231. tickMarkSkip = NestedInteger(allow_none=True)
  232. noMultiLvlLbl = NestedBool(allow_none=True)
  233. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  234. __elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset',
  235. 'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl')
  236. def __init__(self,
  237. auto=None,
  238. lblAlgn=None,
  239. lblOffset=100,
  240. tickLblSkip=None,
  241. tickMarkSkip=None,
  242. noMultiLvlLbl=None,
  243. extLst=None,
  244. **kw
  245. ):
  246. self.auto = auto
  247. self.lblAlgn = lblAlgn
  248. self.lblOffset = lblOffset
  249. self.tickLblSkip = tickLblSkip
  250. self.tickMarkSkip = tickMarkSkip
  251. self.noMultiLvlLbl = noMultiLvlLbl
  252. kw.setdefault('axId', 10)
  253. kw.setdefault('crossAx', 100)
  254. super().__init__(**kw)
  255. class DateAxis(TextAxis):
  256. tagname = "dateAx"
  257. axId = _BaseAxis.axId
  258. scaling = _BaseAxis.scaling
  259. delete = _BaseAxis.delete
  260. axPos = _BaseAxis.axPos
  261. majorGridlines = _BaseAxis.majorGridlines
  262. minorGridlines = _BaseAxis.minorGridlines
  263. title = _BaseAxis.title
  264. numFmt = _BaseAxis.numFmt
  265. majorTickMark = _BaseAxis.majorTickMark
  266. minorTickMark = _BaseAxis.minorTickMark
  267. tickLblPos = _BaseAxis.tickLblPos
  268. spPr = _BaseAxis.spPr
  269. txPr = _BaseAxis.txPr
  270. crossAx = _BaseAxis.crossAx
  271. crosses = _BaseAxis.crosses
  272. crossesAt = _BaseAxis.crossesAt
  273. auto = NestedBool(allow_none=True)
  274. lblOffset = NestedInteger(allow_none=True)
  275. baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
  276. majorUnit = NestedFloat(allow_none=True)
  277. majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
  278. minorUnit = NestedFloat(allow_none=True)
  279. minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
  280. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  281. __elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset',
  282. 'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit',
  283. 'minorTimeUnit')
  284. def __init__(self,
  285. auto=None,
  286. lblOffset=None,
  287. baseTimeUnit=None,
  288. majorUnit=None,
  289. majorTimeUnit=None,
  290. minorUnit=None,
  291. minorTimeUnit=None,
  292. extLst=None,
  293. **kw
  294. ):
  295. self.auto = auto
  296. self.lblOffset = lblOffset
  297. self.baseTimeUnit = baseTimeUnit
  298. self.majorUnit = majorUnit
  299. self.majorTimeUnit = majorTimeUnit
  300. self.minorUnit = minorUnit
  301. self.minorTimeUnit = minorTimeUnit
  302. kw.setdefault('axId', 500)
  303. kw.setdefault('lblOffset', lblOffset)
  304. super().__init__(**kw)
  305. class SeriesAxis(_BaseAxis):
  306. tagname = "serAx"
  307. axId = _BaseAxis.axId
  308. scaling = _BaseAxis.scaling
  309. delete = _BaseAxis.delete
  310. axPos = _BaseAxis.axPos
  311. majorGridlines = _BaseAxis.majorGridlines
  312. minorGridlines = _BaseAxis.minorGridlines
  313. title = _BaseAxis.title
  314. numFmt = _BaseAxis.numFmt
  315. majorTickMark = _BaseAxis.majorTickMark
  316. minorTickMark = _BaseAxis.minorTickMark
  317. tickLblPos = _BaseAxis.tickLblPos
  318. spPr = _BaseAxis.spPr
  319. txPr = _BaseAxis.txPr
  320. crossAx = _BaseAxis.crossAx
  321. crosses = _BaseAxis.crosses
  322. crossesAt = _BaseAxis.crossesAt
  323. tickLblSkip = NestedInteger(allow_none=True)
  324. tickMarkSkip = NestedInteger(allow_none=True)
  325. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  326. __elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip')
  327. def __init__(self,
  328. tickLblSkip=None,
  329. tickMarkSkip=None,
  330. extLst=None,
  331. **kw
  332. ):
  333. self.tickLblSkip = tickLblSkip
  334. self.tickMarkSkip = tickMarkSkip
  335. kw.setdefault('axId', 1000)
  336. kw.setdefault('crossAx', 10)
  337. super().__init__(**kw)