monopyly 1.5.2__py3-none-any.whl → 1.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. monopyly/CHANGELOG.md +22 -0
  2. monopyly/README.md +1 -1
  3. monopyly/__init__.py +1 -1
  4. monopyly/_version.py +2 -2
  5. monopyly/auth/blueprint.py +2 -0
  6. monopyly/auth/routes.py +1 -2
  7. monopyly/banking/accounts.py +4 -4
  8. monopyly/banking/actions.py +20 -17
  9. monopyly/banking/blueprint.py +2 -0
  10. monopyly/banking/filters.py +6 -6
  11. monopyly/banking/forms.py +7 -5
  12. monopyly/banking/routes.py +71 -9
  13. monopyly/banking/transactions.py +2 -3
  14. monopyly/common/forms/__init__.py +8 -0
  15. monopyly/common/forms/_forms.py +6 -16
  16. monopyly/common/forms/fields.py +0 -11
  17. monopyly/common/forms/utils.py +4 -4
  18. monopyly/common/transactions.py +72 -7
  19. monopyly/core/actions.py +2 -1
  20. monopyly/core/blueprint.py +2 -0
  21. monopyly/core/context_processors.py +3 -21
  22. monopyly/core/filters.py +0 -2
  23. monopyly/core/routes.py +1 -1
  24. monopyly/credit/actions.py +4 -5
  25. monopyly/credit/blueprint.py +2 -0
  26. monopyly/credit/cards.py +1 -1
  27. monopyly/credit/forms.py +9 -7
  28. monopyly/credit/routes.py +37 -62
  29. monopyly/credit/transactions/__init__.py +2 -0
  30. monopyly/credit/transactions/_transactions.py +1 -4
  31. monopyly/credit/transactions/activity/__init__.py +6 -0
  32. monopyly/credit/transactions/activity/parser.py +0 -1
  33. monopyly/credit/transactions/activity/reconciliation.py +5 -3
  34. monopyly/database/__init__.py +0 -3
  35. monopyly/database/models.py +63 -49
  36. monopyly/database/preloads.sql +6 -1
  37. monopyly/scripts/screenshot_application.py +100 -0
  38. monopyly/static/chartist-1.5.0.min.js +8 -0
  39. monopyly/static/css/style.css +39 -14
  40. monopyly/static/img/about/bank-account-details.png +0 -0
  41. monopyly/static/img/about/bank-account-summaries.png +0 -0
  42. monopyly/static/img/about/bank-accounts.png +0 -0
  43. monopyly/static/img/about/credit-account-details.png +0 -0
  44. monopyly/static/img/about/credit-statement-details.png +0 -0
  45. monopyly/static/img/about/credit-transactions.png +0 -0
  46. monopyly/static/img/about/homepage-user.png +0 -0
  47. monopyly/static/img/about/homepage.png +0 -0
  48. monopyly/static/jquery-3.7.1.min.js +2 -0
  49. monopyly/static/js/add-transfer.js +8 -9
  50. monopyly/static/js/bind-tag-actions.js +6 -0
  51. monopyly/static/js/create-balance-chart.js +1 -1
  52. monopyly/static/js/create-category-chart.js +1 -1
  53. monopyly/static/js/load-more-transactions.js +27 -0
  54. monopyly/static/js/modules/expand-transaction.js +7 -6
  55. monopyly/static/js/modules/update-display-ajax.js +20 -1
  56. monopyly/static/js/update-transactions-display.js +8 -2
  57. monopyly/templates/banking/account_form/account_form_page_new.html +10 -8
  58. monopyly/templates/banking/account_page.html +32 -33
  59. monopyly/templates/banking/account_summaries.html +2 -2
  60. monopyly/templates/banking/account_summary.html +1 -1
  61. monopyly/templates/banking/accounts_page.html +10 -8
  62. monopyly/templates/banking/transactions_table/table.html +3 -0
  63. monopyly/templates/banking/transactions_table/transactions.html +0 -1
  64. monopyly/templates/common/tag_tree.html +25 -0
  65. monopyly/templates/{credit → common}/tags_page.html +17 -13
  66. monopyly/templates/common/transactions_table/linked_bank_transaction.html +2 -2
  67. monopyly/templates/common/transactions_table/table.html +6 -0
  68. monopyly/templates/common/transactions_table/transactions.html +9 -15
  69. monopyly/templates/core/index.html +124 -109
  70. monopyly/templates/core/profile.html +19 -20
  71. monopyly/templates/credit/account_page.html +17 -17
  72. monopyly/templates/credit/card_form/card_form_page_new.html +8 -5
  73. monopyly/templates/credit/statement_page.html +45 -46
  74. monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +22 -22
  75. monopyly/templates/credit/statement_summary.html +2 -2
  76. monopyly/templates/credit/statements_page.html +13 -13
  77. monopyly/templates/credit/transaction_form/transaction_form_page_update.html +9 -9
  78. monopyly/templates/credit/transaction_submission_page.html +3 -3
  79. monopyly/templates/credit/transactions_page.html +38 -22
  80. monopyly/templates/credit/transactions_table/condensed_row_content.html +2 -2
  81. monopyly/templates/credit/transactions_table/expanded_row_content.html +5 -5
  82. monopyly/templates/credit/transactions_table/table.html +3 -0
  83. monopyly/templates/credit/transactions_table/transactions.html +0 -1
  84. monopyly/templates/layout.html +16 -9
  85. {monopyly-1.5.2.dist-info → monopyly-1.6.1.dist-info}/METADATA +8 -8
  86. {monopyly-1.5.2.dist-info → monopyly-1.6.1.dist-info}/RECORD +90 -84
  87. monopyly-1.6.1.dist-info/entry_points.txt +3 -0
  88. monopyly/static/jquery-3.7.0.min.js +0 -2
  89. monopyly/templates/credit/tag_tree/subtag_tree.html +0 -22
  90. monopyly/templates/credit/tag_tree/tag_tree.html +0 -13
  91. monopyly-1.5.2.dist-info/entry_points.txt +0 -2
  92. {monopyly-1.5.2.dist-info → monopyly-1.6.1.dist-info}/WHEEL +0 -0
  93. {monopyly-1.5.2.dist-info → monopyly-1.6.1.dist-info}/licenses/COPYING +0 -0
  94. {monopyly-1.5.2.dist-info → monopyly-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,7 @@
1
1
  import datetime
2
- from typing import List, Optional
3
2
 
4
3
  from dry_foundation.database.models import AuthorizedAccessMixin, Model
5
- from sqlalchemy import Column, Date, Float, ForeignKey, Integer, String, Table
6
- from sqlalchemy.ext.hybrid import hybrid_property
4
+ from sqlalchemy import Column, ForeignKey, Integer, Table
7
5
  from sqlalchemy.orm import Mapped, mapped_column, relationship
8
6
 
9
7
 
@@ -15,7 +13,7 @@ class User(Model):
15
13
  password: Mapped[str]
16
14
  # Relationships
17
15
  banks: Mapped["Bank"] = relationship(back_populates="user", cascade="all, delete")
18
- bank_account_types: Mapped["BankAccountTypeView"] = relationship(
16
+ bank_account_type_views: Mapped[list["BankAccountTypeView"]] = relationship(
19
17
  back_populates="user", viewonly=True
20
18
  )
21
19
 
@@ -25,16 +23,23 @@ class InternalTransaction(Model):
25
23
  # Columns
26
24
  id: Mapped[int] = mapped_column(primary_key=True)
27
25
  # Relationships
28
- bank_transactions: Mapped[List["BankTransactionView"]] = relationship(
26
+ bank_transactions: Mapped[list["BankTransaction"]] = relationship(
29
27
  back_populates="internal_transaction"
30
28
  )
31
- credit_transactions: Mapped[List["CreditTransactionView"]] = relationship(
29
+ bank_transaction_views: Mapped[list["BankTransactionView"]] = relationship(
30
+ back_populates="internal_transaction", viewonly=True
31
+ )
32
+ credit_transactions: Mapped[list["CreditTransaction"]] = relationship(
32
33
  back_populates="internal_transaction"
33
34
  )
35
+ credit_transaction_views: Mapped[list["CreditTransactionView"]] = relationship(
36
+ back_populates="internal_transaction", viewonly=True
37
+ )
34
38
 
39
+ @property
40
+ def transaction_views(self):
41
+ return self.bank_transaction_views + self.credit_transaction_views
35
42
 
36
- # Not sure why these tables are necessary, because they should already be reflected;
37
- # perhaps explore or wait until sqlalchemy 2.0
38
43
 
39
44
  bank_tag_link_table = Table(
40
45
  "bank_tag_links",
@@ -62,20 +67,21 @@ credit_tag_link_table = Table(
62
67
 
63
68
  class TransactionTag(AuthorizedAccessMixin, Model):
64
69
  __tablename__ = "transaction_tags"
70
+ _alt_authorized_ids = (0,)
65
71
  # Columns
66
72
  id: Mapped[int] = mapped_column(primary_key=True)
67
73
  user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
68
- parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("transaction_tags.id"))
74
+ parent_id: Mapped[int | None] = mapped_column(ForeignKey("transaction_tags.id"))
69
75
  tag_name: Mapped[str]
70
76
  # Relationships
71
77
  parent: Mapped["TransactionTag"] = relationship(
72
78
  back_populates="children", remote_side=[id]
73
79
  )
74
- children: Mapped[List["TransactionTag"]] = relationship(back_populates="parent")
75
- bank_subtransactions: Mapped[List["BankSubtransaction"]] = relationship(
80
+ children: Mapped[list["TransactionTag"]] = relationship(back_populates="parent")
81
+ bank_subtransactions: Mapped[list["BankSubtransaction"]] = relationship(
76
82
  back_populates="tags", secondary=bank_tag_link_table
77
83
  )
78
- credit_subtransactions: Mapped[List["CreditSubtransaction"]] = relationship(
84
+ credit_subtransactions: Mapped[list["CreditSubtransaction"]] = relationship(
79
85
  back_populates="tags", secondary=credit_tag_link_table
80
86
  )
81
87
 
@@ -95,10 +101,10 @@ class Bank(AuthorizedAccessMixin, Model):
95
101
  bank_name: Mapped[str]
96
102
  # Relationships
97
103
  user: Mapped["User"] = relationship(back_populates="banks")
98
- bank_accounts: Mapped[List["BankAccountView"]] = relationship(
104
+ bank_account_views: Mapped[list["BankAccountView"]] = relationship(
99
105
  back_populates="bank", cascade="all, delete", viewonly=True
100
106
  )
101
- credit_accounts: Mapped[List["CreditAccount"]] = relationship(
107
+ credit_accounts: Mapped[list["CreditAccount"]] = relationship(
102
108
  back_populates="bank", cascade="all, delete"
103
109
  )
104
110
 
@@ -110,7 +116,7 @@ class BankAccountType(AuthorizedAccessMixin, Model):
110
116
  id: Mapped[int] = mapped_column(primary_key=True)
111
117
  user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
112
118
  type_name: Mapped[str]
113
- type_abbreviation: Mapped[Optional[str]]
119
+ type_abbreviation: Mapped[str | None]
114
120
  # Relationships
115
121
  view: Mapped["BankAccountTypeView"] = relationship(
116
122
  back_populates="account_type", uselist=False, viewonly=True
@@ -124,13 +130,13 @@ class BankAccountTypeView(AuthorizedAccessMixin, Model):
124
130
  id = mapped_column(Integer, ForeignKey("bank_account_types.id"), primary_key=True)
125
131
  user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
126
132
  type_name: Mapped[str]
127
- type_abbreviation: Mapped[Optional[str]]
133
+ type_abbreviation: Mapped[str | None]
128
134
  type_common_name: Mapped[str]
129
135
  # Relationships
130
136
  account_type: Mapped["BankAccountType"] = relationship(back_populates="view")
131
- user: Mapped["User"] = relationship(back_populates="bank_account_types")
132
- accounts: Mapped[List["BankAccountView"]] = relationship(
133
- back_populates="account_type", viewonly=True
137
+ user: Mapped["User"] = relationship(back_populates="bank_account_type_views")
138
+ account_views: Mapped[list["BankAccountView"]] = relationship(
139
+ back_populates="account_type_view", viewonly=True
134
140
  )
135
141
 
136
142
 
@@ -166,12 +172,12 @@ class BankAccountView(AuthorizedAccessMixin, Model):
166
172
  projected_balance: Mapped[float]
167
173
  # Relationships
168
174
  account: Mapped["BankAccount"] = relationship(back_populates="view")
169
- bank: Mapped["Bank"] = relationship(back_populates="bank_accounts")
170
- account_type: Mapped["BankAccountTypeView"] = relationship(
171
- back_populates="accounts", viewonly=True
175
+ bank: Mapped["Bank"] = relationship(back_populates="bank_account_views")
176
+ account_type_view: Mapped["BankAccountTypeView"] = relationship(
177
+ back_populates="account_views", viewonly=True
172
178
  )
173
- transactions: Mapped[List["BankTransactionView"]] = relationship(
174
- back_populates="account", viewonly=True
179
+ transaction_views: Mapped[list["BankTransactionView"]] = relationship(
180
+ back_populates="account_view", viewonly=True
175
181
  )
176
182
 
177
183
 
@@ -182,16 +188,19 @@ class BankTransaction(AuthorizedAccessMixin, Model):
182
188
  subtype = "bank"
183
189
  # Columns
184
190
  id: Mapped[int] = mapped_column(primary_key=True)
185
- internal_transaction_id: Mapped[Optional[int]] = mapped_column(
191
+ internal_transaction_id: Mapped[int | None] = mapped_column(
186
192
  ForeignKey("internal_transactions.id")
187
193
  )
188
194
  account_id: Mapped[int] = mapped_column(ForeignKey("bank_accounts_view.id"))
189
195
  transaction_date: Mapped[datetime.date]
190
- merchant: Mapped[Optional[str]]
196
+ merchant: Mapped[str | None]
191
197
  # Relationships
192
198
  view: Mapped["BankTransactionView"] = relationship(
193
199
  back_populates="transaction", uselist=False, viewonly=True
194
200
  )
201
+ internal_transaction: Mapped["InternalTransaction"] = relationship(
202
+ back_populates="bank_transactions"
203
+ )
195
204
 
196
205
 
197
206
  class BankTransactionView(AuthorizedAccessMixin, Model):
@@ -203,23 +212,25 @@ class BankTransactionView(AuthorizedAccessMixin, Model):
203
212
  id: Mapped[int] = mapped_column(
204
213
  ForeignKey("bank_transactions.id"), primary_key=True
205
214
  )
206
- internal_transaction_id: Mapped[Optional[int]] = mapped_column(
215
+ internal_transaction_id: Mapped[int | None] = mapped_column(
207
216
  ForeignKey("internal_transactions.id")
208
217
  )
209
218
  account_id: Mapped[int] = mapped_column(ForeignKey("bank_accounts_view.id"))
210
219
  transaction_date: Mapped[datetime.date]
211
- merchant: Mapped[Optional[str]]
220
+ merchant: Mapped[str | None]
212
221
  total: Mapped[float]
213
- notes: Mapped[Optional[str]]
222
+ notes: Mapped[str | None]
214
223
  balance: Mapped[float]
215
224
  # Relationships
216
225
  transaction: Mapped["BankTransaction"] = relationship(back_populates="view")
217
226
  internal_transaction: Mapped["InternalTransaction"] = relationship(
218
- back_populates="bank_transactions"
227
+ back_populates="bank_transaction_views"
228
+ )
229
+ account_view: Mapped["BankAccountView"] = relationship(
230
+ back_populates="transaction_views", viewonly=True
219
231
  )
220
- account: Mapped["BankAccountView"] = relationship(back_populates="transactions")
221
- subtransactions: Mapped[List["BankSubtransaction"]] = relationship(
222
- back_populates="transaction", lazy="selectin"
232
+ subtransactions: Mapped[list["BankSubtransaction"]] = relationship(
233
+ back_populates="transaction_view", lazy="selectin", cascade="all, delete"
223
234
  )
224
235
 
225
236
 
@@ -232,10 +243,10 @@ class BankSubtransaction(AuthorizedAccessMixin, Model):
232
243
  subtotal: Mapped[float]
233
244
  note: Mapped[str]
234
245
  # Relationships
235
- transaction: Mapped["BankTransactionView"] = relationship(
246
+ transaction_view: Mapped["BankTransactionView"] = relationship(
236
247
  back_populates="subtransactions", viewonly=True
237
248
  )
238
- tags: Mapped[List["TransactionTag"]] = relationship(
249
+ tags: Mapped[list["TransactionTag"]] = relationship(
239
250
  back_populates="bank_subtransactions",
240
251
  secondary=bank_tag_link_table,
241
252
  lazy="selectin",
@@ -253,7 +264,7 @@ class CreditAccount(AuthorizedAccessMixin, Model):
253
264
  # ((Should probably have an 'active' field))
254
265
  # Relationships
255
266
  bank: Mapped["Bank"] = relationship(back_populates="credit_accounts")
256
- cards: Mapped[List["CreditCard"]] = relationship(
267
+ cards: Mapped[list["CreditCard"]] = relationship(
257
268
  back_populates="account", cascade="all, delete"
258
269
  )
259
270
 
@@ -268,7 +279,7 @@ class CreditCard(AuthorizedAccessMixin, Model):
268
279
  active: Mapped[int]
269
280
  # Relationships
270
281
  account: Mapped["CreditAccount"] = relationship(back_populates="cards")
271
- statements: Mapped[List["CreditStatementView"]] = relationship(
282
+ statement_views: Mapped[list["CreditStatementView"]] = relationship(
272
283
  back_populates="card", viewonly=True
273
284
  )
274
285
 
@@ -301,9 +312,9 @@ class CreditStatementView(AuthorizedAccessMixin, Model):
301
312
  payment_date: Mapped[datetime.date]
302
313
  # Relationships
303
314
  statement: Mapped["CreditStatement"] = relationship(back_populates="view")
304
- card: Mapped["CreditCard"] = relationship(back_populates="statements")
305
- transactions: Mapped[List["CreditTransactionView"]] = relationship(
306
- back_populates="statement", viewonly=True
315
+ card: Mapped["CreditCard"] = relationship(back_populates="statement_views")
316
+ transaction_views: Mapped[list["CreditTransactionView"]] = relationship(
317
+ back_populates="statement_view", viewonly=True
307
318
  )
308
319
 
309
320
 
@@ -314,7 +325,7 @@ class CreditTransaction(AuthorizedAccessMixin, Model):
314
325
  subtype = "credit"
315
326
  # Columns
316
327
  id: Mapped[int] = mapped_column(primary_key=True)
317
- internal_transaction_id: Mapped[Optional[int]] = mapped_column(
328
+ internal_transaction_id: Mapped[int | None] = mapped_column(
318
329
  ForeignKey("internal_transactions.id")
319
330
  )
320
331
  statement_id: Mapped[int] = mapped_column(ForeignKey("credit_statements_view.id"))
@@ -324,6 +335,9 @@ class CreditTransaction(AuthorizedAccessMixin, Model):
324
335
  view: Mapped["CreditTransactionView"] = relationship(
325
336
  back_populates="transaction", uselist=False, viewonly=True
326
337
  )
338
+ internal_transaction: Mapped["InternalTransaction"] = relationship(
339
+ back_populates="credit_transactions"
340
+ )
327
341
 
328
342
 
329
343
  class CreditTransactionView(AuthorizedAccessMixin, Model):
@@ -335,7 +349,7 @@ class CreditTransactionView(AuthorizedAccessMixin, Model):
335
349
  id: Mapped[int] = mapped_column(
336
350
  ForeignKey("credit_transactions.id"), primary_key=True
337
351
  )
338
- internal_transaction_id: Mapped[Optional[int]] = mapped_column(
352
+ internal_transaction_id: Mapped[int | None] = mapped_column(
339
353
  ForeignKey("internal_transactions.id")
340
354
  )
341
355
  statement_id: Mapped[int] = mapped_column(ForeignKey("credit_statements_view.id"))
@@ -348,13 +362,13 @@ class CreditTransactionView(AuthorizedAccessMixin, Model):
348
362
  back_populates="view", uselist=False
349
363
  )
350
364
  internal_transaction: Mapped["InternalTransaction"] = relationship(
351
- back_populates="credit_transactions"
365
+ back_populates="credit_transaction_views"
352
366
  )
353
- statement: Mapped["CreditStatementView"] = relationship(
354
- back_populates="transactions", viewonly=True
367
+ statement_view: Mapped["CreditStatementView"] = relationship(
368
+ back_populates="transaction_views", viewonly=True
355
369
  )
356
- subtransactions: Mapped[List["CreditSubtransaction"]] = relationship(
357
- back_populates="transaction", lazy="selectin"
370
+ subtransactions: Mapped[list["CreditSubtransaction"]] = relationship(
371
+ back_populates="transaction_view", lazy="selectin", cascade="all, delete"
358
372
  )
359
373
 
360
374
 
@@ -375,10 +389,10 @@ class CreditSubtransaction(AuthorizedAccessMixin, Model):
375
389
  subtotal: Mapped[float]
376
390
  note: Mapped[str]
377
391
  # Relationships
378
- transaction: Mapped["CreditTransactionView"] = relationship(
392
+ transaction_view: Mapped["CreditTransactionView"] = relationship(
379
393
  back_populates="subtransactions", viewonly=True
380
394
  )
381
- tags: Mapped[List["TransactionTag"]] = relationship(
395
+ tags: Mapped[list["TransactionTag"]] = relationship(
382
396
  back_populates="credit_subtransactions",
383
397
  secondary=credit_tag_link_table,
384
398
  lazy="selectin",
@@ -4,7 +4,12 @@ INSERT INTO users
4
4
  VALUES
5
5
  (0, 'global', 'n/a');
6
6
 
7
- /* Set some default account types (user_id=0 indicates the "global" user) */
7
+ /* Set a default transaction tag for credit payments */
8
+ INSERT INTO transaction_tags
9
+ (user_id, parent_id, tag_name)
10
+ VALUES (0, NULL, 'Credit payments');
11
+
12
+ /* Set some default account types */
8
13
  INSERT INTO bank_account_types
9
14
  (user_id, type_name, type_abbreviation)
10
15
  VALUES
@@ -0,0 +1,100 @@
1
+ """
2
+ This script generates screenshots for the Monopyly application.
3
+
4
+ This is a script to automatically take screenshots of pages in the
5
+ Monopyly interface. It relies on entries in the development database
6
+ (and described in `tests/data.sql`); however, to take production-style
7
+ screenshots, Monopyly should be run in local mode:
8
+
9
+ monopyly launch local
10
+
11
+ To use the development database, replace the local database
12
+ (e.g., `instance/monopyly.sqlite`) with a copy of the development
13
+ database.
14
+ """
15
+
16
+ from pathlib import Path
17
+ from urllib.parse import urljoin
18
+
19
+ from selenium import webdriver
20
+ from selenium.webdriver.common.action_chains import ActionChains
21
+ from selenium.webdriver.common.by import By
22
+ from selenium.webdriver.firefox.options import Options
23
+
24
+ SCRIPTS_DIR = Path(__file__).parent
25
+ PACKAGE_DIR = SCRIPTS_DIR.parent
26
+ SCREENSHOTS_DIR = PACKAGE_DIR / "static/img/about"
27
+ MONOPYLY_URL = "http://localhost:5001"
28
+
29
+
30
+ def main():
31
+ # Set Firefox launch options and create the webdriver
32
+ options = Options()
33
+ options.add_argument("--headless")
34
+ options.add_argument("--width=1520")
35
+ options.add_argument("--height=1080")
36
+ driver = webdriver.Firefox(options=options)
37
+ driver.implicitly_wait(3)
38
+
39
+ # Take screenshot of the app homepage
40
+ photographer = Photographer(driver, SCREENSHOTS_DIR)
41
+ photographer.screenshot("homepage.png", "/")
42
+ # Login
43
+ driver.get(urljoin(MONOPYLY_URL, "/auth/login"))
44
+ username = driver.find_element(By.ID, "username")
45
+ password = driver.find_element(By.ID, "password")
46
+ username.send_keys("mr.monopyly")
47
+ password.send_keys("MONOPYLY")
48
+ submit_button = driver.find_element(By.CSS_SELECTOR, "input.button")
49
+ submit_button.click()
50
+ # Take a screenshot of the (logged in) app homepage
51
+ photographer.screenshot("homepage-user.png", "/")
52
+ # Take screenshots of the bank account functionality
53
+ photographer.screenshot("bank-accounts.png", "/banking/accounts")
54
+ photographer.screenshot(
55
+ "bank-account-summaries.png", "/banking/account_summaries/2"
56
+ )
57
+ photographer.screenshot("bank-account-details.png", "/banking/account/2")
58
+ # Take screenshots of the credit card functionality
59
+ photographer.screenshot("credit-account-details.png", "/credit/account/3")
60
+ photographer.get("/credit/transactions")
61
+ element = driver.find_element(By.CSS_SELECTOR, "#transaction-5 .more.button")
62
+ photographer.screenshot(
63
+ "credit-transactions.png",
64
+ actions=[photographer.define_click_action(element).pause(1)],
65
+ )
66
+ photographer.screenshot("credit-statement-details.png", "/credit/statement/7")
67
+ driver.quit()
68
+
69
+
70
+ class Photographer:
71
+ """An object to photographs/screenshots of the web application."""
72
+
73
+ _base_url = MONOPYLY_URL
74
+
75
+ def __init__(self, driver, output_dir):
76
+ self._driver = driver
77
+ self._output_dir = output_dir
78
+
79
+ def get(self, url):
80
+ """Get the given URL."""
81
+ self._driver.get(urljoin(self._base_url, url))
82
+
83
+ def screenshot(self, filename, url=None, actions=()):
84
+ """Take a screenshot of the given URL."""
85
+ if url:
86
+ self.get(url)
87
+ for action in actions:
88
+ action.perform()
89
+ output_filepath = self._output_dir / filename
90
+ self._driver.save_full_page_screenshot(str(output_filepath))
91
+
92
+ def define_click_action(self, element):
93
+ """Define an action that clicks the element."""
94
+ action = ActionChains(self._driver).move_to_element(element)
95
+ action.click()
96
+ return action
97
+
98
+
99
+ if __name__ == "__main__":
100
+ main()
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Skipped minification because the original files appears to be already minified.
3
+ * Original file: /npm/chartist@1.5.0/dist/index.umd.js
4
+ *
5
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
6
+ */
7
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chartist={})}(this,(function(e){"use strict";const t={svg:"http://www.w3.org/2000/svg",xmlns:"http://www.w3.org/2000/xmlns/",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",ct:"http://gionkunz.github.com/chartist-js/ct"},s={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"};function i(e,t){return"number"==typeof e?e+t:e}function n(e){if("string"==typeof e){const t=/^(\d+)\s*(.*)$/g.exec(e);return{value:t?+t[1]:0,unit:(null==t?void 0:t[2])||void 0}}return{value:Number(e)}}function r(e){return String.fromCharCode(97+e%26)}const a=2221e-19;function o(e){return Math.floor(Math.log(Math.abs(e))/Math.LN10)}function l(e,t,s){return t/s.range*e}function h(e,t){const s=Math.pow(10,t||8);return Math.round(e*s)/s}function c(e){if(1===e)return e;function t(e,s){return e%s==0?s:t(s,e%s)}function s(e){return e*e+1}let i,n=2,r=2;if(e%2==0)return 2;do{n=s(n)%e,r=s(s(r))%e,i=t(Math.abs(n-r),e)}while(1===i);return i}function u(e,t,s,i){const n=(i-90)*Math.PI/180;return{x:e+s*Math.cos(n),y:t+s*Math.sin(n)}}function d(e,t,s){let i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];const n={high:t.high,low:t.low,valueRange:0,oom:0,step:0,min:0,max:0,range:0,numberOfSteps:0,values:[]};n.valueRange=n.high-n.low,n.oom=o(n.valueRange),n.step=Math.pow(10,n.oom),n.min=Math.floor(n.low/n.step)*n.step,n.max=Math.ceil(n.high/n.step)*n.step,n.range=n.max-n.min,n.numberOfSteps=Math.round(n.range/n.step);const r=l(e,n.step,n),u=r<s,d=i?c(n.range):0;if(i&&l(e,1,n)>=s)n.step=1;else if(i&&d<n.step&&l(e,d,n)>=s)n.step=d;else{let t=0;for(;;){if(u&&l(e,n.step,n)<=s)n.step*=2;else{if(u||!(l(e,n.step/2,n)>=s))break;if(n.step/=2,i&&n.step%1!=0){n.step*=2;break}}if(t++>1e3)throw new Error("Exceeded maximum number of iterations while optimizing scale step!")}}function m(e,t){return e===(e+=t)&&(e*=1+(t>0?a:-a)),e}n.step=Math.max(n.step,a);let p=n.min,f=n.max;for(;p+n.step<=n.low;)p=m(p,n.step);for(;f-n.step>=n.high;)f=m(f,-n.step);n.min=p,n.max=f,n.range=n.max-n.min;const g=[];for(let e=n.min;e<=n.max;e=m(e,n.step)){const t=h(e);t!==g[g.length-1]&&g.push(t)}return n.values=g,n}function m(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};for(var t=arguments.length,s=new Array(t>1?t-1:0),i=1;i<t;i++)s[i-1]=arguments[i];for(let t=0;t<s.length;t++){const i=s[t],n=Object.getPrototypeOf(e);for(const t in i){if(null!==n&&t in n)continue;const s=i[t];e[t]="object"!=typeof s||null===s||s instanceof Array?s:m(e[t],s)}}return e}const p=e=>e;function f(e,t){return Array.from({length:e},t?(e,s)=>t(s):()=>{})}const g=(e,t)=>e+(t||0),x=(e,t)=>f(Math.max(...e.map((e=>e.length))),(s=>t(...e.map((e=>e[s])))));function v(e,t){return null!==e&&"object"==typeof e&&Reflect.has(e,t)}function y(e){return null!==e&&isFinite(e)}function w(e){return!e&&0!==e}function b(e){return y(e)?Number(e):void 0}function E(e){return!!Array.isArray(e)&&e.every(Array.isArray)}function A(e,t){let s=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=0;e[s?"reduceRight":"reduce"](((e,s,n)=>t(s,i++,n)),void 0)}function M(e,t){const s=Array.isArray(e)?e[t]:v(e,"data")?e.data[t]:null;return v(s,"meta")?s.meta:void 0}function N(e){return null==e||"number"==typeof e&&isNaN(e)}function C(e){return Array.isArray(e)&&e.every((e=>Array.isArray(e)||v(e,"data")))}function O(e){return"object"==typeof e&&null!==e&&(Reflect.has(e,"x")||Reflect.has(e,"y"))}function L(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"y";return O(e)&&v(e,t)?b(e[t]):b(e)}function S(e,t,s){const i={high:void 0===(t={...t,...s?"x"===s?t.axisX:t.axisY:{}}).high?-Number.MAX_VALUE:+t.high,low:void 0===t.low?Number.MAX_VALUE:+t.low},n=void 0===t.high,r=void 0===t.low;return(n||r)&&function e(t){if(!N(t))if(Array.isArray(t))for(let s=0;s<t.length;s++)e(t[s]);else{const e=Number(s&&v(t,s)?t[s]:t);n&&e>i.high&&(i.high=e),r&&e<i.low&&(i.low=e)}}(e),(t.referenceValue||0===t.referenceValue)&&(i.high=Math.max(t.referenceValue,i.high),i.low=Math.min(t.referenceValue,i.low)),i.high<=i.low&&(0===i.low?i.high=1:i.low<0?i.high=0:(i.high>0||(i.high=1),i.low=0)),i}function B(e){let t,s=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=arguments.length>2?arguments[2]:void 0,n=arguments.length>3?arguments[3]:void 0;const r={labels:(e.labels||[]).slice(),series:z(e.series,i,n)},a=r.labels.length;return E(r.series)?(t=Math.max(a,...r.series.map((e=>e.length))),r.series.forEach((e=>{e.push(...f(Math.max(0,t-e.length)))}))):t=r.series.length,r.labels.push(...f(Math.max(0,t-a),(()=>""))),s&&k(r),r}function k(e){var t;null===(t=e.labels)||void 0===t||t.reverse(),e.series.reverse();for(const t of e.series)v(t,"data")?t.data.reverse():Array.isArray(t)&&t.reverse()}function _(e,t){if(!N(e))return t?function(e,t){let s,i;if("object"!=typeof e){const n=b(e);"x"===t?s=n:i=n}else v(e,"x")&&(s=b(e.x)),v(e,"y")&&(i=b(e.y));if(void 0!==s||void 0!==i)return{x:s,y:i}}(e,t):b(e)}function j(e,t){return Array.isArray(e)?e.map((e=>v(e,"value")?_(e.value,t):_(e,t))):j(e.data,t)}function z(e,t,s){if(C(e))return e.map((e=>j(e,t)));const i=j(e,t);return s?i.map((e=>[e])):i}function I(e,t,s){const i={increasingX:!1,fillHoles:!1,...s},n=[];let r=!0;for(let s=0;s<e.length;s+=2)void 0===L(t[s/2].value)?i.fillHoles||(r=!0):(i.increasingX&&s>=2&&e[s]<=e[s-2]&&(r=!0),r&&(n.push({pathCoordinates:[],valueData:[]}),r=!1),n[n.length-1].pathCoordinates.push(e[s],e[s+1]),n[n.length-1].valueData.push(t[s/2]));return n}function P(e){let t="";return null==e?e:(t="number"==typeof e?""+e:"object"==typeof e?JSON.stringify({data:e}):String(e),Object.keys(s).reduce(((e,t)=>e.replaceAll(t,s[t])),t))}class X{call(e,t){return this.svgElements.forEach((s=>Reflect.apply(s[e],s,t))),this}attr(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("attr",t)}elem(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("elem",t)}root(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("root",t)}getNode(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("getNode",t)}foreignObject(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("foreignObject",t)}text(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("text",t)}empty(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("empty",t)}remove(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("remove",t)}addClass(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("addClass",t)}removeClass(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("removeClass",t)}removeAllClasses(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("removeAllClasses",t)}animate(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return this.call("animate",t)}constructor(e){this.svgElements=[];for(let t=0;t<e.length;t++)this.svgElements.push(new R(e[t]))}}const V={easeInSine:[.47,0,.745,.715],easeOutSine:[.39,.575,.565,1],easeInOutSine:[.445,.05,.55,.95],easeInQuad:[.55,.085,.68,.53],easeOutQuad:[.25,.46,.45,.94],easeInOutQuad:[.455,.03,.515,.955],easeInCubic:[.55,.055,.675,.19],easeOutCubic:[.215,.61,.355,1],easeInOutCubic:[.645,.045,.355,1],easeInQuart:[.895,.03,.685,.22],easeOutQuart:[.165,.84,.44,1],easeInOutQuart:[.77,0,.175,1],easeInQuint:[.755,.05,.855,.06],easeOutQuint:[.23,1,.32,1],easeInOutQuint:[.86,0,.07,1],easeInExpo:[.95,.05,.795,.035],easeOutExpo:[.19,1,.22,1],easeInOutExpo:[1,0,0,1],easeInCirc:[.6,.04,.98,.335],easeOutCirc:[.075,.82,.165,1],easeInOutCirc:[.785,.135,.15,.86],easeInBack:[.6,-.28,.735,.045],easeOutBack:[.175,.885,.32,1.275],easeInOutBack:[.68,-.55,.265,1.55]};function Y(e,t,s){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=arguments.length>4?arguments[4]:void 0;const{easing:o,...l}=s,h={};let c,u;o&&(c=Array.isArray(o)?o:V[o]),l.begin=i(l.begin,"ms"),l.dur=i(l.dur,"ms"),c&&(l.calcMode="spline",l.keySplines=c.join(" "),l.keyTimes="0;1"),r&&(l.fill="freeze",h[t]=l.from,e.attr(h),u=n(l.begin||0).value,l.begin="indefinite");const d=e.elem("animate",{attributeName:t,...l});r&&setTimeout((()=>{try{d._node.beginElement()}catch(s){h[t]=l.to,e.attr(h),d.remove()}}),u);const m=d.getNode();a&&m.addEventListener("beginEvent",(()=>a.emit("animationBegin",{element:e,animate:m,params:s}))),m.addEventListener("endEvent",(()=>{a&&a.emit("animationEnd",{element:e,animate:m,params:s}),r&&(h[t]=l.to,e.attr(h),d.remove())}))}class R{attr(e,s){return"string"==typeof e?s?this._node.getAttributeNS(s,e):this._node.getAttribute(e):(Object.keys(e).forEach((s=>{if(void 0!==e[s])if(-1!==s.indexOf(":")){const i=s.split(":");this._node.setAttributeNS(t[i[0]],s,String(e[s]))}else this._node.setAttribute(s,String(e[s]))})),this)}elem(e,t,s){return new R(e,t,s,this,arguments.length>3&&void 0!==arguments[3]&&arguments[3])}parent(){return this._node.parentNode instanceof SVGElement?new R(this._node.parentNode):null}root(){let e=this._node;for(;"svg"!==e.nodeName&&e.parentElement;)e=e.parentElement;return new R(e)}querySelector(e){const t=this._node.querySelector(e);return t?new R(t):null}querySelectorAll(e){const t=this._node.querySelectorAll(e);return new X(t)}getNode(){return this._node}foreignObject(e,s,i){let n,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if("string"==typeof e){const t=document.createElement("div");t.innerHTML=e,n=t.firstChild}else n=e;n instanceof Element&&n.setAttribute("xmlns",t.xmlns);const a=this.elem("foreignObject",s,i,r);return a._node.appendChild(n),a}text(e){return this._node.appendChild(document.createTextNode(e)),this}empty(){for(;this._node.firstChild;)this._node.removeChild(this._node.firstChild);return this}remove(){var e;return null===(e=this._node.parentNode)||void 0===e||e.removeChild(this._node),this.parent()}replace(e){var t;return null===(t=this._node.parentNode)||void 0===t||t.replaceChild(e._node,this._node),e}append(e){return arguments.length>1&&void 0!==arguments[1]&&arguments[1]&&this._node.firstChild?this._node.insertBefore(e._node,this._node.firstChild):this._node.appendChild(e._node),this}classes(){const e=this._node.getAttribute("class");return e?e.trim().split(/\s+/):[]}addClass(e){return this._node.setAttribute("class",this.classes().concat(e.trim().split(/\s+/)).filter((function(e,t,s){return s.indexOf(e)===t})).join(" ")),this}removeClass(e){const t=e.trim().split(/\s+/);return this._node.setAttribute("class",this.classes().filter((e=>-1===t.indexOf(e))).join(" ")),this}removeAllClasses(){return this._node.setAttribute("class",""),this}height(){return this._node.clientHeight}width(){return this._node.clientWidth}animate(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],s=arguments.length>2?arguments[2]:void 0;return Object.keys(e).forEach((i=>{const n=e[i];Array.isArray(n)?n.forEach((e=>Y(this,i,e,!1,s))):Y(this,i,n,t,s)})),this}constructor(e,s,i,n,r=!1){e instanceof Element?this._node=e:(this._node=document.createElementNS(t.svg,e),"svg"===e&&this.attr({"xmlns:ct":t.ct})),s&&this.attr(s),i&&this.addClass(i),n&&(r&&n._node.firstChild?n._node.insertBefore(this._node,n._node.firstChild):n._node.appendChild(this._node))}}function G(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"100%",i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"100%",n=arguments.length>3?arguments[3]:void 0,r=arguments.length>4?arguments[4]:void 0;if(!e)throw new Error("Container element is not found");Array.from(e.querySelectorAll("svg")).filter((e=>e.getAttributeNS(t.xmlns,"ct"))).forEach((t=>e.removeChild(t)));const a=new R("svg").attr({width:s,height:i}).attr({style:"width: ".concat(s,"; height: ").concat(i,";")});return n&&a.addClass(n),r&&a.attr({viewBox:"0 0 ".concat(r.width," ").concat(r.height)}),e.appendChild(a.getNode()),a}function D(e){return"number"==typeof e?{top:e,right:e,bottom:e,left:e}:void 0===e?{top:0,right:0,bottom:0,left:0}:{top:"number"==typeof e.top?e.top:0,right:"number"==typeof e.right?e.right:0,bottom:"number"==typeof e.bottom?e.bottom:0,left:"number"==typeof e.left?e.left:0}}function U(e,t){var s,i,r,a,o,l;const h=Boolean(t.axisX||t.axisY),c=(null===(s=t.axisY)||void 0===s?void 0:s.offset)||0,u=(null===(i=t.axisX)||void 0===i?void 0:i.offset)||0,d=null===(r=t.axisY)||void 0===r?void 0:r.position,m=null===(a=t.axisX)||void 0===a?void 0:a.position;let p=(null===(o=t.viewBox)||void 0===o?void 0:o.width)||e.width()||n(t.width).value||0,f=(null===(l=t.viewBox)||void 0===l?void 0:l.height)||e.height()||n(t.height).value||0;const g=D(t.chartPadding);p=Math.max(p,c+g.left+g.right),f=Math.max(f,u+g.top+g.bottom);const x={x1:0,x2:0,y1:0,y2:0,padding:g,width(){return this.x2-this.x1},height(){return this.y1-this.y2}};return h?("start"===m?(x.y2=g.top+u,x.y1=Math.max(f-g.bottom,x.y2+1)):(x.y2=g.top,x.y1=Math.max(f-g.bottom-u,x.y2+1)),"start"===d?(x.x1=g.left+c,x.x2=Math.max(p-g.right,x.x1+1)):(x.x1=g.left,x.x2=Math.max(p-g.right-c,x.x1+1))):(x.x1=g.left,x.x2=Math.max(p-g.right,x.x1+1),x.y2=g.top,x.y1=Math.max(f-g.bottom,x.y2+1)),x}function H(e,t,s,i,n,r,a,o){const l={["".concat(s.units.pos,"1")]:e,["".concat(s.units.pos,"2")]:e,["".concat(s.counterUnits.pos,"1")]:i,["".concat(s.counterUnits.pos,"2")]:i+n},h=r.elem("line",l,a.join(" "));o.emit("draw",{type:"grid",axis:s,index:t,group:r,element:h,...l})}function T(e,t,s,i){const n=e.elem("rect",{x:t.x1,y:t.y2,width:t.width(),height:t.height()},s,!0);i.emit("draw",{type:"gridBackground",group:e,element:n})}function Q(e,t,s,i,n,r,a,o,l,h){const c={[n.units.pos]:e+a[n.units.pos],[n.counterUnits.pos]:a[n.counterUnits.pos],[n.units.len]:t,[n.counterUnits.len]:Math.max(0,r-10)},u=Math.round(c[n.units.len]),d=Math.round(c[n.counterUnits.len]),m=document.createElement("span");m.className=l.join(" "),m.style[n.units.len]=u+"px",m.style[n.counterUnits.len]=d+"px",m.textContent=String(i);const p=o.foreignObject(m,{style:"overflow: visible;",...c});h.emit("draw",{type:"label",axis:n,index:s,group:o,element:p,text:i,...c})}function F(e,t,s){let i;const n=[];function r(n){const r=i;i=m({},e),t&&t.forEach((e=>{window.matchMedia(e[0]).matches&&(i=m({},i,e[1]))})),s&&n&&s.emit("optionsChanged",{previousOptions:r,currentOptions:i})}if(!window.matchMedia)throw new Error("window.matchMedia not found! Make sure you're using a polyfill.");return t&&t.forEach((e=>{const t=window.matchMedia(e[0]);t.addEventListener("change",r),n.push(t)})),r(),{removeMediaQueryListeners:function(){n.forEach((e=>e.removeEventListener("change",r)))},getCurrentOptions:()=>i}}R.Easing=V;const q={m:["x","y"],l:["x","y"],c:["x1","y1","x2","y2","x","y"],a:["rx","ry","xAr","lAf","sf","x","y"]},W={accuracy:3};function Z(e,t,s,i,n,r){const a={command:n?e.toLowerCase():e.toUpperCase(),...t,...r?{data:r}:{}};s.splice(i,0,a)}function $(e,t){e.forEach(((s,i)=>{q[s.command.toLowerCase()].forEach(((n,r)=>{t(s,n,i,r,e)}))}))}class J{static join(e){const t=new J(arguments.length>1&&void 0!==arguments[1]&&arguments[1],arguments.length>2?arguments[2]:void 0);for(let s=0;s<e.length;s++){const i=e[s];for(let e=0;e<i.pathElements.length;e++)t.pathElements.push(i.pathElements[e])}return t}position(e){return void 0!==e?(this.pos=Math.max(0,Math.min(this.pathElements.length,e)),this):this.pos}remove(e){return this.pathElements.splice(this.pos,e),this}move(e,t){let s=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=arguments.length>3?arguments[3]:void 0;return Z("M",{x:+e,y:+t},this.pathElements,this.pos++,s,i),this}line(e,t){let s=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=arguments.length>3?arguments[3]:void 0;return Z("L",{x:+e,y:+t},this.pathElements,this.pos++,s,i),this}curve(e,t,s,i,n,r){let a=arguments.length>6&&void 0!==arguments[6]&&arguments[6],o=arguments.length>7?arguments[7]:void 0;return Z("C",{x1:+e,y1:+t,x2:+s,y2:+i,x:+n,y:+r},this.pathElements,this.pos++,a,o),this}arc(e,t,s,i,n,r,a){let o=arguments.length>7&&void 0!==arguments[7]&&arguments[7],l=arguments.length>8?arguments[8]:void 0;return Z("A",{rx:e,ry:t,xAr:s,lAf:i,sf:n,x:r,y:a},this.pathElements,this.pos++,o,l),this}parse(e){const t=e.replace(/([A-Za-z])(-?[0-9])/g,"$1 $2").replace(/([0-9])([A-Za-z])/g,"$1 $2").split(/[\s,]+/).reduce(((e,t)=>(t.match(/[A-Za-z]/)&&e.push([]),e[e.length-1].push(t),e)),[]);"Z"===t[t.length-1][0].toUpperCase()&&t.pop();const s=t.map((e=>{const t=e.shift(),s=q[t.toLowerCase()];return{command:t,...s.reduce(((t,s,i)=>(t[s]=+e[i],t)),{})}}));return this.pathElements.splice(this.pos,0,...s),this.pos+=s.length,this}stringify(){const e=Math.pow(10,this.options.accuracy);return this.pathElements.reduce(((t,s)=>{const i=q[s.command.toLowerCase()].map((t=>{const i=s[t];return this.options.accuracy?Math.round(i*e)/e:i}));return t+s.command+i.join(",")}),"")+(this.close?"Z":"")}scale(e,t){return $(this.pathElements,((s,i)=>{s[i]*="x"===i[0]?e:t})),this}translate(e,t){return $(this.pathElements,((s,i)=>{s[i]+="x"===i[0]?e:t})),this}transform(e){return $(this.pathElements,((t,s,i,n,r)=>{const a=e(t,s,i,n,r);(a||0===a)&&(t[s]=a)})),this}clone(){const e=new J(arguments.length>0&&void 0!==arguments[0]&&arguments[0]||this.close);return e.pos=this.pos,e.pathElements=this.pathElements.slice().map((e=>({...e}))),e.options={...this.options},e}splitByCommand(e){const t=[new J];return this.pathElements.forEach((s=>{s.command===e.toUpperCase()&&0!==t[t.length-1].pathElements.length&&t.push(new J),t[t.length-1].pathElements.push(s)})),t}constructor(e=!1,t){this.close=e,this.pathElements=[],this.pos=0,this.options={...W,...t}}}function K(e){const t={fillHoles:!1,...e};return function(e,s){const i=new J;let n=!0;for(let r=0;r<e.length;r+=2){const a=e[r],o=e[r+1],l=s[r/2];void 0!==L(l.value)?(n?i.move(a,o,!1,l):i.line(a,o,!1,l),n=!1):t.fillHoles||(n=!0)}return i}}function ee(e){const t={fillHoles:!1,...e};return function e(s,i){const n=I(s,i,{fillHoles:t.fillHoles,increasingX:!0});if(n.length){if(n.length>1)return J.join(n.map((t=>e(t.pathCoordinates,t.valueData))));{if(s=n[0].pathCoordinates,i=n[0].valueData,s.length<=4)return K()(s,i);const e=[],t=[],r=s.length/2,a=[],o=[],l=[],h=[];for(let i=0;i<r;i++)e[i]=s[2*i],t[i]=s[2*i+1];for(let s=0;s<r-1;s++)l[s]=t[s+1]-t[s],h[s]=e[s+1]-e[s],o[s]=l[s]/h[s];a[0]=o[0],a[r-1]=o[r-2];for(let e=1;e<r-1;e++)0===o[e]||0===o[e-1]||o[e-1]>0!=o[e]>0?a[e]=0:(a[e]=3*(h[e-1]+h[e])/((2*h[e]+h[e-1])/o[e-1]+(h[e]+2*h[e-1])/o[e]),isFinite(a[e])||(a[e]=0));const c=(new J).move(e[0],t[0],!1,i[0]);for(let s=0;s<r-1;s++)c.curve(e[s]+h[s]/3,t[s]+a[s]*h[s]/3,e[s+1]-h[s]/3,t[s+1]-a[s+1]*h[s]/3,e[s+1],t[s+1],!1,i[s+1]);return c}}return K()([],[])}}var te=Object.freeze({__proto__:null,none:K,simple:function(e){const t={divisor:2,fillHoles:!1,...e},s=1/Math.max(1,t.divisor);return function(e,i){const n=new J;let r,a=0,o=0;for(let l=0;l<e.length;l+=2){const h=e[l],c=e[l+1],u=(h-a)*s,d=i[l/2];void 0!==d.value?(void 0===r?n.move(h,c,!1,d):n.curve(a+u,o,h-u,c,h,c,!1,d),a=h,o=c,r=d):t.fillHoles||(a=o=0,r=void 0)}return n}},step:function(e){const t={postpone:!0,fillHoles:!1,...e};return function(e,s){const i=new J;let n,r=0,a=0;for(let o=0;o<e.length;o+=2){const l=e[o],h=e[o+1],c=s[o/2];void 0!==c.value?(void 0===n?i.move(l,h,!1,c):(t.postpone?i.line(l,a,!1,n):i.line(r,h,!1,c),i.line(l,h,!1,c)),r=l,a=h,n=c):t.fillHoles||(r=a=0,n=void 0)}return i}},cardinal:function(e){const t={tension:1,fillHoles:!1,...e},s=Math.min(1,Math.max(0,t.tension)),i=1-s;return function e(n,r){const a=I(n,r,{fillHoles:t.fillHoles});if(a.length){if(a.length>1)return J.join(a.map((t=>e(t.pathCoordinates,t.valueData))));{if(n=a[0].pathCoordinates,r=a[0].valueData,n.length<=4)return K()(n,r);const e=(new J).move(n[0],n[1],!1,r[0]),t=!1;for(let a=0,o=n.length;o-2*Number(!t)>a;a+=2){const t=[{x:+n[a-2],y:+n[a-1]},{x:+n[a],y:+n[a+1]},{x:+n[a+2],y:+n[a+3]},{x:+n[a+4],y:+n[a+5]}];o-4===a?t[3]=t[2]:a||(t[0]={x:+n[a],y:+n[a+1]}),e.curve(s*(-t[0].x+6*t[1].x+t[2].x)/6+i*t[2].x,s*(-t[0].y+6*t[1].y+t[2].y)/6+i*t[2].y,s*(t[1].x+6*t[2].x-t[3].x)/6+i*t[2].x,s*(t[1].y+6*t[2].y-t[3].y)/6+i*t[2].y,t[2].x,t[2].y,!1,r[(a+2)/2])}return e}}return K()([],[])}},monotoneCubic:ee});class se{on(e,t){const{allListeners:s,listeners:i}=this;"*"===e?s.add(t):(i.has(e)||i.set(e,new Set),i.get(e).add(t))}off(e,t){const{allListeners:s,listeners:i}=this;if("*"===e)t?s.delete(t):s.clear();else if(i.has(e)){const s=i.get(e);t?s.delete(t):s.clear(),s.size||i.delete(e)}}emit(e,t){const{allListeners:s,listeners:i}=this;i.has(e)&&i.get(e).forEach((e=>e(t))),s.forEach((s=>s(e,t)))}constructor(){this.listeners=new Map,this.allListeners=new Set}}const ie=new WeakMap;class ne{update(e,t){let s=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var i;(e&&(this.data=e||{},this.data.labels=this.data.labels||[],this.data.series=this.data.series||[],this.eventEmitter.emit("data",{type:"update",data:this.data})),t)&&(this.options=m({},s?this.options:this.defaultOptions,t),this.initializeTimeoutId||(null===(i=this.optionsProvider)||void 0===i||i.removeMediaQueryListeners(),this.optionsProvider=F(this.options,this.responsiveOptions,this.eventEmitter)));return!this.initializeTimeoutId&&this.optionsProvider&&this.createChart(this.optionsProvider.getCurrentOptions()),this}detach(){var e;this.initializeTimeoutId?window.clearTimeout(this.initializeTimeoutId):(window.removeEventListener("resize",this.resizeListener),null===(e=this.optionsProvider)||void 0===e||e.removeMediaQueryListeners());return ie.delete(this.container),this}on(e,t){return this.eventEmitter.on(e,t),this}off(e,t){return this.eventEmitter.off(e,t),this}initialize(){window.addEventListener("resize",this.resizeListener),this.optionsProvider=F(this.options,this.responsiveOptions,this.eventEmitter),this.eventEmitter.on("optionsChanged",(()=>this.update())),this.options.plugins&&this.options.plugins.forEach((e=>{Array.isArray(e)?e[0](this,e[1]):e(this)})),this.eventEmitter.emit("data",{type:"initial",data:this.data}),this.createChart(this.optionsProvider.getCurrentOptions()),this.initializeTimeoutId=null}constructor(e,t,s,i,n){this.data=t,this.defaultOptions=s,this.options=i,this.responsiveOptions=n,this.eventEmitter=new se,this.resizeListener=()=>this.update(),this.initializeTimeoutId=setTimeout((()=>this.initialize()),0);const r="string"==typeof e?document.querySelector(e):e;if(!r)throw new Error("Target element ".concat("string"==typeof e?'"'.concat(e,'"'):""," is not found"));this.container=r;const a=ie.get(r);a&&a.detach(),ie.set(r,this)}}const re={x:{pos:"x",len:"width",dir:"horizontal",rectStart:"x1",rectEnd:"x2",rectOffset:"y2"},y:{pos:"y",len:"height",dir:"vertical",rectStart:"y2",rectEnd:"y1",rectOffset:"x1"}};class ae{createGridAndLabels(e,t,s,i){const n="x"===this.units.pos?s.axisX:s.axisY,r=this.ticks.map(((e,t)=>this.projectValue(e,t))),a=this.ticks.map(n.labelInterpolationFnc);r.forEach(((o,l)=>{const h=a[l],c={x:0,y:0};let u;u=r[l+1]?r[l+1]-o:Math.max(this.axisLength-o,this.axisLength/this.ticks.length),""!==h&&w(h)||("x"===this.units.pos?(o=this.chartRect.x1+o,c.x=s.axisX.labelOffset.x,"start"===s.axisX.position?c.y=this.chartRect.padding.top+s.axisX.labelOffset.y+5:c.y=this.chartRect.y1+s.axisX.labelOffset.y+5):(o=this.chartRect.y1-o,c.y=s.axisY.labelOffset.y-u,"start"===s.axisY.position?c.x=this.chartRect.padding.left+s.axisY.labelOffset.x:c.x=this.chartRect.x2+s.axisY.labelOffset.x+10),n.showGrid&&H(o,l,this,this.gridOffset,this.chartRect[this.counterUnits.len](),e,[s.classNames.grid,s.classNames[this.units.dir]],i),n.showLabel&&Q(o,u,l,h,this,n.offset,c,t,[s.classNames.label,s.classNames[this.units.dir],"start"===n.position?s.classNames[n.position]:s.classNames.end],i))}))}constructor(e,t,s){this.units=e,this.chartRect=t,this.ticks=s,this.counterUnits=e===re.x?re.y:re.x,this.axisLength=t[this.units.rectEnd]-t[this.units.rectStart],this.gridOffset=t[this.units.rectOffset]}}class oe extends ae{projectValue(e){const t=Number(L(e,this.units.pos));return this.axisLength*(t-this.bounds.min)/this.bounds.range}constructor(e,t,s,i){const n=i.highLow||S(t,i,e.pos),r=d(s[e.rectEnd]-s[e.rectStart],n,i.scaleMinSpace||20,i.onlyInteger),a={min:r.min,max:r.max};super(e,s,r.values),this.bounds=r,this.range=a}}class le extends ae{projectValue(e,t){return this.stepLength*t}constructor(e,t,s,i){const n=i.ticks||[];super(e,s,n);const r=Math.max(1,n.length-(i.stretch?1:0));this.stepLength=this.axisLength/r,this.stretch=Boolean(i.stretch)}}function he(e,t,s){var i;if(v(e,"name")&&e.name&&(null===(i=t.series)||void 0===i?void 0:i[e.name])){const i=(null==t?void 0:t.series[e.name])[s];return void 0===i?t[s]:i}return t[s]}const ce={axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:p,type:void 0},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:p,type:void 0,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,showLine:!0,showPoint:!0,showArea:!1,areaBase:0,lineSmooth:!0,showGridBackground:!1,low:void 0,high:void 0,chartPadding:{top:15,right:15,bottom:5,left:10},fullWidth:!1,reverseData:!1,classNames:{chart:"ct-chart-line",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",line:"ct-line",point:"ct-point",area:"ct-area",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}};const ue={axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:p,scaleMinSpace:30,onlyInteger:!1},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:p,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,high:void 0,low:void 0,referenceValue:0,chartPadding:{top:15,right:15,bottom:5,left:10},seriesBarDistance:15,stackBars:!1,stackMode:"accumulate",horizontalBars:!1,distributeSeries:!1,reverseData:!1,showGridBackground:!1,classNames:{chart:"ct-chart-bar",horizontalBars:"ct-horizontal-bars",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",bar:"ct-bar",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}};const de={width:void 0,height:void 0,chartPadding:5,classNames:{chartPie:"ct-chart-pie",chartDonut:"ct-chart-donut",series:"ct-series",slicePie:"ct-slice-pie",sliceDonut:"ct-slice-donut",label:"ct-label"},startAngle:0,total:void 0,donut:!1,donutWidth:60,showLabel:!0,labelOffset:0,labelPosition:"inside",labelInterpolationFnc:p,labelDirection:"neutral",ignoreEmptyValues:!1,preventOverlappingLabelOffset:0};function me(e,t,s){const i=t.x>e.x;return i&&"explode"===s||!i&&"implode"===s?"start":i&&"implode"===s||!i&&"explode"===s?"end":"middle"}e.AutoScaleAxis=oe,e.Axis=ae,e.BarChart=class extends ne{createChart(e){const{data:t}=this,s=B(t,e.reverseData,e.horizontalBars?"x":"y",!0),i=G(this.container,e.width,e.height,e.classNames.chart+(e.horizontalBars?" "+e.classNames.horizontalBars:""),e.viewBox),n=e.stackBars&&!0!==e.stackMode&&s.series.length?S([(a=s.series,x(a,(function(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];return Array.from(t).reduce(((e,t)=>({x:e.x+(v(t,"x")?t.x:0),y:e.y+(v(t,"y")?t.y:0)})),{x:0,y:0})})))],e,e.horizontalBars?"x":"y"):S(s.series,e,e.horizontalBars?"x":"y");var a;this.svg=i;const o=i.elem("g").addClass(e.classNames.gridGroup),l=i.elem("g"),h=i.elem("g").addClass(e.classNames.labelGroup);"number"==typeof e.high&&(n.high=e.high),"number"==typeof e.low&&(n.low=e.low);const c=U(i,e);let u;const d=e.distributeSeries&&e.stackBars?s.labels.slice(0,1):s.labels;let m,p,f;e.horizontalBars?(u=p=void 0===e.axisX.type?new oe(re.x,s.series,c,{...e.axisX,highLow:n,referenceValue:0}):new e.axisX.type(re.x,s.series,c,{...e.axisX,highLow:n,referenceValue:0}),m=f=void 0===e.axisY.type?new le(re.y,s.series,c,{ticks:d}):new e.axisY.type(re.y,s.series,c,e.axisY)):(m=p=void 0===e.axisX.type?new le(re.x,s.series,c,{ticks:d}):new e.axisX.type(re.x,s.series,c,e.axisX),u=f=void 0===e.axisY.type?new oe(re.y,s.series,c,{...e.axisY,highLow:n,referenceValue:0}):new e.axisY.type(re.y,s.series,c,{...e.axisY,highLow:n,referenceValue:0}));const g=e.horizontalBars?c.x1+u.projectValue(0):c.y1-u.projectValue(0),w="accumulate"===e.stackMode,b="accumulate-relative"===e.stackMode,E=[],N=[];let C=E;m.createGridAndLabels(o,h,e,this.eventEmitter),u.createGridAndLabels(o,h,e,this.eventEmitter),e.showGridBackground&&T(o,c,e.classNames.gridBackground,this.eventEmitter),A(t.series,((i,n)=>{const a=n-(t.series.length-1)/2;let o;o=e.distributeSeries&&!e.stackBars?m.axisLength/s.series.length/2:e.distributeSeries&&e.stackBars?m.axisLength/2:m.axisLength/s.series[n].length/2;const h=l.elem("g"),d=v(i,"name")&&i.name,x=v(i,"className")&&i.className,A=v(i,"meta")?i.meta:void 0;d&&h.attr({"ct:series-name":d}),A&&h.attr({"ct:meta":P(A)}),h.addClass([e.classNames.series,x||"".concat(e.classNames.series,"-").concat(r(n))].join(" ")),s.series[n].forEach(((t,r)=>{const l=v(t,"x")&&t.x,d=v(t,"y")&&t.y;let x,A;x=e.distributeSeries&&!e.stackBars?n:e.distributeSeries&&e.stackBars?0:r,A=e.horizontalBars?{x:c.x1+u.projectValue(l||0,r,s.series[n]),y:c.y1-m.projectValue(d||0,x,s.series[n])}:{x:c.x1+m.projectValue(l||0,x,s.series[n]),y:c.y1-u.projectValue(d||0,r,s.series[n])},m instanceof le&&(m.stretch||(A[m.units.pos]+=o*(e.horizontalBars?-1:1)),A[m.units.pos]+=e.stackBars||e.distributeSeries?0:a*e.seriesBarDistance*(e.horizontalBars?-1:1)),b&&(C=d>=0||l>=0?E:N);const O=C[r]||g;if(C[r]=O-(g-A[m.counterUnits.pos]),void 0===t)return;const L={["".concat(m.units.pos,"1")]:A[m.units.pos],["".concat(m.units.pos,"2")]:A[m.units.pos]};e.stackBars&&(w||b||!e.stackMode)?(L["".concat(m.counterUnits.pos,"1")]=O,L["".concat(m.counterUnits.pos,"2")]=C[r]):(L["".concat(m.counterUnits.pos,"1")]=g,L["".concat(m.counterUnits.pos,"2")]=A[m.counterUnits.pos]),L.x1=Math.min(Math.max(L.x1,c.x1),c.x2),L.x2=Math.min(Math.max(L.x2,c.x1),c.x2),L.y1=Math.min(Math.max(L.y1,c.y2),c.y1),L.y2=Math.min(Math.max(L.y2,c.y2),c.y1);const S=M(i,r),B=h.elem("line",L,e.classNames.bar).attr({"ct:value":[l,d].filter(y).join(","),"ct:meta":P(S)});this.eventEmitter.emit("draw",{type:"bar",value:t,index:r,meta:S,series:i,seriesIndex:n,axisX:p,axisY:f,chartRect:c,group:h,element:B,...L})}))}),e.reverseData),this.eventEmitter.emit("created",{chartRect:c,axisX:p,axisY:f,svg:i,options:e})}constructor(e,t,s,i){super(e,t,ue,m({},ue,s),i),this.data=t}},e.BaseChart=ne,e.EPSILON=a,e.EventEmitter=se,e.FixedScaleAxis=class extends ae{projectValue(e){const t=Number(L(e,this.units.pos));return this.axisLength*(t-this.range.min)/(this.range.max-this.range.min)}constructor(e,t,s,i){const n=i.highLow||S(t,i,e.pos),r=i.divisor||1,a=(i.ticks||f(r,(e=>n.low+(n.high-n.low)/r*e))).sort(((e,t)=>Number(e)-Number(t))),o={min:n.low,max:n.high};super(e,s,a),this.range=o}},e.Interpolation=te,e.LineChart=class extends ne{createChart(e){const{data:t}=this,s=B(t,e.reverseData,!0),i=G(this.container,e.width,e.height,e.classNames.chart,e.viewBox);this.svg=i;const n=i.elem("g").addClass(e.classNames.gridGroup),a=i.elem("g"),o=i.elem("g").addClass(e.classNames.labelGroup),l=U(i,e);let h,c;h=void 0===e.axisX.type?new le(re.x,s.series,l,{...e.axisX,ticks:s.labels,stretch:e.fullWidth}):new e.axisX.type(re.x,s.series,l,e.axisX),c=void 0===e.axisY.type?new oe(re.y,s.series,l,{...e.axisY,high:y(e.high)?e.high:e.axisY.high,low:y(e.low)?e.low:e.axisY.low}):new e.axisY.type(re.y,s.series,l,e.axisY),h.createGridAndLabels(n,o,e,this.eventEmitter),c.createGridAndLabels(n,o,e,this.eventEmitter),e.showGridBackground&&T(n,l,e.classNames.gridBackground,this.eventEmitter),A(t.series,((t,i)=>{const n=a.elem("g"),o=v(t,"name")&&t.name,u=v(t,"className")&&t.className,d=v(t,"meta")?t.meta:void 0;o&&n.attr({"ct:series-name":o}),d&&n.attr({"ct:meta":P(d)}),n.addClass([e.classNames.series,u||"".concat(e.classNames.series,"-").concat(r(i))].join(" "));const m=[],p=[];s.series[i].forEach(((e,n)=>{const r={x:l.x1+h.projectValue(e,n,s.series[i]),y:l.y1-c.projectValue(e,n,s.series[i])};m.push(r.x,r.y),p.push({value:e,valueIndex:n,meta:M(t,n)})}));const f={lineSmooth:he(t,e,"lineSmooth"),showPoint:he(t,e,"showPoint"),showLine:he(t,e,"showLine"),showArea:he(t,e,"showArea"),areaBase:he(t,e,"areaBase")};let g;g="function"==typeof f.lineSmooth?f.lineSmooth:f.lineSmooth?ee():K();const x=g(m,p);if(f.showPoint&&x.pathElements.forEach((s=>{const{data:r}=s,a=n.elem("line",{x1:s.x,y1:s.y,x2:s.x+.01,y2:s.y},e.classNames.point);if(r){let e,t;v(r.value,"x")&&(e=r.value.x),v(r.value,"y")&&(t=r.value.y),a.attr({"ct:value":[e,t].filter(y).join(","),"ct:meta":P(r.meta)})}this.eventEmitter.emit("draw",{type:"point",value:null==r?void 0:r.value,index:(null==r?void 0:r.valueIndex)||0,meta:null==r?void 0:r.meta,series:t,seriesIndex:i,axisX:h,axisY:c,group:n,element:a,x:s.x,y:s.y,chartRect:l})})),f.showLine){const r=n.elem("path",{d:x.stringify()},e.classNames.line,!0);this.eventEmitter.emit("draw",{type:"line",values:s.series[i],path:x.clone(),chartRect:l,index:i,series:t,seriesIndex:i,meta:d,axisX:h,axisY:c,group:n,element:r})}if(f.showArea&&c.range){const r=Math.max(Math.min(f.areaBase,c.range.max),c.range.min),a=l.y1-c.projectValue(r);x.splitByCommand("M").filter((e=>e.pathElements.length>1)).map((e=>{const t=e.pathElements[0],s=e.pathElements[e.pathElements.length-1];return e.clone(!0).position(0).remove(1).move(t.x,a).line(t.x,t.y).position(e.pathElements.length+1).line(s.x,a)})).forEach((r=>{const a=n.elem("path",{d:r.stringify()},e.classNames.area,!0);this.eventEmitter.emit("draw",{type:"area",values:s.series[i],path:r.clone(),series:t,seriesIndex:i,axisX:h,axisY:c,chartRect:l,index:i,group:n,element:a,meta:d})}))}}),e.reverseData),this.eventEmitter.emit("created",{chartRect:l,axisX:h,axisY:c,svg:i,options:e})}constructor(e,t,s,i){super(e,t,ce,m({},ce,s),i),this.data=t}},e.PieChart=class extends ne{moveLabel(e,t,s,i){e.y>t.y-s&&e.y<t.y+s&&e.x>t.x-i*s&&e.x<t.x+i*s&&(e.y-=s,e.x-=s,this.moveLabel(e,t,s,i))}createChart(e){const{data:t}=this,s=B(t),i=[];let a;const o=[];let l,h=e.startAngle;const c=G(this.container,e.width,e.height,e.donut?e.classNames.chartDonut:e.classNames.chartPie,e.viewBox);this.svg=c;const d=U(c,e);let m=Math.min(d.width()/2,d.height()/2);const p=e.total||s.series.reduce(g,0),f=n(e.donutWidth);"%"===f.unit&&(f.value*=m/100),m-=e.donut?f.value/2:0,l="outside"===e.labelPosition||e.donut?m:"center"===e.labelPosition?0:m/2,e.labelOffset&&(l+=e.labelOffset);const x={x:d.x1+d.width()/2,y:d.y2+d.height()/2},y=1===t.series.filter((e=>v(e,"value")?0!==e.value:0!==e)).length;t.series.forEach(((e,t)=>i[t]=c.elem("g"))),e.showLabel&&(a=c.elem("g")),t.series.forEach(((n,c)=>{var g,b;if(0===s.series[c]&&e.ignoreEmptyValues)return;const E=v(n,"name")&&n.name,A=v(n,"className")&&n.className,M=v(n,"meta")?n.meta:void 0;E&&i[c].attr({"ct:series-name":E}),i[c].addClass([null===(g=e.classNames)||void 0===g?void 0:g.series,A||"".concat(null===(b=e.classNames)||void 0===b?void 0:b.series,"-").concat(r(c))].join(" "));let N=p>0?h+s.series[c]/p*360:0;const C=Math.max(0,h-(0===c||y?0:.2));N-C>=359.99&&(N=C+359.99);const O=u(x.x,x.y,m,C),L=u(x.x,x.y,m,N),S=new J(!e.donut).move(L.x,L.y).arc(m,m,0,Number(N-h>180),0,O.x,O.y);e.donut||S.line(x.x,x.y);const B=i[c].elem("path",{d:S.stringify()},e.donut?e.classNames.sliceDonut:e.classNames.slicePie);if(B.attr({"ct:value":s.series[c],"ct:meta":P(M)}),e.donut&&B.attr({style:"stroke-width: "+f.value+"px"}),this.eventEmitter.emit("draw",{type:"slice",value:s.series[c],totalDataSum:p,index:c,meta:M,series:n,group:i[c],element:B,path:S.clone(),center:x,radius:m,startAngle:h,endAngle:N,chartRect:d}),e.showLabel){let i,r;i=1===t.series.length?{x:x.x,y:x.y}:u(x.x,x.y,l,h+(N-h)/2),r=s.labels&&!w(s.labels[c])?s.labels[c]:s.series[c];const m=e.labelInterpolationFnc(r,c);if(m||0===m){if(e.preventOverlappingLabelOffset){const t=e.preventOverlappingLabelOffset,n=String(s.labels[c]).length;o.forEach((e=>{this.moveLabel(i,e,t,n)})),o.push(i)}const t=a.elem("text",{dx:i.x,dy:i.y,"text-anchor":me(x,i,e.labelDirection)},e.classNames.label).text(String(m));this.eventEmitter.emit("draw",{type:"label",index:c,group:a,element:t,text:""+m,chartRect:d,series:n,meta:M,...i})}}h=N})),this.eventEmitter.emit("created",{chartRect:d,svg:c,options:e})}constructor(e,t,s,i){super(e,t,de,m({},de,s),i),this.data=t}},e.StepAxis=le,e.Svg=R,e.SvgList=X,e.SvgPath=J,e.alphaNumerate=r,e.axisUnits=re,e.createChartRect=U,e.createGrid=H,e.createGridBackground=T,e.createLabel=Q,e.createSvg=G,e.deserialize=function(e){if("string"!=typeof e)return e;if("NaN"===e)return NaN;let t=e=Object.keys(s).reduce(((e,t)=>e.replaceAll(s[t],t)),e);if("string"==typeof e)try{t=JSON.parse(e),t=void 0!==t.data?t.data:t}catch(e){}return t},e.determineAnchorPosition=me,e.each=A,e.easings=V,e.ensureUnit=i,e.escapingMap=s,e.extend=m,e.getBounds=d,e.getHighLow=S,e.getMetaData=M,e.getMultiValue=L,e.getNumberOrUndefined=b,e.getSeriesOption=he,e.isArrayOfArrays=E,e.isArrayOfSeries=C,e.isDataHoleValue=N,e.isFalseyButZero=w,e.isMultiValue=O,e.isNumeric=y,e.namespaces=t,e.noop=p,e.normalizeData=B,e.normalizePadding=D,e.optionsProvider=F,e.orderOfMagnitude=o,e.polarToCartesian=u,e.precision=8,e.projectLength=l,e.quantity=n,e.rho=c,e.roundWithPrecision=h,e.safeHasProperty=v,e.serialMap=x,e.serialize=P,e.splitIntoSegments=I,e.sum=g,e.times=f,Object.defineProperty(e,"__esModule",{value:!0})}));
8
+ //# sourceMappingURL=index.umd.js.map
@@ -1340,7 +1340,34 @@ form .autocomplete-box .item.active {
1340
1340
  /*
1341
1341
  * Style tables for presenting transaction details
1342
1342
  */
1343
+ .transactions-container {
1344
+ display: flex;
1345
+ flex-direction: column;
1346
+ align-items: center;
1347
+ width: 100%;
1348
+ }
1349
+
1350
+ .transactions-container #more-transactions.button {
1351
+ display: flex;
1352
+ justify-content: space-between;
1353
+ align-items: center;
1354
+ margin: 20px 0;
1355
+ padding: 8px 12px;
1356
+ border: 1px solid var(--border-gray);
1357
+ border-radius: 10px;
1358
+ background-color: #fafafa;
1359
+ color: #666666;
1360
+ font-size: 9pt;
1361
+ }
1362
+
1363
+ .transactions-container #more-transactions.button img {
1364
+ height: 12px;
1365
+ width: 12px;
1366
+ margin-right: 10px;
1367
+ }
1368
+
1343
1369
  .transactions-table {
1370
+ width: 100%;
1344
1371
  font-size: 9pt;
1345
1372
  }
1346
1373
 
@@ -2386,6 +2413,8 @@ form .autocomplete-box .item.active {
2386
2413
  #homepage-panels {
2387
2414
  display: flex;
2388
2415
  justify-content: space-between;
2416
+ align-items: stretch;
2417
+ gap: 20px;
2389
2418
  width: 100%;
2390
2419
  }
2391
2420
  @media screen and (max-width: 600px) {
@@ -2395,29 +2424,21 @@ form .autocomplete-box .item.active {
2395
2424
  }
2396
2425
  }
2397
2426
 
2427
+ #homepage-panels .panel-column {
2428
+ display: flex;
2429
+ flex-direction: column;
2430
+ flex-grow: 1;
2431
+ }
2432
+
2398
2433
  #homepage-panels .panel {
2399
2434
  display: flex;
2400
2435
  flex-direction: column;
2401
2436
  flex-grow: 1;
2402
- margin: 20px;
2403
2437
  padding: 50px;
2404
2438
  border: 1px solid #f5f5f5;
2405
2439
  box-shadow: 2px 2px 5px var(--silver-dollar);
2406
2440
  background-color: #fafafa;
2407
2441
  }
2408
- @media screen and (max-width: 600px) {
2409
- /* Mobile layout */
2410
- #homepage-panels .panel {
2411
- margin: 20px 0;
2412
- }
2413
- }
2414
-
2415
- #homepage-panels div:first-of-type {
2416
- margin-left: 0;
2417
- }
2418
- #homepage-panels div:last-of-type {
2419
- margin-right: 0;
2420
- }
2421
2442
 
2422
2443
  #homepage-panels .panel h2 {
2423
2444
  margin: 0 0 15px;
@@ -3460,6 +3481,10 @@ form#pay #make-payment[type="submit"] #prompt {
3460
3481
  min-height: 400px;
3461
3482
  }
3462
3483
 
3484
+ #category-chart.ct-chart svg {
3485
+ overflow: visible;
3486
+ }
3487
+
3463
3488
  #category-chart text.ct-label {
3464
3489
  transition: fill 0.1s ease;
3465
3490
  }
Binary file
Binary file
Binary file