You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11377 lines
396 KiB

3 years ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. /*!
  2. * Chart.js v4.2.1
  3. * https://www.chartjs.org
  4. * (c) 2023 Chart.js Contributors
  5. * Released under the MIT License
  6. */
  7. 'use strict';
  8. var helpers_segment = require('./chunks/helpers.segment.cjs');
  9. require('@kurkle/color');
  10. class Animator {
  11. constructor(){
  12. this._request = null;
  13. this._charts = new Map();
  14. this._running = false;
  15. this._lastDate = undefined;
  16. }
  17. _notify(chart, anims, date, type) {
  18. const callbacks = anims.listeners[type];
  19. const numSteps = anims.duration;
  20. callbacks.forEach((fn)=>fn({
  21. chart,
  22. initial: anims.initial,
  23. numSteps,
  24. currentStep: Math.min(date - anims.start, numSteps)
  25. }));
  26. }
  27. _refresh() {
  28. if (this._request) {
  29. return;
  30. }
  31. this._running = true;
  32. this._request = helpers_segment.requestAnimFrame.call(window, ()=>{
  33. this._update();
  34. this._request = null;
  35. if (this._running) {
  36. this._refresh();
  37. }
  38. });
  39. }
  40. _update(date = Date.now()) {
  41. let remaining = 0;
  42. this._charts.forEach((anims, chart)=>{
  43. if (!anims.running || !anims.items.length) {
  44. return;
  45. }
  46. const items = anims.items;
  47. let i = items.length - 1;
  48. let draw = false;
  49. let item;
  50. for(; i >= 0; --i){
  51. item = items[i];
  52. if (item._active) {
  53. if (item._total > anims.duration) {
  54. anims.duration = item._total;
  55. }
  56. item.tick(date);
  57. draw = true;
  58. } else {
  59. items[i] = items[items.length - 1];
  60. items.pop();
  61. }
  62. }
  63. if (draw) {
  64. chart.draw();
  65. this._notify(chart, anims, date, 'progress');
  66. }
  67. if (!items.length) {
  68. anims.running = false;
  69. this._notify(chart, anims, date, 'complete');
  70. anims.initial = false;
  71. }
  72. remaining += items.length;
  73. });
  74. this._lastDate = date;
  75. if (remaining === 0) {
  76. this._running = false;
  77. }
  78. }
  79. _getAnims(chart) {
  80. const charts = this._charts;
  81. let anims = charts.get(chart);
  82. if (!anims) {
  83. anims = {
  84. running: false,
  85. initial: true,
  86. items: [],
  87. listeners: {
  88. complete: [],
  89. progress: []
  90. }
  91. };
  92. charts.set(chart, anims);
  93. }
  94. return anims;
  95. }
  96. listen(chart, event, cb) {
  97. this._getAnims(chart).listeners[event].push(cb);
  98. }
  99. add(chart, items) {
  100. if (!items || !items.length) {
  101. return;
  102. }
  103. this._getAnims(chart).items.push(...items);
  104. }
  105. has(chart) {
  106. return this._getAnims(chart).items.length > 0;
  107. }
  108. start(chart) {
  109. const anims = this._charts.get(chart);
  110. if (!anims) {
  111. return;
  112. }
  113. anims.running = true;
  114. anims.start = Date.now();
  115. anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
  116. this._refresh();
  117. }
  118. running(chart) {
  119. if (!this._running) {
  120. return false;
  121. }
  122. const anims = this._charts.get(chart);
  123. if (!anims || !anims.running || !anims.items.length) {
  124. return false;
  125. }
  126. return true;
  127. }
  128. stop(chart) {
  129. const anims = this._charts.get(chart);
  130. if (!anims || !anims.items.length) {
  131. return;
  132. }
  133. const items = anims.items;
  134. let i = items.length - 1;
  135. for(; i >= 0; --i){
  136. items[i].cancel();
  137. }
  138. anims.items = [];
  139. this._notify(chart, anims, Date.now(), 'complete');
  140. }
  141. remove(chart) {
  142. return this._charts.delete(chart);
  143. }
  144. }
  145. var animator = /* #__PURE__ */ new Animator();
  146. const transparent = 'transparent';
  147. const interpolators = {
  148. boolean (from, to, factor) {
  149. return factor > 0.5 ? to : from;
  150. },
  151. color (from, to, factor) {
  152. const c0 = helpers_segment.color(from || transparent);
  153. const c1 = c0.valid && helpers_segment.color(to || transparent);
  154. return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
  155. },
  156. number (from, to, factor) {
  157. return from + (to - from) * factor;
  158. }
  159. };
  160. class Animation {
  161. constructor(cfg, target, prop, to){
  162. const currentValue = target[prop];
  163. to = helpers_segment.resolve([
  164. cfg.to,
  165. to,
  166. currentValue,
  167. cfg.from
  168. ]);
  169. const from = helpers_segment.resolve([
  170. cfg.from,
  171. currentValue,
  172. to
  173. ]);
  174. this._active = true;
  175. this._fn = cfg.fn || interpolators[cfg.type || typeof from];
  176. this._easing = helpers_segment.effects[cfg.easing] || helpers_segment.effects.linear;
  177. this._start = Math.floor(Date.now() + (cfg.delay || 0));
  178. this._duration = this._total = Math.floor(cfg.duration);
  179. this._loop = !!cfg.loop;
  180. this._target = target;
  181. this._prop = prop;
  182. this._from = from;
  183. this._to = to;
  184. this._promises = undefined;
  185. }
  186. active() {
  187. return this._active;
  188. }
  189. update(cfg, to, date) {
  190. if (this._active) {
  191. this._notify(false);
  192. const currentValue = this._target[this._prop];
  193. const elapsed = date - this._start;
  194. const remain = this._duration - elapsed;
  195. this._start = date;
  196. this._duration = Math.floor(Math.max(remain, cfg.duration));
  197. this._total += elapsed;
  198. this._loop = !!cfg.loop;
  199. this._to = helpers_segment.resolve([
  200. cfg.to,
  201. to,
  202. currentValue,
  203. cfg.from
  204. ]);
  205. this._from = helpers_segment.resolve([
  206. cfg.from,
  207. currentValue,
  208. to
  209. ]);
  210. }
  211. }
  212. cancel() {
  213. if (this._active) {
  214. this.tick(Date.now());
  215. this._active = false;
  216. this._notify(false);
  217. }
  218. }
  219. tick(date) {
  220. const elapsed = date - this._start;
  221. const duration = this._duration;
  222. const prop = this._prop;
  223. const from = this._from;
  224. const loop = this._loop;
  225. const to = this._to;
  226. let factor;
  227. this._active = from !== to && (loop || elapsed < duration);
  228. if (!this._active) {
  229. this._target[prop] = to;
  230. this._notify(true);
  231. return;
  232. }
  233. if (elapsed < 0) {
  234. this._target[prop] = from;
  235. return;
  236. }
  237. factor = elapsed / duration % 2;
  238. factor = loop && factor > 1 ? 2 - factor : factor;
  239. factor = this._easing(Math.min(1, Math.max(0, factor)));
  240. this._target[prop] = this._fn(from, to, factor);
  241. }
  242. wait() {
  243. const promises = this._promises || (this._promises = []);
  244. return new Promise((res, rej)=>{
  245. promises.push({
  246. res,
  247. rej
  248. });
  249. });
  250. }
  251. _notify(resolved) {
  252. const method = resolved ? 'res' : 'rej';
  253. const promises = this._promises || [];
  254. for(let i = 0; i < promises.length; i++){
  255. promises[i][method]();
  256. }
  257. }
  258. }
  259. class Animations {
  260. constructor(chart, config){
  261. this._chart = chart;
  262. this._properties = new Map();
  263. this.configure(config);
  264. }
  265. configure(config) {
  266. if (!helpers_segment.isObject(config)) {
  267. return;
  268. }
  269. const animationOptions = Object.keys(helpers_segment.defaults.animation);
  270. const animatedProps = this._properties;
  271. Object.getOwnPropertyNames(config).forEach((key)=>{
  272. const cfg = config[key];
  273. if (!helpers_segment.isObject(cfg)) {
  274. return;
  275. }
  276. const resolved = {};
  277. for (const option of animationOptions){
  278. resolved[option] = cfg[option];
  279. }
  280. (helpers_segment.isArray(cfg.properties) && cfg.properties || [
  281. key
  282. ]).forEach((prop)=>{
  283. if (prop === key || !animatedProps.has(prop)) {
  284. animatedProps.set(prop, resolved);
  285. }
  286. });
  287. });
  288. }
  289. _animateOptions(target, values) {
  290. const newOptions = values.options;
  291. const options = resolveTargetOptions(target, newOptions);
  292. if (!options) {
  293. return [];
  294. }
  295. const animations = this._createAnimations(options, newOptions);
  296. if (newOptions.$shared) {
  297. awaitAll(target.options.$animations, newOptions).then(()=>{
  298. target.options = newOptions;
  299. }, ()=>{
  300. });
  301. }
  302. return animations;
  303. }
  304. _createAnimations(target, values) {
  305. const animatedProps = this._properties;
  306. const animations = [];
  307. const running = target.$animations || (target.$animations = {});
  308. const props = Object.keys(values);
  309. const date = Date.now();
  310. let i;
  311. for(i = props.length - 1; i >= 0; --i){
  312. const prop = props[i];
  313. if (prop.charAt(0) === '$') {
  314. continue;
  315. }
  316. if (prop === 'options') {
  317. animations.push(...this._animateOptions(target, values));
  318. continue;
  319. }
  320. const value = values[prop];
  321. let animation = running[prop];
  322. const cfg = animatedProps.get(prop);
  323. if (animation) {
  324. if (cfg && animation.active()) {
  325. animation.update(cfg, value, date);
  326. continue;
  327. } else {
  328. animation.cancel();
  329. }
  330. }
  331. if (!cfg || !cfg.duration) {
  332. target[prop] = value;
  333. continue;
  334. }
  335. running[prop] = animation = new Animation(cfg, target, prop, value);
  336. animations.push(animation);
  337. }
  338. return animations;
  339. }
  340. update(target, values) {
  341. if (this._properties.size === 0) {
  342. Object.assign(target, values);
  343. return;
  344. }
  345. const animations = this._createAnimations(target, values);
  346. if (animations.length) {
  347. animator.add(this._chart, animations);
  348. return true;
  349. }
  350. }
  351. }
  352. function awaitAll(animations, properties) {
  353. const running = [];
  354. const keys = Object.keys(properties);
  355. for(let i = 0; i < keys.length; i++){
  356. const anim = animations[keys[i]];
  357. if (anim && anim.active()) {
  358. running.push(anim.wait());
  359. }
  360. }
  361. return Promise.all(running);
  362. }
  363. function resolveTargetOptions(target, newOptions) {
  364. if (!newOptions) {
  365. return;
  366. }
  367. let options = target.options;
  368. if (!options) {
  369. target.options = newOptions;
  370. return;
  371. }
  372. if (options.$shared) {
  373. target.options = options = Object.assign({}, options, {
  374. $shared: false,
  375. $animations: {}
  376. });
  377. }
  378. return options;
  379. }
  380. function scaleClip(scale, allowedOverflow) {
  381. const opts = scale && scale.options || {};
  382. const reverse = opts.reverse;
  383. const min = opts.min === undefined ? allowedOverflow : 0;
  384. const max = opts.max === undefined ? allowedOverflow : 0;
  385. return {
  386. start: reverse ? max : min,
  387. end: reverse ? min : max
  388. };
  389. }
  390. function defaultClip(xScale, yScale, allowedOverflow) {
  391. if (allowedOverflow === false) {
  392. return false;
  393. }
  394. const x = scaleClip(xScale, allowedOverflow);
  395. const y = scaleClip(yScale, allowedOverflow);
  396. return {
  397. top: y.end,
  398. right: x.end,
  399. bottom: y.start,
  400. left: x.start
  401. };
  402. }
  403. function toClip(value) {
  404. let t, r, b, l;
  405. if (helpers_segment.isObject(value)) {
  406. t = value.top;
  407. r = value.right;
  408. b = value.bottom;
  409. l = value.left;
  410. } else {
  411. t = r = b = l = value;
  412. }
  413. return {
  414. top: t,
  415. right: r,
  416. bottom: b,
  417. left: l,
  418. disabled: value === false
  419. };
  420. }
  421. function getSortedDatasetIndices(chart, filterVisible) {
  422. const keys = [];
  423. const metasets = chart._getSortedDatasetMetas(filterVisible);
  424. let i, ilen;
  425. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  426. keys.push(metasets[i].index);
  427. }
  428. return keys;
  429. }
  430. function applyStack(stack, value, dsIndex, options = {}) {
  431. const keys = stack.keys;
  432. const singleMode = options.mode === 'single';
  433. let i, ilen, datasetIndex, otherValue;
  434. if (value === null) {
  435. return;
  436. }
  437. for(i = 0, ilen = keys.length; i < ilen; ++i){
  438. datasetIndex = +keys[i];
  439. if (datasetIndex === dsIndex) {
  440. if (options.all) {
  441. continue;
  442. }
  443. break;
  444. }
  445. otherValue = stack.values[datasetIndex];
  446. if (helpers_segment.isNumberFinite(otherValue) && (singleMode || value === 0 || helpers_segment.sign(value) === helpers_segment.sign(otherValue))) {
  447. value += otherValue;
  448. }
  449. }
  450. return value;
  451. }
  452. function convertObjectDataToArray(data) {
  453. const keys = Object.keys(data);
  454. const adata = new Array(keys.length);
  455. let i, ilen, key;
  456. for(i = 0, ilen = keys.length; i < ilen; ++i){
  457. key = keys[i];
  458. adata[i] = {
  459. x: key,
  460. y: data[key]
  461. };
  462. }
  463. return adata;
  464. }
  465. function isStacked(scale, meta) {
  466. const stacked = scale && scale.options.stacked;
  467. return stacked || stacked === undefined && meta.stack !== undefined;
  468. }
  469. function getStackKey(indexScale, valueScale, meta) {
  470. return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
  471. }
  472. function getUserBounds(scale) {
  473. const { min , max , minDefined , maxDefined } = scale.getUserBounds();
  474. return {
  475. min: minDefined ? min : Number.NEGATIVE_INFINITY,
  476. max: maxDefined ? max : Number.POSITIVE_INFINITY
  477. };
  478. }
  479. function getOrCreateStack(stacks, stackKey, indexValue) {
  480. const subStack = stacks[stackKey] || (stacks[stackKey] = {});
  481. return subStack[indexValue] || (subStack[indexValue] = {});
  482. }
  483. function getLastIndexInStack(stack, vScale, positive, type) {
  484. for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
  485. const value = stack[meta.index];
  486. if (positive && value > 0 || !positive && value < 0) {
  487. return meta.index;
  488. }
  489. }
  490. return null;
  491. }
  492. function updateStacks(controller, parsed) {
  493. const { chart , _cachedMeta: meta } = controller;
  494. const stacks = chart._stacks || (chart._stacks = {});
  495. const { iScale , vScale , index: datasetIndex } = meta;
  496. const iAxis = iScale.axis;
  497. const vAxis = vScale.axis;
  498. const key = getStackKey(iScale, vScale, meta);
  499. const ilen = parsed.length;
  500. let stack;
  501. for(let i = 0; i < ilen; ++i){
  502. const item = parsed[i];
  503. const { [iAxis]: index , [vAxis]: value } = item;
  504. const itemStacks = item._stacks || (item._stacks = {});
  505. stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
  506. stack[datasetIndex] = value;
  507. stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
  508. stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
  509. const visualValues = stack._visualValues || (stack._visualValues = {});
  510. visualValues[datasetIndex] = value;
  511. }
  512. }
  513. function getFirstScaleId(chart, axis) {
  514. const scales = chart.scales;
  515. return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
  516. }
  517. function createDatasetContext(parent, index) {
  518. return helpers_segment.createContext(parent, {
  519. active: false,
  520. dataset: undefined,
  521. datasetIndex: index,
  522. index,
  523. mode: 'default',
  524. type: 'dataset'
  525. });
  526. }
  527. function createDataContext(parent, index, element) {
  528. return helpers_segment.createContext(parent, {
  529. active: false,
  530. dataIndex: index,
  531. parsed: undefined,
  532. raw: undefined,
  533. element,
  534. index,
  535. mode: 'default',
  536. type: 'data'
  537. });
  538. }
  539. function clearStacks(meta, items) {
  540. const datasetIndex = meta.controller.index;
  541. const axis = meta.vScale && meta.vScale.axis;
  542. if (!axis) {
  543. return;
  544. }
  545. items = items || meta._parsed;
  546. for (const parsed of items){
  547. const stacks = parsed._stacks;
  548. if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
  549. return;
  550. }
  551. delete stacks[axis][datasetIndex];
  552. if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
  553. delete stacks[axis]._visualValues[datasetIndex];
  554. }
  555. }
  556. }
  557. const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
  558. const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
  559. const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
  560. keys: getSortedDatasetIndices(chart, true),
  561. values: null
  562. };
  563. class DatasetController {
  564. static defaults = {};
  565. static datasetElementType = null;
  566. static dataElementType = null;
  567. constructor(chart, datasetIndex){
  568. this.chart = chart;
  569. this._ctx = chart.ctx;
  570. this.index = datasetIndex;
  571. this._cachedDataOpts = {};
  572. this._cachedMeta = this.getMeta();
  573. this._type = this._cachedMeta.type;
  574. this.options = undefined;
  575. this._parsing = false;
  576. this._data = undefined;
  577. this._objectData = undefined;
  578. this._sharedOptions = undefined;
  579. this._drawStart = undefined;
  580. this._drawCount = undefined;
  581. this.enableOptionSharing = false;
  582. this.supportsDecimation = false;
  583. this.$context = undefined;
  584. this._syncList = [];
  585. this.datasetElementType = new.target.datasetElementType;
  586. this.dataElementType = new.target.dataElementType;
  587. this.initialize();
  588. }
  589. initialize() {
  590. const meta = this._cachedMeta;
  591. this.configure();
  592. this.linkScales();
  593. meta._stacked = isStacked(meta.vScale, meta);
  594. this.addElements();
  595. if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
  596. console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
  597. }
  598. }
  599. updateIndex(datasetIndex) {
  600. if (this.index !== datasetIndex) {
  601. clearStacks(this._cachedMeta);
  602. }
  603. this.index = datasetIndex;
  604. }
  605. linkScales() {
  606. const chart = this.chart;
  607. const meta = this._cachedMeta;
  608. const dataset = this.getDataset();
  609. const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
  610. const xid = meta.xAxisID = helpers_segment.valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
  611. const yid = meta.yAxisID = helpers_segment.valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
  612. const rid = meta.rAxisID = helpers_segment.valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
  613. const indexAxis = meta.indexAxis;
  614. const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
  615. const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
  616. meta.xScale = this.getScaleForId(xid);
  617. meta.yScale = this.getScaleForId(yid);
  618. meta.rScale = this.getScaleForId(rid);
  619. meta.iScale = this.getScaleForId(iid);
  620. meta.vScale = this.getScaleForId(vid);
  621. }
  622. getDataset() {
  623. return this.chart.data.datasets[this.index];
  624. }
  625. getMeta() {
  626. return this.chart.getDatasetMeta(this.index);
  627. }
  628. getScaleForId(scaleID) {
  629. return this.chart.scales[scaleID];
  630. }
  631. _getOtherScale(scale) {
  632. const meta = this._cachedMeta;
  633. return scale === meta.iScale ? meta.vScale : meta.iScale;
  634. }
  635. reset() {
  636. this._update('reset');
  637. }
  638. _destroy() {
  639. const meta = this._cachedMeta;
  640. if (this._data) {
  641. helpers_segment.unlistenArrayEvents(this._data, this);
  642. }
  643. if (meta._stacked) {
  644. clearStacks(meta);
  645. }
  646. }
  647. _dataCheck() {
  648. const dataset = this.getDataset();
  649. const data = dataset.data || (dataset.data = []);
  650. const _data = this._data;
  651. if (helpers_segment.isObject(data)) {
  652. this._data = convertObjectDataToArray(data);
  653. } else if (_data !== data) {
  654. if (_data) {
  655. helpers_segment.unlistenArrayEvents(_data, this);
  656. const meta = this._cachedMeta;
  657. clearStacks(meta);
  658. meta._parsed = [];
  659. }
  660. if (data && Object.isExtensible(data)) {
  661. helpers_segment.listenArrayEvents(data, this);
  662. }
  663. this._syncList = [];
  664. this._data = data;
  665. }
  666. }
  667. addElements() {
  668. const meta = this._cachedMeta;
  669. this._dataCheck();
  670. if (this.datasetElementType) {
  671. meta.dataset = new this.datasetElementType();
  672. }
  673. }
  674. buildOrUpdateElements(resetNewElements) {
  675. const meta = this._cachedMeta;
  676. const dataset = this.getDataset();
  677. let stackChanged = false;
  678. this._dataCheck();
  679. const oldStacked = meta._stacked;
  680. meta._stacked = isStacked(meta.vScale, meta);
  681. if (meta.stack !== dataset.stack) {
  682. stackChanged = true;
  683. clearStacks(meta);
  684. meta.stack = dataset.stack;
  685. }
  686. this._resyncElements(resetNewElements);
  687. if (stackChanged || oldStacked !== meta._stacked) {
  688. updateStacks(this, meta._parsed);
  689. }
  690. }
  691. configure() {
  692. const config = this.chart.config;
  693. const scopeKeys = config.datasetScopeKeys(this._type);
  694. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
  695. this.options = config.createResolver(scopes, this.getContext());
  696. this._parsing = this.options.parsing;
  697. this._cachedDataOpts = {};
  698. }
  699. parse(start, count) {
  700. const { _cachedMeta: meta , _data: data } = this;
  701. const { iScale , _stacked } = meta;
  702. const iAxis = iScale.axis;
  703. let sorted = start === 0 && count === data.length ? true : meta._sorted;
  704. let prev = start > 0 && meta._parsed[start - 1];
  705. let i, cur, parsed;
  706. if (this._parsing === false) {
  707. meta._parsed = data;
  708. meta._sorted = true;
  709. parsed = data;
  710. } else {
  711. if (helpers_segment.isArray(data[start])) {
  712. parsed = this.parseArrayData(meta, data, start, count);
  713. } else if (helpers_segment.isObject(data[start])) {
  714. parsed = this.parseObjectData(meta, data, start, count);
  715. } else {
  716. parsed = this.parsePrimitiveData(meta, data, start, count);
  717. }
  718. const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
  719. for(i = 0; i < count; ++i){
  720. meta._parsed[i + start] = cur = parsed[i];
  721. if (sorted) {
  722. if (isNotInOrderComparedToPrev()) {
  723. sorted = false;
  724. }
  725. prev = cur;
  726. }
  727. }
  728. meta._sorted = sorted;
  729. }
  730. if (_stacked) {
  731. updateStacks(this, parsed);
  732. }
  733. }
  734. parsePrimitiveData(meta, data, start, count) {
  735. const { iScale , vScale } = meta;
  736. const iAxis = iScale.axis;
  737. const vAxis = vScale.axis;
  738. const labels = iScale.getLabels();
  739. const singleScale = iScale === vScale;
  740. const parsed = new Array(count);
  741. let i, ilen, index;
  742. for(i = 0, ilen = count; i < ilen; ++i){
  743. index = i + start;
  744. parsed[i] = {
  745. [iAxis]: singleScale || iScale.parse(labels[index], index),
  746. [vAxis]: vScale.parse(data[index], index)
  747. };
  748. }
  749. return parsed;
  750. }
  751. parseArrayData(meta, data, start, count) {
  752. const { xScale , yScale } = meta;
  753. const parsed = new Array(count);
  754. let i, ilen, index, item;
  755. for(i = 0, ilen = count; i < ilen; ++i){
  756. index = i + start;
  757. item = data[index];
  758. parsed[i] = {
  759. x: xScale.parse(item[0], index),
  760. y: yScale.parse(item[1], index)
  761. };
  762. }
  763. return parsed;
  764. }
  765. parseObjectData(meta, data, start, count) {
  766. const { xScale , yScale } = meta;
  767. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  768. const parsed = new Array(count);
  769. let i, ilen, index, item;
  770. for(i = 0, ilen = count; i < ilen; ++i){
  771. index = i + start;
  772. item = data[index];
  773. parsed[i] = {
  774. x: xScale.parse(helpers_segment.resolveObjectKey(item, xAxisKey), index),
  775. y: yScale.parse(helpers_segment.resolveObjectKey(item, yAxisKey), index)
  776. };
  777. }
  778. return parsed;
  779. }
  780. getParsed(index) {
  781. return this._cachedMeta._parsed[index];
  782. }
  783. getDataElement(index) {
  784. return this._cachedMeta.data[index];
  785. }
  786. applyStack(scale, parsed, mode) {
  787. const chart = this.chart;
  788. const meta = this._cachedMeta;
  789. const value = parsed[scale.axis];
  790. const stack = {
  791. keys: getSortedDatasetIndices(chart, true),
  792. values: parsed._stacks[scale.axis]._visualValues
  793. };
  794. return applyStack(stack, value, meta.index, {
  795. mode
  796. });
  797. }
  798. updateRangeFromParsed(range, scale, parsed, stack) {
  799. const parsedValue = parsed[scale.axis];
  800. let value = parsedValue === null ? NaN : parsedValue;
  801. const values = stack && parsed._stacks[scale.axis];
  802. if (stack && values) {
  803. stack.values = values;
  804. value = applyStack(stack, parsedValue, this._cachedMeta.index);
  805. }
  806. range.min = Math.min(range.min, value);
  807. range.max = Math.max(range.max, value);
  808. }
  809. getMinMax(scale, canStack) {
  810. const meta = this._cachedMeta;
  811. const _parsed = meta._parsed;
  812. const sorted = meta._sorted && scale === meta.iScale;
  813. const ilen = _parsed.length;
  814. const otherScale = this._getOtherScale(scale);
  815. const stack = createStack(canStack, meta, this.chart);
  816. const range = {
  817. min: Number.POSITIVE_INFINITY,
  818. max: Number.NEGATIVE_INFINITY
  819. };
  820. const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
  821. let i, parsed;
  822. function _skip() {
  823. parsed = _parsed[i];
  824. const otherValue = parsed[otherScale.axis];
  825. return !helpers_segment.isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
  826. }
  827. for(i = 0; i < ilen; ++i){
  828. if (_skip()) {
  829. continue;
  830. }
  831. this.updateRangeFromParsed(range, scale, parsed, stack);
  832. if (sorted) {
  833. break;
  834. }
  835. }
  836. if (sorted) {
  837. for(i = ilen - 1; i >= 0; --i){
  838. if (_skip()) {
  839. continue;
  840. }
  841. this.updateRangeFromParsed(range, scale, parsed, stack);
  842. break;
  843. }
  844. }
  845. return range;
  846. }
  847. getAllParsedValues(scale) {
  848. const parsed = this._cachedMeta._parsed;
  849. const values = [];
  850. let i, ilen, value;
  851. for(i = 0, ilen = parsed.length; i < ilen; ++i){
  852. value = parsed[i][scale.axis];
  853. if (helpers_segment.isNumberFinite(value)) {
  854. values.push(value);
  855. }
  856. }
  857. return values;
  858. }
  859. getMaxOverflow() {
  860. return false;
  861. }
  862. getLabelAndValue(index) {
  863. const meta = this._cachedMeta;
  864. const iScale = meta.iScale;
  865. const vScale = meta.vScale;
  866. const parsed = this.getParsed(index);
  867. return {
  868. label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
  869. value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
  870. };
  871. }
  872. _update(mode) {
  873. const meta = this._cachedMeta;
  874. this.update(mode || 'default');
  875. meta._clip = toClip(helpers_segment.valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
  876. }
  877. update(mode) {}
  878. draw() {
  879. const ctx = this._ctx;
  880. const chart = this.chart;
  881. const meta = this._cachedMeta;
  882. const elements = meta.data || [];
  883. const area = chart.chartArea;
  884. const active = [];
  885. const start = this._drawStart || 0;
  886. const count = this._drawCount || elements.length - start;
  887. const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
  888. let i;
  889. if (meta.dataset) {
  890. meta.dataset.draw(ctx, area, start, count);
  891. }
  892. for(i = start; i < start + count; ++i){
  893. const element = elements[i];
  894. if (element.hidden) {
  895. continue;
  896. }
  897. if (element.active && drawActiveElementsOnTop) {
  898. active.push(element);
  899. } else {
  900. element.draw(ctx, area);
  901. }
  902. }
  903. for(i = 0; i < active.length; ++i){
  904. active[i].draw(ctx, area);
  905. }
  906. }
  907. getStyle(index, active) {
  908. const mode = active ? 'active' : 'default';
  909. return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
  910. }
  911. getContext(index, active, mode) {
  912. const dataset = this.getDataset();
  913. let context;
  914. if (index >= 0 && index < this._cachedMeta.data.length) {
  915. const element = this._cachedMeta.data[index];
  916. context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
  917. context.parsed = this.getParsed(index);
  918. context.raw = dataset.data[index];
  919. context.index = context.dataIndex = index;
  920. } else {
  921. context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
  922. context.dataset = dataset;
  923. context.index = context.datasetIndex = this.index;
  924. }
  925. context.active = !!active;
  926. context.mode = mode;
  927. return context;
  928. }
  929. resolveDatasetElementOptions(mode) {
  930. return this._resolveElementOptions(this.datasetElementType.id, mode);
  931. }
  932. resolveDataElementOptions(index, mode) {
  933. return this._resolveElementOptions(this.dataElementType.id, mode, index);
  934. }
  935. _resolveElementOptions(elementType, mode = 'default', index) {
  936. const active = mode === 'active';
  937. const cache = this._cachedDataOpts;
  938. const cacheKey = elementType + '-' + mode;
  939. const cached = cache[cacheKey];
  940. const sharing = this.enableOptionSharing && helpers_segment.defined(index);
  941. if (cached) {
  942. return cloneIfNotShared(cached, sharing);
  943. }
  944. const config = this.chart.config;
  945. const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
  946. const prefixes = active ? [
  947. `${elementType}Hover`,
  948. 'hover',
  949. elementType,
  950. ''
  951. ] : [
  952. elementType,
  953. ''
  954. ];
  955. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  956. const names = Object.keys(helpers_segment.defaults.elements[elementType]);
  957. const context = ()=>this.getContext(index, active, mode);
  958. const values = config.resolveNamedOptions(scopes, names, context, prefixes);
  959. if (values.$shared) {
  960. values.$shared = sharing;
  961. cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
  962. }
  963. return values;
  964. }
  965. _resolveAnimations(index, transition, active) {
  966. const chart = this.chart;
  967. const cache = this._cachedDataOpts;
  968. const cacheKey = `animation-${transition}`;
  969. const cached = cache[cacheKey];
  970. if (cached) {
  971. return cached;
  972. }
  973. let options;
  974. if (chart.options.animation !== false) {
  975. const config = this.chart.config;
  976. const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
  977. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  978. options = config.createResolver(scopes, this.getContext(index, active, transition));
  979. }
  980. const animations = new Animations(chart, options && options.animations);
  981. if (options && options._cacheable) {
  982. cache[cacheKey] = Object.freeze(animations);
  983. }
  984. return animations;
  985. }
  986. getSharedOptions(options) {
  987. if (!options.$shared) {
  988. return;
  989. }
  990. return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
  991. }
  992. includeOptions(mode, sharedOptions) {
  993. return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
  994. }
  995. _getSharedOptions(start, mode) {
  996. const firstOpts = this.resolveDataElementOptions(start, mode);
  997. const previouslySharedOptions = this._sharedOptions;
  998. const sharedOptions = this.getSharedOptions(firstOpts);
  999. const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
  1000. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  1001. return {
  1002. sharedOptions,
  1003. includeOptions
  1004. };
  1005. }
  1006. updateElement(element, index, properties, mode) {
  1007. if (isDirectUpdateMode(mode)) {
  1008. Object.assign(element, properties);
  1009. } else {
  1010. this._resolveAnimations(index, mode).update(element, properties);
  1011. }
  1012. }
  1013. updateSharedOptions(sharedOptions, mode, newOptions) {
  1014. if (sharedOptions && !isDirectUpdateMode(mode)) {
  1015. this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
  1016. }
  1017. }
  1018. _setStyle(element, index, mode, active) {
  1019. element.active = active;
  1020. const options = this.getStyle(index, active);
  1021. this._resolveAnimations(index, mode, active).update(element, {
  1022. options: !active && this.getSharedOptions(options) || options
  1023. });
  1024. }
  1025. removeHoverStyle(element, datasetIndex, index) {
  1026. this._setStyle(element, index, 'active', false);
  1027. }
  1028. setHoverStyle(element, datasetIndex, index) {
  1029. this._setStyle(element, index, 'active', true);
  1030. }
  1031. _removeDatasetHoverStyle() {
  1032. const element = this._cachedMeta.dataset;
  1033. if (element) {
  1034. this._setStyle(element, undefined, 'active', false);
  1035. }
  1036. }
  1037. _setDatasetHoverStyle() {
  1038. const element = this._cachedMeta.dataset;
  1039. if (element) {
  1040. this._setStyle(element, undefined, 'active', true);
  1041. }
  1042. }
  1043. _resyncElements(resetNewElements) {
  1044. const data = this._data;
  1045. const elements = this._cachedMeta.data;
  1046. for (const [method, arg1, arg2] of this._syncList){
  1047. this[method](arg1, arg2);
  1048. }
  1049. this._syncList = [];
  1050. const numMeta = elements.length;
  1051. const numData = data.length;
  1052. const count = Math.min(numData, numMeta);
  1053. if (count) {
  1054. this.parse(0, count);
  1055. }
  1056. if (numData > numMeta) {
  1057. this._insertElements(numMeta, numData - numMeta, resetNewElements);
  1058. } else if (numData < numMeta) {
  1059. this._removeElements(numData, numMeta - numData);
  1060. }
  1061. }
  1062. _insertElements(start, count, resetNewElements = true) {
  1063. const meta = this._cachedMeta;
  1064. const data = meta.data;
  1065. const end = start + count;
  1066. let i;
  1067. const move = (arr)=>{
  1068. arr.length += count;
  1069. for(i = arr.length - 1; i >= end; i--){
  1070. arr[i] = arr[i - count];
  1071. }
  1072. };
  1073. move(data);
  1074. for(i = start; i < end; ++i){
  1075. data[i] = new this.dataElementType();
  1076. }
  1077. if (this._parsing) {
  1078. move(meta._parsed);
  1079. }
  1080. this.parse(start, count);
  1081. if (resetNewElements) {
  1082. this.updateElements(data, start, count, 'reset');
  1083. }
  1084. }
  1085. updateElements(element, start, count, mode) {}
  1086. _removeElements(start, count) {
  1087. const meta = this._cachedMeta;
  1088. if (this._parsing) {
  1089. const removed = meta._parsed.splice(start, count);
  1090. if (meta._stacked) {
  1091. clearStacks(meta, removed);
  1092. }
  1093. }
  1094. meta.data.splice(start, count);
  1095. }
  1096. _sync(args) {
  1097. if (this._parsing) {
  1098. this._syncList.push(args);
  1099. } else {
  1100. const [method, arg1, arg2] = args;
  1101. this[method](arg1, arg2);
  1102. }
  1103. this.chart._dataChanges.push([
  1104. this.index,
  1105. ...args
  1106. ]);
  1107. }
  1108. _onDataPush() {
  1109. const count = arguments.length;
  1110. this._sync([
  1111. '_insertElements',
  1112. this.getDataset().data.length - count,
  1113. count
  1114. ]);
  1115. }
  1116. _onDataPop() {
  1117. this._sync([
  1118. '_removeElements',
  1119. this._cachedMeta.data.length - 1,
  1120. 1
  1121. ]);
  1122. }
  1123. _onDataShift() {
  1124. this._sync([
  1125. '_removeElements',
  1126. 0,
  1127. 1
  1128. ]);
  1129. }
  1130. _onDataSplice(start, count) {
  1131. if (count) {
  1132. this._sync([
  1133. '_removeElements',
  1134. start,
  1135. count
  1136. ]);
  1137. }
  1138. const newCount = arguments.length - 2;
  1139. if (newCount) {
  1140. this._sync([
  1141. '_insertElements',
  1142. start,
  1143. newCount
  1144. ]);
  1145. }
  1146. }
  1147. _onDataUnshift() {
  1148. this._sync([
  1149. '_insertElements',
  1150. 0,
  1151. arguments.length
  1152. ]);
  1153. }
  1154. }
  1155. function getAllScaleValues(scale, type) {
  1156. if (!scale._cache.$bar) {
  1157. const visibleMetas = scale.getMatchingVisibleMetas(type);
  1158. let values = [];
  1159. for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
  1160. values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
  1161. }
  1162. scale._cache.$bar = helpers_segment._arrayUnique(values.sort((a, b)=>a - b));
  1163. }
  1164. return scale._cache.$bar;
  1165. }
  1166. function computeMinSampleSize(meta) {
  1167. const scale = meta.iScale;
  1168. const values = getAllScaleValues(scale, meta.type);
  1169. let min = scale._length;
  1170. let i, ilen, curr, prev;
  1171. const updateMinAndPrev = ()=>{
  1172. if (curr === 32767 || curr === -32768) {
  1173. return;
  1174. }
  1175. if (helpers_segment.defined(prev)) {
  1176. min = Math.min(min, Math.abs(curr - prev) || min);
  1177. }
  1178. prev = curr;
  1179. };
  1180. for(i = 0, ilen = values.length; i < ilen; ++i){
  1181. curr = scale.getPixelForValue(values[i]);
  1182. updateMinAndPrev();
  1183. }
  1184. prev = undefined;
  1185. for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
  1186. curr = scale.getPixelForTick(i);
  1187. updateMinAndPrev();
  1188. }
  1189. return min;
  1190. }
  1191. function computeFitCategoryTraits(index, ruler, options, stackCount) {
  1192. const thickness = options.barThickness;
  1193. let size, ratio;
  1194. if (helpers_segment.isNullOrUndef(thickness)) {
  1195. size = ruler.min * options.categoryPercentage;
  1196. ratio = options.barPercentage;
  1197. } else {
  1198. size = thickness * stackCount;
  1199. ratio = 1;
  1200. }
  1201. return {
  1202. chunk: size / stackCount,
  1203. ratio,
  1204. start: ruler.pixels[index] - size / 2
  1205. };
  1206. }
  1207. function computeFlexCategoryTraits(index, ruler, options, stackCount) {
  1208. const pixels = ruler.pixels;
  1209. const curr = pixels[index];
  1210. let prev = index > 0 ? pixels[index - 1] : null;
  1211. let next = index < pixels.length - 1 ? pixels[index + 1] : null;
  1212. const percent = options.categoryPercentage;
  1213. if (prev === null) {
  1214. prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
  1215. }
  1216. if (next === null) {
  1217. next = curr + curr - prev;
  1218. }
  1219. const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
  1220. const size = Math.abs(next - prev) / 2 * percent;
  1221. return {
  1222. chunk: size / stackCount,
  1223. ratio: options.barPercentage,
  1224. start
  1225. };
  1226. }
  1227. function parseFloatBar(entry, item, vScale, i) {
  1228. const startValue = vScale.parse(entry[0], i);
  1229. const endValue = vScale.parse(entry[1], i);
  1230. const min = Math.min(startValue, endValue);
  1231. const max = Math.max(startValue, endValue);
  1232. let barStart = min;
  1233. let barEnd = max;
  1234. if (Math.abs(min) > Math.abs(max)) {
  1235. barStart = max;
  1236. barEnd = min;
  1237. }
  1238. item[vScale.axis] = barEnd;
  1239. item._custom = {
  1240. barStart,
  1241. barEnd,
  1242. start: startValue,
  1243. end: endValue,
  1244. min,
  1245. max
  1246. };
  1247. }
  1248. function parseValue(entry, item, vScale, i) {
  1249. if (helpers_segment.isArray(entry)) {
  1250. parseFloatBar(entry, item, vScale, i);
  1251. } else {
  1252. item[vScale.axis] = vScale.parse(entry, i);
  1253. }
  1254. return item;
  1255. }
  1256. function parseArrayOrPrimitive(meta, data, start, count) {
  1257. const iScale = meta.iScale;
  1258. const vScale = meta.vScale;
  1259. const labels = iScale.getLabels();
  1260. const singleScale = iScale === vScale;
  1261. const parsed = [];
  1262. let i, ilen, item, entry;
  1263. for(i = start, ilen = start + count; i < ilen; ++i){
  1264. entry = data[i];
  1265. item = {};
  1266. item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
  1267. parsed.push(parseValue(entry, item, vScale, i));
  1268. }
  1269. return parsed;
  1270. }
  1271. function isFloatBar(custom) {
  1272. return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  1273. }
  1274. function barSign(size, vScale, actualBase) {
  1275. if (size !== 0) {
  1276. return helpers_segment.sign(size);
  1277. }
  1278. return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  1279. }
  1280. function borderProps(properties) {
  1281. let reverse, start, end, top, bottom;
  1282. if (properties.horizontal) {
  1283. reverse = properties.base > properties.x;
  1284. start = 'left';
  1285. end = 'right';
  1286. } else {
  1287. reverse = properties.base < properties.y;
  1288. start = 'bottom';
  1289. end = 'top';
  1290. }
  1291. if (reverse) {
  1292. top = 'end';
  1293. bottom = 'start';
  1294. } else {
  1295. top = 'start';
  1296. bottom = 'end';
  1297. }
  1298. return {
  1299. start,
  1300. end,
  1301. reverse,
  1302. top,
  1303. bottom
  1304. };
  1305. }
  1306. function setBorderSkipped(properties, options, stack, index) {
  1307. let edge = options.borderSkipped;
  1308. const res = {};
  1309. if (!edge) {
  1310. properties.borderSkipped = res;
  1311. return;
  1312. }
  1313. if (edge === true) {
  1314. properties.borderSkipped = {
  1315. top: true,
  1316. right: true,
  1317. bottom: true,
  1318. left: true
  1319. };
  1320. return;
  1321. }
  1322. const { start , end , reverse , top , bottom } = borderProps(properties);
  1323. if (edge === 'middle' && stack) {
  1324. properties.enableBorderRadius = true;
  1325. if ((stack._top || 0) === index) {
  1326. edge = top;
  1327. } else if ((stack._bottom || 0) === index) {
  1328. edge = bottom;
  1329. } else {
  1330. res[parseEdge(bottom, start, end, reverse)] = true;
  1331. edge = top;
  1332. }
  1333. }
  1334. res[parseEdge(edge, start, end, reverse)] = true;
  1335. properties.borderSkipped = res;
  1336. }
  1337. function parseEdge(edge, a, b, reverse) {
  1338. if (reverse) {
  1339. edge = swap(edge, a, b);
  1340. edge = startEnd(edge, b, a);
  1341. } else {
  1342. edge = startEnd(edge, a, b);
  1343. }
  1344. return edge;
  1345. }
  1346. function swap(orig, v1, v2) {
  1347. return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  1348. }
  1349. function startEnd(v, start, end) {
  1350. return v === 'start' ? start : v === 'end' ? end : v;
  1351. }
  1352. function setInflateAmount(properties, { inflateAmount }, ratio) {
  1353. properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
  1354. }
  1355. class BarController extends DatasetController {
  1356. static id = 'bar';
  1357. static defaults = {
  1358. datasetElementType: false,
  1359. dataElementType: 'bar',
  1360. categoryPercentage: 0.8,
  1361. barPercentage: 0.9,
  1362. grouped: true,
  1363. animations: {
  1364. numbers: {
  1365. type: 'number',
  1366. properties: [
  1367. 'x',
  1368. 'y',
  1369. 'base',
  1370. 'width',
  1371. 'height'
  1372. ]
  1373. }
  1374. }
  1375. };
  1376. static overrides = {
  1377. scales: {
  1378. _index_: {
  1379. type: 'category',
  1380. offset: true,
  1381. grid: {
  1382. offset: true
  1383. }
  1384. },
  1385. _value_: {
  1386. type: 'linear',
  1387. beginAtZero: true
  1388. }
  1389. }
  1390. };
  1391. parsePrimitiveData(meta, data, start, count) {
  1392. return parseArrayOrPrimitive(meta, data, start, count);
  1393. }
  1394. parseArrayData(meta, data, start, count) {
  1395. return parseArrayOrPrimitive(meta, data, start, count);
  1396. }
  1397. parseObjectData(meta, data, start, count) {
  1398. const { iScale , vScale } = meta;
  1399. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  1400. const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
  1401. const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
  1402. const parsed = [];
  1403. let i, ilen, item, obj;
  1404. for(i = start, ilen = start + count; i < ilen; ++i){
  1405. obj = data[i];
  1406. item = {};
  1407. item[iScale.axis] = iScale.parse(helpers_segment.resolveObjectKey(obj, iAxisKey), i);
  1408. parsed.push(parseValue(helpers_segment.resolveObjectKey(obj, vAxisKey), item, vScale, i));
  1409. }
  1410. return parsed;
  1411. }
  1412. updateRangeFromParsed(range, scale, parsed, stack) {
  1413. super.updateRangeFromParsed(range, scale, parsed, stack);
  1414. const custom = parsed._custom;
  1415. if (custom && scale === this._cachedMeta.vScale) {
  1416. range.min = Math.min(range.min, custom.min);
  1417. range.max = Math.max(range.max, custom.max);
  1418. }
  1419. }
  1420. getMaxOverflow() {
  1421. return 0;
  1422. }
  1423. getLabelAndValue(index) {
  1424. const meta = this._cachedMeta;
  1425. const { iScale , vScale } = meta;
  1426. const parsed = this.getParsed(index);
  1427. const custom = parsed._custom;
  1428. const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
  1429. return {
  1430. label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
  1431. value
  1432. };
  1433. }
  1434. initialize() {
  1435. this.enableOptionSharing = true;
  1436. super.initialize();
  1437. const meta = this._cachedMeta;
  1438. meta.stack = this.getDataset().stack;
  1439. }
  1440. update(mode) {
  1441. const meta = this._cachedMeta;
  1442. this.updateElements(meta.data, 0, meta.data.length, mode);
  1443. }
  1444. updateElements(bars, start, count, mode) {
  1445. const reset = mode === 'reset';
  1446. const { index , _cachedMeta: { vScale } } = this;
  1447. const base = vScale.getBasePixel();
  1448. const horizontal = vScale.isHorizontal();
  1449. const ruler = this._getRuler();
  1450. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1451. for(let i = start; i < start + count; i++){
  1452. const parsed = this.getParsed(i);
  1453. const vpixels = reset || helpers_segment.isNullOrUndef(parsed[vScale.axis]) ? {
  1454. base,
  1455. head: base
  1456. } : this._calculateBarValuePixels(i);
  1457. const ipixels = this._calculateBarIndexPixels(i, ruler);
  1458. const stack = (parsed._stacks || {})[vScale.axis];
  1459. const properties = {
  1460. horizontal,
  1461. base: vpixels.base,
  1462. enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
  1463. x: horizontal ? vpixels.head : ipixels.center,
  1464. y: horizontal ? ipixels.center : vpixels.head,
  1465. height: horizontal ? ipixels.size : Math.abs(vpixels.size),
  1466. width: horizontal ? Math.abs(vpixels.size) : ipixels.size
  1467. };
  1468. if (includeOptions) {
  1469. properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
  1470. }
  1471. const options = properties.options || bars[i].options;
  1472. setBorderSkipped(properties, options, stack, index);
  1473. setInflateAmount(properties, options, ruler.ratio);
  1474. this.updateElement(bars[i], i, properties, mode);
  1475. }
  1476. }
  1477. _getStacks(last, dataIndex) {
  1478. const { iScale } = this._cachedMeta;
  1479. const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
  1480. const stacked = iScale.options.stacked;
  1481. const stacks = [];
  1482. const skipNull = (meta)=>{
  1483. const parsed = meta.controller.getParsed(dataIndex);
  1484. const val = parsed && parsed[meta.vScale.axis];
  1485. if (helpers_segment.isNullOrUndef(val) || isNaN(val)) {
  1486. return true;
  1487. }
  1488. };
  1489. for (const meta of metasets){
  1490. if (dataIndex !== undefined && skipNull(meta)) {
  1491. continue;
  1492. }
  1493. if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
  1494. stacks.push(meta.stack);
  1495. }
  1496. if (meta.index === last) {
  1497. break;
  1498. }
  1499. }
  1500. if (!stacks.length) {
  1501. stacks.push(undefined);
  1502. }
  1503. return stacks;
  1504. }
  1505. _getStackCount(index) {
  1506. return this._getStacks(undefined, index).length;
  1507. }
  1508. _getStackIndex(datasetIndex, name, dataIndex) {
  1509. const stacks = this._getStacks(datasetIndex, dataIndex);
  1510. const index = name !== undefined ? stacks.indexOf(name) : -1;
  1511. return index === -1 ? stacks.length - 1 : index;
  1512. }
  1513. _getRuler() {
  1514. const opts = this.options;
  1515. const meta = this._cachedMeta;
  1516. const iScale = meta.iScale;
  1517. const pixels = [];
  1518. let i, ilen;
  1519. for(i = 0, ilen = meta.data.length; i < ilen; ++i){
  1520. pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
  1521. }
  1522. const barThickness = opts.barThickness;
  1523. const min = barThickness || computeMinSampleSize(meta);
  1524. return {
  1525. min,
  1526. pixels,
  1527. start: iScale._startPixel,
  1528. end: iScale._endPixel,
  1529. stackCount: this._getStackCount(),
  1530. scale: iScale,
  1531. grouped: opts.grouped,
  1532. ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
  1533. };
  1534. }
  1535. _calculateBarValuePixels(index) {
  1536. const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
  1537. const actualBase = baseValue || 0;
  1538. const parsed = this.getParsed(index);
  1539. const custom = parsed._custom;
  1540. const floating = isFloatBar(custom);
  1541. let value = parsed[vScale.axis];
  1542. let start = 0;
  1543. let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
  1544. let head, size;
  1545. if (length !== value) {
  1546. start = length - value;
  1547. length = value;
  1548. }
  1549. if (floating) {
  1550. value = custom.barStart;
  1551. length = custom.barEnd - custom.barStart;
  1552. if (value !== 0 && helpers_segment.sign(value) !== helpers_segment.sign(custom.barEnd)) {
  1553. start = 0;
  1554. }
  1555. start += value;
  1556. }
  1557. const startValue = !helpers_segment.isNullOrUndef(baseValue) && !floating ? baseValue : start;
  1558. let base = vScale.getPixelForValue(startValue);
  1559. if (this.chart.getDataVisibility(index)) {
  1560. head = vScale.getPixelForValue(start + length);
  1561. } else {
  1562. head = base;
  1563. }
  1564. size = head - base;
  1565. if (Math.abs(size) < minBarLength) {
  1566. size = barSign(size, vScale, actualBase) * minBarLength;
  1567. if (value === actualBase) {
  1568. base -= size / 2;
  1569. }
  1570. const startPixel = vScale.getPixelForDecimal(0);
  1571. const endPixel = vScale.getPixelForDecimal(1);
  1572. const min = Math.min(startPixel, endPixel);
  1573. const max = Math.max(startPixel, endPixel);
  1574. base = Math.max(Math.min(base, max), min);
  1575. head = base + size;
  1576. if (_stacked && !floating) {
  1577. parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
  1578. }
  1579. }
  1580. if (base === vScale.getPixelForValue(actualBase)) {
  1581. const halfGrid = helpers_segment.sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
  1582. base += halfGrid;
  1583. size -= halfGrid;
  1584. }
  1585. return {
  1586. size,
  1587. base,
  1588. head,
  1589. center: head + size / 2
  1590. };
  1591. }
  1592. _calculateBarIndexPixels(index, ruler) {
  1593. const scale = ruler.scale;
  1594. const options = this.options;
  1595. const skipNull = options.skipNull;
  1596. const maxBarThickness = helpers_segment.valueOrDefault(options.maxBarThickness, Infinity);
  1597. let center, size;
  1598. if (ruler.grouped) {
  1599. const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
  1600. const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
  1601. const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
  1602. center = range.start + range.chunk * stackIndex + range.chunk / 2;
  1603. size = Math.min(maxBarThickness, range.chunk * range.ratio);
  1604. } else {
  1605. center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
  1606. size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
  1607. }
  1608. return {
  1609. base: center - size / 2,
  1610. head: center + size / 2,
  1611. center,
  1612. size
  1613. };
  1614. }
  1615. draw() {
  1616. const meta = this._cachedMeta;
  1617. const vScale = meta.vScale;
  1618. const rects = meta.data;
  1619. const ilen = rects.length;
  1620. let i = 0;
  1621. for(; i < ilen; ++i){
  1622. if (this.getParsed(i)[vScale.axis] !== null) {
  1623. rects[i].draw(this._ctx);
  1624. }
  1625. }
  1626. }
  1627. }
  1628. class BubbleController extends DatasetController {
  1629. static id = 'bubble';
  1630. static defaults = {
  1631. datasetElementType: false,
  1632. dataElementType: 'point',
  1633. animations: {
  1634. numbers: {
  1635. type: 'number',
  1636. properties: [
  1637. 'x',
  1638. 'y',
  1639. 'borderWidth',
  1640. 'radius'
  1641. ]
  1642. }
  1643. }
  1644. };
  1645. static overrides = {
  1646. scales: {
  1647. x: {
  1648. type: 'linear'
  1649. },
  1650. y: {
  1651. type: 'linear'
  1652. }
  1653. }
  1654. };
  1655. initialize() {
  1656. this.enableOptionSharing = true;
  1657. super.initialize();
  1658. }
  1659. parsePrimitiveData(meta, data, start, count) {
  1660. const parsed = super.parsePrimitiveData(meta, data, start, count);
  1661. for(let i = 0; i < parsed.length; i++){
  1662. parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
  1663. }
  1664. return parsed;
  1665. }
  1666. parseArrayData(meta, data, start, count) {
  1667. const parsed = super.parseArrayData(meta, data, start, count);
  1668. for(let i = 0; i < parsed.length; i++){
  1669. const item = data[start + i];
  1670. parsed[i]._custom = helpers_segment.valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
  1671. }
  1672. return parsed;
  1673. }
  1674. parseObjectData(meta, data, start, count) {
  1675. const parsed = super.parseObjectData(meta, data, start, count);
  1676. for(let i = 0; i < parsed.length; i++){
  1677. const item = data[start + i];
  1678. parsed[i]._custom = helpers_segment.valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
  1679. }
  1680. return parsed;
  1681. }
  1682. getMaxOverflow() {
  1683. const data = this._cachedMeta.data;
  1684. let max = 0;
  1685. for(let i = data.length - 1; i >= 0; --i){
  1686. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  1687. }
  1688. return max > 0 && max;
  1689. }
  1690. getLabelAndValue(index) {
  1691. const meta = this._cachedMeta;
  1692. const labels = this.chart.data.labels || [];
  1693. const { xScale , yScale } = meta;
  1694. const parsed = this.getParsed(index);
  1695. const x = xScale.getLabelForValue(parsed.x);
  1696. const y = yScale.getLabelForValue(parsed.y);
  1697. const r = parsed._custom;
  1698. return {
  1699. label: labels[index] || '',
  1700. value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
  1701. };
  1702. }
  1703. update(mode) {
  1704. const points = this._cachedMeta.data;
  1705. this.updateElements(points, 0, points.length, mode);
  1706. }
  1707. updateElements(points, start, count, mode) {
  1708. const reset = mode === 'reset';
  1709. const { iScale , vScale } = this._cachedMeta;
  1710. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1711. const iAxis = iScale.axis;
  1712. const vAxis = vScale.axis;
  1713. for(let i = start; i < start + count; i++){
  1714. const point = points[i];
  1715. const parsed = !reset && this.getParsed(i);
  1716. const properties = {};
  1717. const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
  1718. const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
  1719. properties.skip = isNaN(iPixel) || isNaN(vPixel);
  1720. if (includeOptions) {
  1721. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  1722. if (reset) {
  1723. properties.options.radius = 0;
  1724. }
  1725. }
  1726. this.updateElement(point, i, properties, mode);
  1727. }
  1728. }
  1729. resolveDataElementOptions(index, mode) {
  1730. const parsed = this.getParsed(index);
  1731. let values = super.resolveDataElementOptions(index, mode);
  1732. if (values.$shared) {
  1733. values = Object.assign({}, values, {
  1734. $shared: false
  1735. });
  1736. }
  1737. const radius = values.radius;
  1738. if (mode !== 'active') {
  1739. values.radius = 0;
  1740. }
  1741. values.radius += helpers_segment.valueOrDefault(parsed && parsed._custom, radius);
  1742. return values;
  1743. }
  1744. }
  1745. function getRatioAndOffset(rotation, circumference, cutout) {
  1746. let ratioX = 1;
  1747. let ratioY = 1;
  1748. let offsetX = 0;
  1749. let offsetY = 0;
  1750. if (circumference < helpers_segment.TAU) {
  1751. const startAngle = rotation;
  1752. const endAngle = startAngle + circumference;
  1753. const startX = Math.cos(startAngle);
  1754. const startY = Math.sin(startAngle);
  1755. const endX = Math.cos(endAngle);
  1756. const endY = Math.sin(endAngle);
  1757. const calcMax = (angle, a, b)=>helpers_segment._angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
  1758. const calcMin = (angle, a, b)=>helpers_segment._angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
  1759. const maxX = calcMax(0, startX, endX);
  1760. const maxY = calcMax(helpers_segment.HALF_PI, startY, endY);
  1761. const minX = calcMin(helpers_segment.PI, startX, endX);
  1762. const minY = calcMin(helpers_segment.PI + helpers_segment.HALF_PI, startY, endY);
  1763. ratioX = (maxX - minX) / 2;
  1764. ratioY = (maxY - minY) / 2;
  1765. offsetX = -(maxX + minX) / 2;
  1766. offsetY = -(maxY + minY) / 2;
  1767. }
  1768. return {
  1769. ratioX,
  1770. ratioY,
  1771. offsetX,
  1772. offsetY
  1773. };
  1774. }
  1775. class DoughnutController extends DatasetController {
  1776. static id = 'doughnut';
  1777. static defaults = {
  1778. datasetElementType: false,
  1779. dataElementType: 'arc',
  1780. animation: {
  1781. animateRotate: true,
  1782. animateScale: false
  1783. },
  1784. animations: {
  1785. numbers: {
  1786. type: 'number',
  1787. properties: [
  1788. 'circumference',
  1789. 'endAngle',
  1790. 'innerRadius',
  1791. 'outerRadius',
  1792. 'startAngle',
  1793. 'x',
  1794. 'y',
  1795. 'offset',
  1796. 'borderWidth',
  1797. 'spacing'
  1798. ]
  1799. }
  1800. },
  1801. cutout: '50%',
  1802. rotation: 0,
  1803. circumference: 360,
  1804. radius: '100%',
  1805. spacing: 0,
  1806. indexAxis: 'r'
  1807. };
  1808. static descriptors = {
  1809. _scriptable: (name)=>name !== 'spacing',
  1810. _indexable: (name)=>name !== 'spacing'
  1811. };
  1812. static overrides = {
  1813. aspectRatio: 1,
  1814. plugins: {
  1815. legend: {
  1816. labels: {
  1817. generateLabels (chart) {
  1818. const data = chart.data;
  1819. if (data.labels.length && data.datasets.length) {
  1820. const { labels: { pointStyle , color } } = chart.legend.options;
  1821. return data.labels.map((label, i)=>{
  1822. const meta = chart.getDatasetMeta(0);
  1823. const style = meta.controller.getStyle(i);
  1824. return {
  1825. text: label,
  1826. fillStyle: style.backgroundColor,
  1827. strokeStyle: style.borderColor,
  1828. fontColor: color,
  1829. lineWidth: style.borderWidth,
  1830. pointStyle: pointStyle,
  1831. hidden: !chart.getDataVisibility(i),
  1832. index: i
  1833. };
  1834. });
  1835. }
  1836. return [];
  1837. }
  1838. },
  1839. onClick (e, legendItem, legend) {
  1840. legend.chart.toggleDataVisibility(legendItem.index);
  1841. legend.chart.update();
  1842. }
  1843. }
  1844. }
  1845. };
  1846. constructor(chart, datasetIndex){
  1847. super(chart, datasetIndex);
  1848. this.enableOptionSharing = true;
  1849. this.innerRadius = undefined;
  1850. this.outerRadius = undefined;
  1851. this.offsetX = undefined;
  1852. this.offsetY = undefined;
  1853. }
  1854. linkScales() {}
  1855. parse(start, count) {
  1856. const data = this.getDataset().data;
  1857. const meta = this._cachedMeta;
  1858. if (this._parsing === false) {
  1859. meta._parsed = data;
  1860. } else {
  1861. let getter = (i)=>+data[i];
  1862. if (helpers_segment.isObject(data[start])) {
  1863. const { key ='value' } = this._parsing;
  1864. getter = (i)=>+helpers_segment.resolveObjectKey(data[i], key);
  1865. }
  1866. let i, ilen;
  1867. for(i = start, ilen = start + count; i < ilen; ++i){
  1868. meta._parsed[i] = getter(i);
  1869. }
  1870. }
  1871. }
  1872. _getRotation() {
  1873. return helpers_segment.toRadians(this.options.rotation - 90);
  1874. }
  1875. _getCircumference() {
  1876. return helpers_segment.toRadians(this.options.circumference);
  1877. }
  1878. _getRotationExtents() {
  1879. let min = helpers_segment.TAU;
  1880. let max = -helpers_segment.TAU;
  1881. for(let i = 0; i < this.chart.data.datasets.length; ++i){
  1882. if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
  1883. const controller = this.chart.getDatasetMeta(i).controller;
  1884. const rotation = controller._getRotation();
  1885. const circumference = controller._getCircumference();
  1886. min = Math.min(min, rotation);
  1887. max = Math.max(max, rotation + circumference);
  1888. }
  1889. }
  1890. return {
  1891. rotation: min,
  1892. circumference: max - min
  1893. };
  1894. }
  1895. update(mode) {
  1896. const chart = this.chart;
  1897. const { chartArea } = chart;
  1898. const meta = this._cachedMeta;
  1899. const arcs = meta.data;
  1900. const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
  1901. const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
  1902. const cutout = Math.min(helpers_segment.toPercentage(this.options.cutout, maxSize), 1);
  1903. const chartWeight = this._getRingWeight(this.index);
  1904. const { circumference , rotation } = this._getRotationExtents();
  1905. const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
  1906. const maxWidth = (chartArea.width - spacing) / ratioX;
  1907. const maxHeight = (chartArea.height - spacing) / ratioY;
  1908. const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
  1909. const outerRadius = helpers_segment.toDimension(this.options.radius, maxRadius);
  1910. const innerRadius = Math.max(outerRadius * cutout, 0);
  1911. const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
  1912. this.offsetX = offsetX * outerRadius;
  1913. this.offsetY = offsetY * outerRadius;
  1914. meta.total = this.calculateTotal();
  1915. this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
  1916. this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
  1917. this.updateElements(arcs, 0, arcs.length, mode);
  1918. }
  1919. _circumference(i, reset) {
  1920. const opts = this.options;
  1921. const meta = this._cachedMeta;
  1922. const circumference = this._getCircumference();
  1923. if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
  1924. return 0;
  1925. }
  1926. return this.calculateCircumference(meta._parsed[i] * circumference / helpers_segment.TAU);
  1927. }
  1928. updateElements(arcs, start, count, mode) {
  1929. const reset = mode === 'reset';
  1930. const chart = this.chart;
  1931. const chartArea = chart.chartArea;
  1932. const opts = chart.options;
  1933. const animationOpts = opts.animation;
  1934. const centerX = (chartArea.left + chartArea.right) / 2;
  1935. const centerY = (chartArea.top + chartArea.bottom) / 2;
  1936. const animateScale = reset && animationOpts.animateScale;
  1937. const innerRadius = animateScale ? 0 : this.innerRadius;
  1938. const outerRadius = animateScale ? 0 : this.outerRadius;
  1939. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1940. let startAngle = this._getRotation();
  1941. let i;
  1942. for(i = 0; i < start; ++i){
  1943. startAngle += this._circumference(i, reset);
  1944. }
  1945. for(i = start; i < start + count; ++i){
  1946. const circumference = this._circumference(i, reset);
  1947. const arc = arcs[i];
  1948. const properties = {
  1949. x: centerX + this.offsetX,
  1950. y: centerY + this.offsetY,
  1951. startAngle,
  1952. endAngle: startAngle + circumference,
  1953. circumference,
  1954. outerRadius,
  1955. innerRadius
  1956. };
  1957. if (includeOptions) {
  1958. properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
  1959. }
  1960. startAngle += circumference;
  1961. this.updateElement(arc, i, properties, mode);
  1962. }
  1963. }
  1964. calculateTotal() {
  1965. const meta = this._cachedMeta;
  1966. const metaData = meta.data;
  1967. let total = 0;
  1968. let i;
  1969. for(i = 0; i < metaData.length; i++){
  1970. const value = meta._parsed[i];
  1971. if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
  1972. total += Math.abs(value);
  1973. }
  1974. }
  1975. return total;
  1976. }
  1977. calculateCircumference(value) {
  1978. const total = this._cachedMeta.total;
  1979. if (total > 0 && !isNaN(value)) {
  1980. return helpers_segment.TAU * (Math.abs(value) / total);
  1981. }
  1982. return 0;
  1983. }
  1984. getLabelAndValue(index) {
  1985. const meta = this._cachedMeta;
  1986. const chart = this.chart;
  1987. const labels = chart.data.labels || [];
  1988. const value = helpers_segment.formatNumber(meta._parsed[index], chart.options.locale);
  1989. return {
  1990. label: labels[index] || '',
  1991. value
  1992. };
  1993. }
  1994. getMaxBorderWidth(arcs) {
  1995. let max = 0;
  1996. const chart = this.chart;
  1997. let i, ilen, meta, controller, options;
  1998. if (!arcs) {
  1999. for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
  2000. if (chart.isDatasetVisible(i)) {
  2001. meta = chart.getDatasetMeta(i);
  2002. arcs = meta.data;
  2003. controller = meta.controller;
  2004. break;
  2005. }
  2006. }
  2007. }
  2008. if (!arcs) {
  2009. return 0;
  2010. }
  2011. for(i = 0, ilen = arcs.length; i < ilen; ++i){
  2012. options = controller.resolveDataElementOptions(i);
  2013. if (options.borderAlign !== 'inner') {
  2014. max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
  2015. }
  2016. }
  2017. return max;
  2018. }
  2019. getMaxOffset(arcs) {
  2020. let max = 0;
  2021. for(let i = 0, ilen = arcs.length; i < ilen; ++i){
  2022. const options = this.resolveDataElementOptions(i);
  2023. max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
  2024. }
  2025. return max;
  2026. }
  2027. _getRingWeightOffset(datasetIndex) {
  2028. let ringWeightOffset = 0;
  2029. for(let i = 0; i < datasetIndex; ++i){
  2030. if (this.chart.isDatasetVisible(i)) {
  2031. ringWeightOffset += this._getRingWeight(i);
  2032. }
  2033. }
  2034. return ringWeightOffset;
  2035. }
  2036. _getRingWeight(datasetIndex) {
  2037. return Math.max(helpers_segment.valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
  2038. }
  2039. _getVisibleDatasetWeightTotal() {
  2040. return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
  2041. }
  2042. }
  2043. class LineController extends DatasetController {
  2044. static id = 'line';
  2045. static defaults = {
  2046. datasetElementType: 'line',
  2047. dataElementType: 'point',
  2048. showLine: true,
  2049. spanGaps: false
  2050. };
  2051. static overrides = {
  2052. scales: {
  2053. _index_: {
  2054. type: 'category'
  2055. },
  2056. _value_: {
  2057. type: 'linear'
  2058. }
  2059. }
  2060. };
  2061. initialize() {
  2062. this.enableOptionSharing = true;
  2063. this.supportsDecimation = true;
  2064. super.initialize();
  2065. }
  2066. update(mode) {
  2067. const meta = this._cachedMeta;
  2068. const { dataset: line , data: points = [] , _dataset } = meta;
  2069. const animationsDisabled = this.chart._animationsDisabled;
  2070. let { start , count } = helpers_segment._getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2071. this._drawStart = start;
  2072. this._drawCount = count;
  2073. if (helpers_segment._scaleRangesChanged(meta)) {
  2074. start = 0;
  2075. count = points.length;
  2076. }
  2077. line._chart = this.chart;
  2078. line._datasetIndex = this.index;
  2079. line._decimated = !!_dataset._decimated;
  2080. line.points = points;
  2081. const options = this.resolveDatasetElementOptions(mode);
  2082. if (!this.options.showLine) {
  2083. options.borderWidth = 0;
  2084. }
  2085. options.segment = this.options.segment;
  2086. this.updateElement(line, undefined, {
  2087. animated: !animationsDisabled,
  2088. options
  2089. }, mode);
  2090. this.updateElements(points, start, count, mode);
  2091. }
  2092. updateElements(points, start, count, mode) {
  2093. const reset = mode === 'reset';
  2094. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2095. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  2096. const iAxis = iScale.axis;
  2097. const vAxis = vScale.axis;
  2098. const { spanGaps , segment } = this.options;
  2099. const maxGapLength = helpers_segment.isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2100. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2101. const end = start + count;
  2102. const pointsCount = points.length;
  2103. let prevParsed = start > 0 && this.getParsed(start - 1);
  2104. for(let i = 0; i < pointsCount; ++i){
  2105. const point = points[i];
  2106. const properties = directUpdate ? point : {};
  2107. if (i < start || i >= end) {
  2108. properties.skip = true;
  2109. continue;
  2110. }
  2111. const parsed = this.getParsed(i);
  2112. const nullData = helpers_segment.isNullOrUndef(parsed[vAxis]);
  2113. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2114. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2115. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2116. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2117. if (segment) {
  2118. properties.parsed = parsed;
  2119. properties.raw = _dataset.data[i];
  2120. }
  2121. if (includeOptions) {
  2122. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2123. }
  2124. if (!directUpdate) {
  2125. this.updateElement(point, i, properties, mode);
  2126. }
  2127. prevParsed = parsed;
  2128. }
  2129. }
  2130. getMaxOverflow() {
  2131. const meta = this._cachedMeta;
  2132. const dataset = meta.dataset;
  2133. const border = dataset.options && dataset.options.borderWidth || 0;
  2134. const data = meta.data || [];
  2135. if (!data.length) {
  2136. return border;
  2137. }
  2138. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2139. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2140. return Math.max(border, firstPoint, lastPoint) / 2;
  2141. }
  2142. draw() {
  2143. const meta = this._cachedMeta;
  2144. meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
  2145. super.draw();
  2146. }
  2147. }
  2148. class PolarAreaController extends DatasetController {
  2149. static id = 'polarArea';
  2150. static defaults = {
  2151. dataElementType: 'arc',
  2152. animation: {
  2153. animateRotate: true,
  2154. animateScale: true
  2155. },
  2156. animations: {
  2157. numbers: {
  2158. type: 'number',
  2159. properties: [
  2160. 'x',
  2161. 'y',
  2162. 'startAngle',
  2163. 'endAngle',
  2164. 'innerRadius',
  2165. 'outerRadius'
  2166. ]
  2167. }
  2168. },
  2169. indexAxis: 'r',
  2170. startAngle: 0
  2171. };
  2172. static overrides = {
  2173. aspectRatio: 1,
  2174. plugins: {
  2175. legend: {
  2176. labels: {
  2177. generateLabels (chart) {
  2178. const data = chart.data;
  2179. if (data.labels.length && data.datasets.length) {
  2180. const { labels: { pointStyle , color } } = chart.legend.options;
  2181. return data.labels.map((label, i)=>{
  2182. const meta = chart.getDatasetMeta(0);
  2183. const style = meta.controller.getStyle(i);
  2184. return {
  2185. text: label,
  2186. fillStyle: style.backgroundColor,
  2187. strokeStyle: style.borderColor,
  2188. fontColor: color,
  2189. lineWidth: style.borderWidth,
  2190. pointStyle: pointStyle,
  2191. hidden: !chart.getDataVisibility(i),
  2192. index: i
  2193. };
  2194. });
  2195. }
  2196. return [];
  2197. }
  2198. },
  2199. onClick (e, legendItem, legend) {
  2200. legend.chart.toggleDataVisibility(legendItem.index);
  2201. legend.chart.update();
  2202. }
  2203. }
  2204. },
  2205. scales: {
  2206. r: {
  2207. type: 'radialLinear',
  2208. angleLines: {
  2209. display: false
  2210. },
  2211. beginAtZero: true,
  2212. grid: {
  2213. circular: true
  2214. },
  2215. pointLabels: {
  2216. display: false
  2217. },
  2218. startAngle: 0
  2219. }
  2220. }
  2221. };
  2222. constructor(chart, datasetIndex){
  2223. super(chart, datasetIndex);
  2224. this.innerRadius = undefined;
  2225. this.outerRadius = undefined;
  2226. }
  2227. getLabelAndValue(index) {
  2228. const meta = this._cachedMeta;
  2229. const chart = this.chart;
  2230. const labels = chart.data.labels || [];
  2231. const value = helpers_segment.formatNumber(meta._parsed[index].r, chart.options.locale);
  2232. return {
  2233. label: labels[index] || '',
  2234. value
  2235. };
  2236. }
  2237. parseObjectData(meta, data, start, count) {
  2238. return helpers_segment._parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2239. }
  2240. update(mode) {
  2241. const arcs = this._cachedMeta.data;
  2242. this._updateRadius();
  2243. this.updateElements(arcs, 0, arcs.length, mode);
  2244. }
  2245. getMinMax() {
  2246. const meta = this._cachedMeta;
  2247. const range = {
  2248. min: Number.POSITIVE_INFINITY,
  2249. max: Number.NEGATIVE_INFINITY
  2250. };
  2251. meta.data.forEach((element, index)=>{
  2252. const parsed = this.getParsed(index).r;
  2253. if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
  2254. if (parsed < range.min) {
  2255. range.min = parsed;
  2256. }
  2257. if (parsed > range.max) {
  2258. range.max = parsed;
  2259. }
  2260. }
  2261. });
  2262. return range;
  2263. }
  2264. _updateRadius() {
  2265. const chart = this.chart;
  2266. const chartArea = chart.chartArea;
  2267. const opts = chart.options;
  2268. const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  2269. const outerRadius = Math.max(minSize / 2, 0);
  2270. const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
  2271. const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
  2272. this.outerRadius = outerRadius - radiusLength * this.index;
  2273. this.innerRadius = this.outerRadius - radiusLength;
  2274. }
  2275. updateElements(arcs, start, count, mode) {
  2276. const reset = mode === 'reset';
  2277. const chart = this.chart;
  2278. const opts = chart.options;
  2279. const animationOpts = opts.animation;
  2280. const scale = this._cachedMeta.rScale;
  2281. const centerX = scale.xCenter;
  2282. const centerY = scale.yCenter;
  2283. const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * helpers_segment.PI;
  2284. let angle = datasetStartAngle;
  2285. let i;
  2286. const defaultAngle = 360 / this.countVisibleElements();
  2287. for(i = 0; i < start; ++i){
  2288. angle += this._computeAngle(i, mode, defaultAngle);
  2289. }
  2290. for(i = start; i < start + count; i++){
  2291. const arc = arcs[i];
  2292. let startAngle = angle;
  2293. let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
  2294. let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
  2295. angle = endAngle;
  2296. if (reset) {
  2297. if (animationOpts.animateScale) {
  2298. outerRadius = 0;
  2299. }
  2300. if (animationOpts.animateRotate) {
  2301. startAngle = endAngle = datasetStartAngle;
  2302. }
  2303. }
  2304. const properties = {
  2305. x: centerX,
  2306. y: centerY,
  2307. innerRadius: 0,
  2308. outerRadius,
  2309. startAngle,
  2310. endAngle,
  2311. options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
  2312. };
  2313. this.updateElement(arc, i, properties, mode);
  2314. }
  2315. }
  2316. countVisibleElements() {
  2317. const meta = this._cachedMeta;
  2318. let count = 0;
  2319. meta.data.forEach((element, index)=>{
  2320. if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
  2321. count++;
  2322. }
  2323. });
  2324. return count;
  2325. }
  2326. _computeAngle(index, mode, defaultAngle) {
  2327. return this.chart.getDataVisibility(index) ? helpers_segment.toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
  2328. }
  2329. }
  2330. class PieController extends DoughnutController {
  2331. static id = 'pie';
  2332. static defaults = {
  2333. cutout: 0,
  2334. rotation: 0,
  2335. circumference: 360,
  2336. radius: '100%'
  2337. };
  2338. }
  2339. class RadarController extends DatasetController {
  2340. static id = 'radar';
  2341. static defaults = {
  2342. datasetElementType: 'line',
  2343. dataElementType: 'point',
  2344. indexAxis: 'r',
  2345. showLine: true,
  2346. elements: {
  2347. line: {
  2348. fill: 'start'
  2349. }
  2350. }
  2351. };
  2352. static overrides = {
  2353. aspectRatio: 1,
  2354. scales: {
  2355. r: {
  2356. type: 'radialLinear'
  2357. }
  2358. }
  2359. };
  2360. getLabelAndValue(index) {
  2361. const vScale = this._cachedMeta.vScale;
  2362. const parsed = this.getParsed(index);
  2363. return {
  2364. label: vScale.getLabels()[index],
  2365. value: '' + vScale.getLabelForValue(parsed[vScale.axis])
  2366. };
  2367. }
  2368. parseObjectData(meta, data, start, count) {
  2369. return helpers_segment._parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2370. }
  2371. update(mode) {
  2372. const meta = this._cachedMeta;
  2373. const line = meta.dataset;
  2374. const points = meta.data || [];
  2375. const labels = meta.iScale.getLabels();
  2376. line.points = points;
  2377. if (mode !== 'resize') {
  2378. const options = this.resolveDatasetElementOptions(mode);
  2379. if (!this.options.showLine) {
  2380. options.borderWidth = 0;
  2381. }
  2382. const properties = {
  2383. _loop: true,
  2384. _fullLoop: labels.length === points.length,
  2385. options
  2386. };
  2387. this.updateElement(line, undefined, properties, mode);
  2388. }
  2389. this.updateElements(points, 0, points.length, mode);
  2390. }
  2391. updateElements(points, start, count, mode) {
  2392. const scale = this._cachedMeta.rScale;
  2393. const reset = mode === 'reset';
  2394. for(let i = start; i < start + count; i++){
  2395. const point = points[i];
  2396. const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2397. const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
  2398. const x = reset ? scale.xCenter : pointPosition.x;
  2399. const y = reset ? scale.yCenter : pointPosition.y;
  2400. const properties = {
  2401. x,
  2402. y,
  2403. angle: pointPosition.angle,
  2404. skip: isNaN(x) || isNaN(y),
  2405. options
  2406. };
  2407. this.updateElement(point, i, properties, mode);
  2408. }
  2409. }
  2410. }
  2411. class ScatterController extends DatasetController {
  2412. static id = 'scatter';
  2413. static defaults = {
  2414. datasetElementType: false,
  2415. dataElementType: 'point',
  2416. showLine: false,
  2417. fill: false
  2418. };
  2419. static overrides = {
  2420. interaction: {
  2421. mode: 'point'
  2422. },
  2423. scales: {
  2424. x: {
  2425. type: 'linear'
  2426. },
  2427. y: {
  2428. type: 'linear'
  2429. }
  2430. }
  2431. };
  2432. getLabelAndValue(index) {
  2433. const meta = this._cachedMeta;
  2434. const labels = this.chart.data.labels || [];
  2435. const { xScale , yScale } = meta;
  2436. const parsed = this.getParsed(index);
  2437. const x = xScale.getLabelForValue(parsed.x);
  2438. const y = yScale.getLabelForValue(parsed.y);
  2439. return {
  2440. label: labels[index] || '',
  2441. value: '(' + x + ', ' + y + ')'
  2442. };
  2443. }
  2444. update(mode) {
  2445. const meta = this._cachedMeta;
  2446. const { data: points = [] } = meta;
  2447. const animationsDisabled = this.chart._animationsDisabled;
  2448. let { start , count } = helpers_segment._getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2449. this._drawStart = start;
  2450. this._drawCount = count;
  2451. if (helpers_segment._scaleRangesChanged(meta)) {
  2452. start = 0;
  2453. count = points.length;
  2454. }
  2455. if (this.options.showLine) {
  2456. const { dataset: line , _dataset } = meta;
  2457. line._chart = this.chart;
  2458. line._datasetIndex = this.index;
  2459. line._decimated = !!_dataset._decimated;
  2460. line.points = points;
  2461. const options = this.resolveDatasetElementOptions(mode);
  2462. options.segment = this.options.segment;
  2463. this.updateElement(line, undefined, {
  2464. animated: !animationsDisabled,
  2465. options
  2466. }, mode);
  2467. }
  2468. this.updateElements(points, start, count, mode);
  2469. }
  2470. addElements() {
  2471. const { showLine } = this.options;
  2472. if (!this.datasetElementType && showLine) {
  2473. this.datasetElementType = this.chart.registry.getElement('line');
  2474. }
  2475. super.addElements();
  2476. }
  2477. updateElements(points, start, count, mode) {
  2478. const reset = mode === 'reset';
  2479. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2480. const firstOpts = this.resolveDataElementOptions(start, mode);
  2481. const sharedOptions = this.getSharedOptions(firstOpts);
  2482. const includeOptions = this.includeOptions(mode, sharedOptions);
  2483. const iAxis = iScale.axis;
  2484. const vAxis = vScale.axis;
  2485. const { spanGaps , segment } = this.options;
  2486. const maxGapLength = helpers_segment.isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2487. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2488. let prevParsed = start > 0 && this.getParsed(start - 1);
  2489. for(let i = start; i < start + count; ++i){
  2490. const point = points[i];
  2491. const parsed = this.getParsed(i);
  2492. const properties = directUpdate ? point : {};
  2493. const nullData = helpers_segment.isNullOrUndef(parsed[vAxis]);
  2494. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2495. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2496. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2497. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2498. if (segment) {
  2499. properties.parsed = parsed;
  2500. properties.raw = _dataset.data[i];
  2501. }
  2502. if (includeOptions) {
  2503. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2504. }
  2505. if (!directUpdate) {
  2506. this.updateElement(point, i, properties, mode);
  2507. }
  2508. prevParsed = parsed;
  2509. }
  2510. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  2511. }
  2512. getMaxOverflow() {
  2513. const meta = this._cachedMeta;
  2514. const data = meta.data || [];
  2515. if (!this.options.showLine) {
  2516. let max = 0;
  2517. for(let i = data.length - 1; i >= 0; --i){
  2518. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  2519. }
  2520. return max > 0 && max;
  2521. }
  2522. const dataset = meta.dataset;
  2523. const border = dataset.options && dataset.options.borderWidth || 0;
  2524. if (!data.length) {
  2525. return border;
  2526. }
  2527. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2528. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2529. return Math.max(border, firstPoint, lastPoint) / 2;
  2530. }
  2531. }
  2532. var controllers = /*#__PURE__*/Object.freeze({
  2533. __proto__: null,
  2534. BarController: BarController,
  2535. BubbleController: BubbleController,
  2536. DoughnutController: DoughnutController,
  2537. LineController: LineController,
  2538. PolarAreaController: PolarAreaController,
  2539. PieController: PieController,
  2540. RadarController: RadarController,
  2541. ScatterController: ScatterController
  2542. });
  2543. /**
  2544. * @namespace Chart._adapters
  2545. * @since 2.8.0
  2546. * @private
  2547. */ function abstract() {
  2548. throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  2549. }
  2550. /**
  2551. * Date adapter (current used by the time scale)
  2552. * @namespace Chart._adapters._date
  2553. * @memberof Chart._adapters
  2554. * @private
  2555. */ class DateAdapterBase {
  2556. /**
  2557. * Override default date adapter methods.
  2558. * Accepts type parameter to define options type.
  2559. * @example
  2560. * Chart._adapters._date.override<{myAdapterOption: string}>({
  2561. * init() {
  2562. * console.log(this.options.myAdapterOption);
  2563. * }
  2564. * })
  2565. */ static override(members) {
  2566. Object.assign(DateAdapterBase.prototype, members);
  2567. }
  2568. constructor(options){
  2569. this.options = options || {};
  2570. }
  2571. // eslint-disable-next-line @typescript-eslint/no-empty-function
  2572. init() {}
  2573. formats() {
  2574. return abstract();
  2575. }
  2576. parse() {
  2577. return abstract();
  2578. }
  2579. format() {
  2580. return abstract();
  2581. }
  2582. add() {
  2583. return abstract();
  2584. }
  2585. diff() {
  2586. return abstract();
  2587. }
  2588. startOf() {
  2589. return abstract();
  2590. }
  2591. endOf() {
  2592. return abstract();
  2593. }
  2594. }
  2595. var adapters = {
  2596. _date: DateAdapterBase
  2597. };
  2598. function binarySearch(metaset, axis, value, intersect) {
  2599. const { controller , data , _sorted } = metaset;
  2600. const iScale = controller._cachedMeta.iScale;
  2601. if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
  2602. const lookupMethod = iScale._reversePixels ? helpers_segment._rlookupByKey : helpers_segment._lookupByKey;
  2603. if (!intersect) {
  2604. return lookupMethod(data, axis, value);
  2605. } else if (controller._sharedOptions) {
  2606. const el = data[0];
  2607. const range = typeof el.getRange === 'function' && el.getRange(axis);
  2608. if (range) {
  2609. const start = lookupMethod(data, axis, value - range);
  2610. const end = lookupMethod(data, axis, value + range);
  2611. return {
  2612. lo: start.lo,
  2613. hi: end.hi
  2614. };
  2615. }
  2616. }
  2617. }
  2618. return {
  2619. lo: 0,
  2620. hi: data.length - 1
  2621. };
  2622. }
  2623. function evaluateInteractionItems(chart, axis, position, handler, intersect) {
  2624. const metasets = chart.getSortedVisibleDatasetMetas();
  2625. const value = position[axis];
  2626. for(let i = 0, ilen = metasets.length; i < ilen; ++i){
  2627. const { index , data } = metasets[i];
  2628. const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
  2629. for(let j = lo; j <= hi; ++j){
  2630. const element = data[j];
  2631. if (!element.skip) {
  2632. handler(element, index, j);
  2633. }
  2634. }
  2635. }
  2636. }
  2637. function getDistanceMetricForAxis(axis) {
  2638. const useX = axis.indexOf('x') !== -1;
  2639. const useY = axis.indexOf('y') !== -1;
  2640. return function(pt1, pt2) {
  2641. const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  2642. const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  2643. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  2644. };
  2645. }
  2646. function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
  2647. const items = [];
  2648. if (!includeInvisible && !chart.isPointInArea(position)) {
  2649. return items;
  2650. }
  2651. const evaluationFunc = function(element, datasetIndex, index) {
  2652. if (!includeInvisible && !helpers_segment._isPointInArea(element, chart.chartArea, 0)) {
  2653. return;
  2654. }
  2655. if (element.inRange(position.x, position.y, useFinalPosition)) {
  2656. items.push({
  2657. element,
  2658. datasetIndex,
  2659. index
  2660. });
  2661. }
  2662. };
  2663. evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
  2664. return items;
  2665. }
  2666. function getNearestRadialItems(chart, position, axis, useFinalPosition) {
  2667. let items = [];
  2668. function evaluationFunc(element, datasetIndex, index) {
  2669. const { startAngle , endAngle } = element.getProps([
  2670. 'startAngle',
  2671. 'endAngle'
  2672. ], useFinalPosition);
  2673. const { angle } = helpers_segment.getAngleFromPoint(element, {
  2674. x: position.x,
  2675. y: position.y
  2676. });
  2677. if (helpers_segment._angleBetween(angle, startAngle, endAngle)) {
  2678. items.push({
  2679. element,
  2680. datasetIndex,
  2681. index
  2682. });
  2683. }
  2684. }
  2685. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2686. return items;
  2687. }
  2688. function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2689. let items = [];
  2690. const distanceMetric = getDistanceMetricForAxis(axis);
  2691. let minDistance = Number.POSITIVE_INFINITY;
  2692. function evaluationFunc(element, datasetIndex, index) {
  2693. const inRange = element.inRange(position.x, position.y, useFinalPosition);
  2694. if (intersect && !inRange) {
  2695. return;
  2696. }
  2697. const center = element.getCenterPoint(useFinalPosition);
  2698. const pointInArea = !!includeInvisible || chart.isPointInArea(center);
  2699. if (!pointInArea && !inRange) {
  2700. return;
  2701. }
  2702. const distance = distanceMetric(position, center);
  2703. if (distance < minDistance) {
  2704. items = [
  2705. {
  2706. element,
  2707. datasetIndex,
  2708. index
  2709. }
  2710. ];
  2711. minDistance = distance;
  2712. } else if (distance === minDistance) {
  2713. items.push({
  2714. element,
  2715. datasetIndex,
  2716. index
  2717. });
  2718. }
  2719. }
  2720. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2721. return items;
  2722. }
  2723. function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2724. if (!includeInvisible && !chart.isPointInArea(position)) {
  2725. return [];
  2726. }
  2727. return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
  2728. }
  2729. function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
  2730. const items = [];
  2731. const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
  2732. let intersectsItem = false;
  2733. evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
  2734. if (element[rangeMethod](position[axis], useFinalPosition)) {
  2735. items.push({
  2736. element,
  2737. datasetIndex,
  2738. index
  2739. });
  2740. intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
  2741. }
  2742. });
  2743. if (intersect && !intersectsItem) {
  2744. return [];
  2745. }
  2746. return items;
  2747. }
  2748. var Interaction = {
  2749. evaluateInteractionItems,
  2750. modes: {
  2751. index (chart, e, options, useFinalPosition) {
  2752. const position = helpers_segment.getRelativePosition(e, chart);
  2753. const axis = options.axis || 'x';
  2754. const includeInvisible = options.includeInvisible || false;
  2755. const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2756. const elements = [];
  2757. if (!items.length) {
  2758. return [];
  2759. }
  2760. chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
  2761. const index = items[0].index;
  2762. const element = meta.data[index];
  2763. if (element && !element.skip) {
  2764. elements.push({
  2765. element,
  2766. datasetIndex: meta.index,
  2767. index
  2768. });
  2769. }
  2770. });
  2771. return elements;
  2772. },
  2773. dataset (chart, e, options, useFinalPosition) {
  2774. const position = helpers_segment.getRelativePosition(e, chart);
  2775. const axis = options.axis || 'xy';
  2776. const includeInvisible = options.includeInvisible || false;
  2777. let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2778. if (items.length > 0) {
  2779. const datasetIndex = items[0].datasetIndex;
  2780. const data = chart.getDatasetMeta(datasetIndex).data;
  2781. items = [];
  2782. for(let i = 0; i < data.length; ++i){
  2783. items.push({
  2784. element: data[i],
  2785. datasetIndex,
  2786. index: i
  2787. });
  2788. }
  2789. }
  2790. return items;
  2791. },
  2792. point (chart, e, options, useFinalPosition) {
  2793. const position = helpers_segment.getRelativePosition(e, chart);
  2794. const axis = options.axis || 'xy';
  2795. const includeInvisible = options.includeInvisible || false;
  2796. return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
  2797. },
  2798. nearest (chart, e, options, useFinalPosition) {
  2799. const position = helpers_segment.getRelativePosition(e, chart);
  2800. const axis = options.axis || 'xy';
  2801. const includeInvisible = options.includeInvisible || false;
  2802. return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
  2803. },
  2804. x (chart, e, options, useFinalPosition) {
  2805. const position = helpers_segment.getRelativePosition(e, chart);
  2806. return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
  2807. },
  2808. y (chart, e, options, useFinalPosition) {
  2809. const position = helpers_segment.getRelativePosition(e, chart);
  2810. return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
  2811. }
  2812. }
  2813. };
  2814. const STATIC_POSITIONS = [
  2815. 'left',
  2816. 'top',
  2817. 'right',
  2818. 'bottom'
  2819. ];
  2820. function filterByPosition(array, position) {
  2821. return array.filter((v)=>v.pos === position);
  2822. }
  2823. function filterDynamicPositionByAxis(array, axis) {
  2824. return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
  2825. }
  2826. function sortByWeight(array, reverse) {
  2827. return array.sort((a, b)=>{
  2828. const v0 = reverse ? b : a;
  2829. const v1 = reverse ? a : b;
  2830. return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
  2831. });
  2832. }
  2833. function wrapBoxes(boxes) {
  2834. const layoutBoxes = [];
  2835. let i, ilen, box, pos, stack, stackWeight;
  2836. for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
  2837. box = boxes[i];
  2838. ({ position: pos , options: { stack , stackWeight =1 } } = box);
  2839. layoutBoxes.push({
  2840. index: i,
  2841. box,
  2842. pos,
  2843. horizontal: box.isHorizontal(),
  2844. weight: box.weight,
  2845. stack: stack && pos + stack,
  2846. stackWeight
  2847. });
  2848. }
  2849. return layoutBoxes;
  2850. }
  2851. function buildStacks(layouts) {
  2852. const stacks = {};
  2853. for (const wrap of layouts){
  2854. const { stack , pos , stackWeight } = wrap;
  2855. if (!stack || !STATIC_POSITIONS.includes(pos)) {
  2856. continue;
  2857. }
  2858. const _stack = stacks[stack] || (stacks[stack] = {
  2859. count: 0,
  2860. placed: 0,
  2861. weight: 0,
  2862. size: 0
  2863. });
  2864. _stack.count++;
  2865. _stack.weight += stackWeight;
  2866. }
  2867. return stacks;
  2868. }
  2869. function setLayoutDims(layouts, params) {
  2870. const stacks = buildStacks(layouts);
  2871. const { vBoxMaxWidth , hBoxMaxHeight } = params;
  2872. let i, ilen, layout;
  2873. for(i = 0, ilen = layouts.length; i < ilen; ++i){
  2874. layout = layouts[i];
  2875. const { fullSize } = layout.box;
  2876. const stack = stacks[layout.stack];
  2877. const factor = stack && layout.stackWeight / stack.weight;
  2878. if (layout.horizontal) {
  2879. layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
  2880. layout.height = hBoxMaxHeight;
  2881. } else {
  2882. layout.width = vBoxMaxWidth;
  2883. layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
  2884. }
  2885. }
  2886. return stacks;
  2887. }
  2888. function buildLayoutBoxes(boxes) {
  2889. const layoutBoxes = wrapBoxes(boxes);
  2890. const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
  2891. const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
  2892. const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
  2893. const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
  2894. const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
  2895. const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
  2896. const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
  2897. return {
  2898. fullSize,
  2899. leftAndTop: left.concat(top),
  2900. rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
  2901. chartArea: filterByPosition(layoutBoxes, 'chartArea'),
  2902. vertical: left.concat(right).concat(centerVertical),
  2903. horizontal: top.concat(bottom).concat(centerHorizontal)
  2904. };
  2905. }
  2906. function getCombinedMax(maxPadding, chartArea, a, b) {
  2907. return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  2908. }
  2909. function updateMaxPadding(maxPadding, boxPadding) {
  2910. maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
  2911. maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
  2912. maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
  2913. maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  2914. }
  2915. function updateDims(chartArea, params, layout, stacks) {
  2916. const { pos , box } = layout;
  2917. const maxPadding = chartArea.maxPadding;
  2918. if (!helpers_segment.isObject(pos)) {
  2919. if (layout.size) {
  2920. chartArea[pos] -= layout.size;
  2921. }
  2922. const stack = stacks[layout.stack] || {
  2923. size: 0,
  2924. count: 1
  2925. };
  2926. stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
  2927. layout.size = stack.size / stack.count;
  2928. chartArea[pos] += layout.size;
  2929. }
  2930. if (box.getPadding) {
  2931. updateMaxPadding(maxPadding, box.getPadding());
  2932. }
  2933. const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
  2934. const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
  2935. const widthChanged = newWidth !== chartArea.w;
  2936. const heightChanged = newHeight !== chartArea.h;
  2937. chartArea.w = newWidth;
  2938. chartArea.h = newHeight;
  2939. return layout.horizontal ? {
  2940. same: widthChanged,
  2941. other: heightChanged
  2942. } : {
  2943. same: heightChanged,
  2944. other: widthChanged
  2945. };
  2946. }
  2947. function handleMaxPadding(chartArea) {
  2948. const maxPadding = chartArea.maxPadding;
  2949. function updatePos(pos) {
  2950. const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
  2951. chartArea[pos] += change;
  2952. return change;
  2953. }
  2954. chartArea.y += updatePos('top');
  2955. chartArea.x += updatePos('left');
  2956. updatePos('right');
  2957. updatePos('bottom');
  2958. }
  2959. function getMargins(horizontal, chartArea) {
  2960. const maxPadding = chartArea.maxPadding;
  2961. function marginForPositions(positions) {
  2962. const margin = {
  2963. left: 0,
  2964. top: 0,
  2965. right: 0,
  2966. bottom: 0
  2967. };
  2968. positions.forEach((pos)=>{
  2969. margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
  2970. });
  2971. return margin;
  2972. }
  2973. return horizontal ? marginForPositions([
  2974. 'left',
  2975. 'right'
  2976. ]) : marginForPositions([
  2977. 'top',
  2978. 'bottom'
  2979. ]);
  2980. }
  2981. function fitBoxes(boxes, chartArea, params, stacks) {
  2982. const refitBoxes = [];
  2983. let i, ilen, layout, box, refit, changed;
  2984. for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
  2985. layout = boxes[i];
  2986. box = layout.box;
  2987. box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
  2988. const { same , other } = updateDims(chartArea, params, layout, stacks);
  2989. refit |= same && refitBoxes.length;
  2990. changed = changed || other;
  2991. if (!box.fullSize) {
  2992. refitBoxes.push(layout);
  2993. }
  2994. }
  2995. return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  2996. }
  2997. function setBoxDims(box, left, top, width, height) {
  2998. box.top = top;
  2999. box.left = left;
  3000. box.right = left + width;
  3001. box.bottom = top + height;
  3002. box.width = width;
  3003. box.height = height;
  3004. }
  3005. function placeBoxes(boxes, chartArea, params, stacks) {
  3006. const userPadding = params.padding;
  3007. let { x , y } = chartArea;
  3008. for (const layout of boxes){
  3009. const box = layout.box;
  3010. const stack = stacks[layout.stack] || {
  3011. count: 1,
  3012. placed: 0,
  3013. weight: 1
  3014. };
  3015. const weight = layout.stackWeight / stack.weight || 1;
  3016. if (layout.horizontal) {
  3017. const width = chartArea.w * weight;
  3018. const height = stack.size || box.height;
  3019. if (helpers_segment.defined(stack.start)) {
  3020. y = stack.start;
  3021. }
  3022. if (box.fullSize) {
  3023. setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
  3024. } else {
  3025. setBoxDims(box, chartArea.left + stack.placed, y, width, height);
  3026. }
  3027. stack.start = y;
  3028. stack.placed += width;
  3029. y = box.bottom;
  3030. } else {
  3031. const height1 = chartArea.h * weight;
  3032. const width1 = stack.size || box.width;
  3033. if (helpers_segment.defined(stack.start)) {
  3034. x = stack.start;
  3035. }
  3036. if (box.fullSize) {
  3037. setBoxDims(box, x, userPadding.top, width1, params.outerHeight - userPadding.bottom - userPadding.top);
  3038. } else {
  3039. setBoxDims(box, x, chartArea.top + stack.placed, width1, height1);
  3040. }
  3041. stack.start = x;
  3042. stack.placed += height1;
  3043. x = box.right;
  3044. }
  3045. }
  3046. chartArea.x = x;
  3047. chartArea.y = y;
  3048. }
  3049. var layouts = {
  3050. addBox (chart, item) {
  3051. if (!chart.boxes) {
  3052. chart.boxes = [];
  3053. }
  3054. item.fullSize = item.fullSize || false;
  3055. item.position = item.position || 'top';
  3056. item.weight = item.weight || 0;
  3057. item._layers = item._layers || function() {
  3058. return [
  3059. {
  3060. z: 0,
  3061. draw (chartArea) {
  3062. item.draw(chartArea);
  3063. }
  3064. }
  3065. ];
  3066. };
  3067. chart.boxes.push(item);
  3068. },
  3069. removeBox (chart, layoutItem) {
  3070. const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
  3071. if (index !== -1) {
  3072. chart.boxes.splice(index, 1);
  3073. }
  3074. },
  3075. configure (chart, item, options) {
  3076. item.fullSize = options.fullSize;
  3077. item.position = options.position;
  3078. item.weight = options.weight;
  3079. },
  3080. update (chart, width, height, minPadding) {
  3081. if (!chart) {
  3082. return;
  3083. }
  3084. const padding = helpers_segment.toPadding(chart.options.layout.padding);
  3085. const availableWidth = Math.max(width - padding.width, 0);
  3086. const availableHeight = Math.max(height - padding.height, 0);
  3087. const boxes = buildLayoutBoxes(chart.boxes);
  3088. const verticalBoxes = boxes.vertical;
  3089. const horizontalBoxes = boxes.horizontal;
  3090. helpers_segment.each(chart.boxes, (box)=>{
  3091. if (typeof box.beforeLayout === 'function') {
  3092. box.beforeLayout();
  3093. }
  3094. });
  3095. const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
  3096. const params = Object.freeze({
  3097. outerWidth: width,
  3098. outerHeight: height,
  3099. padding,
  3100. availableWidth,
  3101. availableHeight,
  3102. vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
  3103. hBoxMaxHeight: availableHeight / 2
  3104. });
  3105. const maxPadding = Object.assign({}, padding);
  3106. updateMaxPadding(maxPadding, helpers_segment.toPadding(minPadding));
  3107. const chartArea = Object.assign({
  3108. maxPadding,
  3109. w: availableWidth,
  3110. h: availableHeight,
  3111. x: padding.left,
  3112. y: padding.top
  3113. }, padding);
  3114. const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
  3115. fitBoxes(boxes.fullSize, chartArea, params, stacks);
  3116. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3117. if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
  3118. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3119. }
  3120. handleMaxPadding(chartArea);
  3121. placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
  3122. chartArea.x += chartArea.w;
  3123. chartArea.y += chartArea.h;
  3124. placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
  3125. chart.chartArea = {
  3126. left: chartArea.left,
  3127. top: chartArea.top,
  3128. right: chartArea.left + chartArea.w,
  3129. bottom: chartArea.top + chartArea.h,
  3130. height: chartArea.h,
  3131. width: chartArea.w
  3132. };
  3133. helpers_segment.each(boxes.chartArea, (layout)=>{
  3134. const box = layout.box;
  3135. Object.assign(box, chart.chartArea);
  3136. box.update(chartArea.w, chartArea.h, {
  3137. left: 0,
  3138. top: 0,
  3139. right: 0,
  3140. bottom: 0
  3141. });
  3142. });
  3143. }
  3144. };
  3145. class BasePlatform {
  3146. acquireContext(canvas, aspectRatio) {}
  3147. releaseContext(context) {
  3148. return false;
  3149. }
  3150. addEventListener(chart, type, listener) {}
  3151. removeEventListener(chart, type, listener) {}
  3152. getDevicePixelRatio() {
  3153. return 1;
  3154. }
  3155. getMaximumSize(element, width, height, aspectRatio) {
  3156. width = Math.max(0, width || element.width);
  3157. height = height || element.height;
  3158. return {
  3159. width,
  3160. height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
  3161. };
  3162. }
  3163. isAttached(canvas) {
  3164. return true;
  3165. }
  3166. updateConfig(config) {
  3167. }
  3168. }
  3169. class BasicPlatform extends BasePlatform {
  3170. acquireContext(item) {
  3171. return item && item.getContext && item.getContext('2d') || null;
  3172. }
  3173. updateConfig(config) {
  3174. config.options.animation = false;
  3175. }
  3176. }
  3177. const EXPANDO_KEY = '$chartjs';
  3178. const EVENT_TYPES = {
  3179. touchstart: 'mousedown',
  3180. touchmove: 'mousemove',
  3181. touchend: 'mouseup',
  3182. pointerenter: 'mouseenter',
  3183. pointerdown: 'mousedown',
  3184. pointermove: 'mousemove',
  3185. pointerup: 'mouseup',
  3186. pointerleave: 'mouseout',
  3187. pointerout: 'mouseout'
  3188. };
  3189. const isNullOrEmpty = (value)=>value === null || value === '';
  3190. function initCanvas(canvas, aspectRatio) {
  3191. const style = canvas.style;
  3192. const renderHeight = canvas.getAttribute('height');
  3193. const renderWidth = canvas.getAttribute('width');
  3194. canvas[EXPANDO_KEY] = {
  3195. initial: {
  3196. height: renderHeight,
  3197. width: renderWidth,
  3198. style: {
  3199. display: style.display,
  3200. height: style.height,
  3201. width: style.width
  3202. }
  3203. }
  3204. };
  3205. style.display = style.display || 'block';
  3206. style.boxSizing = style.boxSizing || 'border-box';
  3207. if (isNullOrEmpty(renderWidth)) {
  3208. const displayWidth = helpers_segment.readUsedSize(canvas, 'width');
  3209. if (displayWidth !== undefined) {
  3210. canvas.width = displayWidth;
  3211. }
  3212. }
  3213. if (isNullOrEmpty(renderHeight)) {
  3214. if (canvas.style.height === '') {
  3215. canvas.height = canvas.width / (aspectRatio || 2);
  3216. } else {
  3217. const displayHeight = helpers_segment.readUsedSize(canvas, 'height');
  3218. if (displayHeight !== undefined) {
  3219. canvas.height = displayHeight;
  3220. }
  3221. }
  3222. }
  3223. return canvas;
  3224. }
  3225. const eventListenerOptions = helpers_segment.supportsEventListenerOptions ? {
  3226. passive: true
  3227. } : false;
  3228. function addListener(node, type, listener) {
  3229. node.addEventListener(type, listener, eventListenerOptions);
  3230. }
  3231. function removeListener(chart, type, listener) {
  3232. chart.canvas.removeEventListener(type, listener, eventListenerOptions);
  3233. }
  3234. function fromNativeEvent(event, chart) {
  3235. const type = EVENT_TYPES[event.type] || event.type;
  3236. const { x , y } = helpers_segment.getRelativePosition(event, chart);
  3237. return {
  3238. type,
  3239. chart,
  3240. native: event,
  3241. x: x !== undefined ? x : null,
  3242. y: y !== undefined ? y : null
  3243. };
  3244. }
  3245. function nodeListContains(nodeList, canvas) {
  3246. for (const node of nodeList){
  3247. if (node === canvas || node.contains(canvas)) {
  3248. return true;
  3249. }
  3250. }
  3251. }
  3252. function createAttachObserver(chart, type, listener) {
  3253. const canvas = chart.canvas;
  3254. const observer = new MutationObserver((entries)=>{
  3255. let trigger = false;
  3256. for (const entry of entries){
  3257. trigger = trigger || nodeListContains(entry.addedNodes, canvas);
  3258. trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
  3259. }
  3260. if (trigger) {
  3261. listener();
  3262. }
  3263. });
  3264. observer.observe(document, {
  3265. childList: true,
  3266. subtree: true
  3267. });
  3268. return observer;
  3269. }
  3270. function createDetachObserver(chart, type, listener) {
  3271. const canvas = chart.canvas;
  3272. const observer = new MutationObserver((entries)=>{
  3273. let trigger = false;
  3274. for (const entry of entries){
  3275. trigger = trigger || nodeListContains(entry.removedNodes, canvas);
  3276. trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
  3277. }
  3278. if (trigger) {
  3279. listener();
  3280. }
  3281. });
  3282. observer.observe(document, {
  3283. childList: true,
  3284. subtree: true
  3285. });
  3286. return observer;
  3287. }
  3288. const drpListeningCharts = new Map();
  3289. let oldDevicePixelRatio = 0;
  3290. function onWindowResize() {
  3291. const dpr = window.devicePixelRatio;
  3292. if (dpr === oldDevicePixelRatio) {
  3293. return;
  3294. }
  3295. oldDevicePixelRatio = dpr;
  3296. drpListeningCharts.forEach((resize, chart)=>{
  3297. if (chart.currentDevicePixelRatio !== dpr) {
  3298. resize();
  3299. }
  3300. });
  3301. }
  3302. function listenDevicePixelRatioChanges(chart, resize) {
  3303. if (!drpListeningCharts.size) {
  3304. window.addEventListener('resize', onWindowResize);
  3305. }
  3306. drpListeningCharts.set(chart, resize);
  3307. }
  3308. function unlistenDevicePixelRatioChanges(chart) {
  3309. drpListeningCharts.delete(chart);
  3310. if (!drpListeningCharts.size) {
  3311. window.removeEventListener('resize', onWindowResize);
  3312. }
  3313. }
  3314. function createResizeObserver(chart, type, listener) {
  3315. const canvas = chart.canvas;
  3316. const container = canvas && helpers_segment._getParentNode(canvas);
  3317. if (!container) {
  3318. return;
  3319. }
  3320. const resize = helpers_segment.throttled((width, height)=>{
  3321. const w = container.clientWidth;
  3322. listener(width, height);
  3323. if (w < container.clientWidth) {
  3324. listener();
  3325. }
  3326. }, window);
  3327. const observer = new ResizeObserver((entries)=>{
  3328. const entry = entries[0];
  3329. const width = entry.contentRect.width;
  3330. const height = entry.contentRect.height;
  3331. if (width === 0 && height === 0) {
  3332. return;
  3333. }
  3334. resize(width, height);
  3335. });
  3336. observer.observe(container);
  3337. listenDevicePixelRatioChanges(chart, resize);
  3338. return observer;
  3339. }
  3340. function releaseObserver(chart, type, observer) {
  3341. if (observer) {
  3342. observer.disconnect();
  3343. }
  3344. if (type === 'resize') {
  3345. unlistenDevicePixelRatioChanges(chart);
  3346. }
  3347. }
  3348. function createProxyAndListen(chart, type, listener) {
  3349. const canvas = chart.canvas;
  3350. const proxy = helpers_segment.throttled((event)=>{
  3351. if (chart.ctx !== null) {
  3352. listener(fromNativeEvent(event, chart));
  3353. }
  3354. }, chart);
  3355. addListener(canvas, type, proxy);
  3356. return proxy;
  3357. }
  3358. class DomPlatform extends BasePlatform {
  3359. acquireContext(canvas, aspectRatio) {
  3360. const context = canvas && canvas.getContext && canvas.getContext('2d');
  3361. if (context && context.canvas === canvas) {
  3362. initCanvas(canvas, aspectRatio);
  3363. return context;
  3364. }
  3365. return null;
  3366. }
  3367. releaseContext(context) {
  3368. const canvas = context.canvas;
  3369. if (!canvas[EXPANDO_KEY]) {
  3370. return false;
  3371. }
  3372. const initial = canvas[EXPANDO_KEY].initial;
  3373. [
  3374. 'height',
  3375. 'width'
  3376. ].forEach((prop)=>{
  3377. const value = initial[prop];
  3378. if (helpers_segment.isNullOrUndef(value)) {
  3379. canvas.removeAttribute(prop);
  3380. } else {
  3381. canvas.setAttribute(prop, value);
  3382. }
  3383. });
  3384. const style = initial.style || {};
  3385. Object.keys(style).forEach((key)=>{
  3386. canvas.style[key] = style[key];
  3387. });
  3388. canvas.width = canvas.width;
  3389. delete canvas[EXPANDO_KEY];
  3390. return true;
  3391. }
  3392. addEventListener(chart, type, listener) {
  3393. this.removeEventListener(chart, type);
  3394. const proxies = chart.$proxies || (chart.$proxies = {});
  3395. const handlers = {
  3396. attach: createAttachObserver,
  3397. detach: createDetachObserver,
  3398. resize: createResizeObserver
  3399. };
  3400. const handler = handlers[type] || createProxyAndListen;
  3401. proxies[type] = handler(chart, type, listener);
  3402. }
  3403. removeEventListener(chart, type) {
  3404. const proxies = chart.$proxies || (chart.$proxies = {});
  3405. const proxy = proxies[type];
  3406. if (!proxy) {
  3407. return;
  3408. }
  3409. const handlers = {
  3410. attach: releaseObserver,
  3411. detach: releaseObserver,
  3412. resize: releaseObserver
  3413. };
  3414. const handler = handlers[type] || removeListener;
  3415. handler(chart, type, proxy);
  3416. proxies[type] = undefined;
  3417. }
  3418. getDevicePixelRatio() {
  3419. return window.devicePixelRatio;
  3420. }
  3421. getMaximumSize(canvas, width, height, aspectRatio) {
  3422. return helpers_segment.getMaximumSize(canvas, width, height, aspectRatio);
  3423. }
  3424. isAttached(canvas) {
  3425. const container = helpers_segment._getParentNode(canvas);
  3426. return !!(container && container.isConnected);
  3427. }
  3428. }
  3429. function _detectPlatform(canvas) {
  3430. if (!helpers_segment._isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
  3431. return BasicPlatform;
  3432. }
  3433. return DomPlatform;
  3434. }
  3435. class Element {
  3436. static defaults = {};
  3437. static defaultRoutes = undefined;
  3438. active = false;
  3439. tooltipPosition(useFinalPosition) {
  3440. const { x , y } = this.getProps([
  3441. 'x',
  3442. 'y'
  3443. ], useFinalPosition);
  3444. return {
  3445. x,
  3446. y
  3447. };
  3448. }
  3449. hasValue() {
  3450. return helpers_segment.isNumber(this.x) && helpers_segment.isNumber(this.y);
  3451. }
  3452. getProps(props, final) {
  3453. const anims = this.$animations;
  3454. if (!final || !anims) {
  3455. // let's not create an object, if not needed
  3456. return this;
  3457. }
  3458. const ret = {};
  3459. props.forEach((prop)=>{
  3460. ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
  3461. });
  3462. return ret;
  3463. }
  3464. }
  3465. function autoSkip(scale, ticks) {
  3466. const tickOpts = scale.options.ticks;
  3467. const determinedMaxTicks = determineMaxTicks(scale);
  3468. const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
  3469. const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
  3470. const numMajorIndices = majorIndices.length;
  3471. const first = majorIndices[0];
  3472. const last = majorIndices[numMajorIndices - 1];
  3473. const newTicks = [];
  3474. if (numMajorIndices > ticksLimit) {
  3475. skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
  3476. return newTicks;
  3477. }
  3478. const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
  3479. if (numMajorIndices > 0) {
  3480. let i, ilen;
  3481. const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
  3482. skip(ticks, newTicks, spacing, helpers_segment.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
  3483. for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
  3484. skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
  3485. }
  3486. skip(ticks, newTicks, spacing, last, helpers_segment.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
  3487. return newTicks;
  3488. }
  3489. skip(ticks, newTicks, spacing);
  3490. return newTicks;
  3491. }
  3492. function determineMaxTicks(scale) {
  3493. const offset = scale.options.offset;
  3494. const tickLength = scale._tickSize();
  3495. const maxScale = scale._length / tickLength + (offset ? 0 : 1);
  3496. const maxChart = scale._maxLength / tickLength;
  3497. return Math.floor(Math.min(maxScale, maxChart));
  3498. }
  3499. function calculateSpacing(majorIndices, ticks, ticksLimit) {
  3500. const evenMajorSpacing = getEvenSpacing(majorIndices);
  3501. const spacing = ticks.length / ticksLimit;
  3502. if (!evenMajorSpacing) {
  3503. return Math.max(spacing, 1);
  3504. }
  3505. const factors = helpers_segment._factorize(evenMajorSpacing);
  3506. for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
  3507. const factor = factors[i];
  3508. if (factor > spacing) {
  3509. return factor;
  3510. }
  3511. }
  3512. return Math.max(spacing, 1);
  3513. }
  3514. function getMajorIndices(ticks) {
  3515. const result = [];
  3516. let i, ilen;
  3517. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3518. if (ticks[i].major) {
  3519. result.push(i);
  3520. }
  3521. }
  3522. return result;
  3523. }
  3524. function skipMajors(ticks, newTicks, majorIndices, spacing) {
  3525. let count = 0;
  3526. let next = majorIndices[0];
  3527. let i;
  3528. spacing = Math.ceil(spacing);
  3529. for(i = 0; i < ticks.length; i++){
  3530. if (i === next) {
  3531. newTicks.push(ticks[i]);
  3532. count++;
  3533. next = majorIndices[count * spacing];
  3534. }
  3535. }
  3536. }
  3537. function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
  3538. const start = helpers_segment.valueOrDefault(majorStart, 0);
  3539. const end = Math.min(helpers_segment.valueOrDefault(majorEnd, ticks.length), ticks.length);
  3540. let count = 0;
  3541. let length, i, next;
  3542. spacing = Math.ceil(spacing);
  3543. if (majorEnd) {
  3544. length = majorEnd - majorStart;
  3545. spacing = length / Math.floor(length / spacing);
  3546. }
  3547. next = start;
  3548. while(next < 0){
  3549. count++;
  3550. next = Math.round(start + count * spacing);
  3551. }
  3552. for(i = Math.max(start, 0); i < end; i++){
  3553. if (i === next) {
  3554. newTicks.push(ticks[i]);
  3555. count++;
  3556. next = Math.round(start + count * spacing);
  3557. }
  3558. }
  3559. }
  3560. function getEvenSpacing(arr) {
  3561. const len = arr.length;
  3562. let i, diff;
  3563. if (len < 2) {
  3564. return false;
  3565. }
  3566. for(diff = arr[0], i = 1; i < len; ++i){
  3567. if (arr[i] - arr[i - 1] !== diff) {
  3568. return false;
  3569. }
  3570. }
  3571. return diff;
  3572. }
  3573. const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  3574. const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  3575. const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
  3576. function sample(arr, numItems) {
  3577. const result = [];
  3578. const increment = arr.length / numItems;
  3579. const len = arr.length;
  3580. let i = 0;
  3581. for(; i < len; i += increment){
  3582. result.push(arr[Math.floor(i)]);
  3583. }
  3584. return result;
  3585. }
  3586. function getPixelForGridLine(scale, index, offsetGridLines) {
  3587. const length = scale.ticks.length;
  3588. const validIndex = Math.min(index, length - 1);
  3589. const start = scale._startPixel;
  3590. const end = scale._endPixel;
  3591. const epsilon = 1e-6;
  3592. let lineValue = scale.getPixelForTick(validIndex);
  3593. let offset;
  3594. if (offsetGridLines) {
  3595. if (length === 1) {
  3596. offset = Math.max(lineValue - start, end - lineValue);
  3597. } else if (index === 0) {
  3598. offset = (scale.getPixelForTick(1) - lineValue) / 2;
  3599. } else {
  3600. offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
  3601. }
  3602. lineValue += validIndex < index ? offset : -offset;
  3603. if (lineValue < start - epsilon || lineValue > end + epsilon) {
  3604. return;
  3605. }
  3606. }
  3607. return lineValue;
  3608. }
  3609. function garbageCollect(caches, length) {
  3610. helpers_segment.each(caches, (cache)=>{
  3611. const gc = cache.gc;
  3612. const gcLen = gc.length / 2;
  3613. let i;
  3614. if (gcLen > length) {
  3615. for(i = 0; i < gcLen; ++i){
  3616. delete cache.data[gc[i]];
  3617. }
  3618. gc.splice(0, gcLen);
  3619. }
  3620. });
  3621. }
  3622. function getTickMarkLength(options) {
  3623. return options.drawTicks ? options.tickLength : 0;
  3624. }
  3625. function getTitleHeight(options, fallback) {
  3626. if (!options.display) {
  3627. return 0;
  3628. }
  3629. const font = helpers_segment.toFont(options.font, fallback);
  3630. const padding = helpers_segment.toPadding(options.padding);
  3631. const lines = helpers_segment.isArray(options.text) ? options.text.length : 1;
  3632. return lines * font.lineHeight + padding.height;
  3633. }
  3634. function createScaleContext(parent, scale) {
  3635. return helpers_segment.createContext(parent, {
  3636. scale,
  3637. type: 'scale'
  3638. });
  3639. }
  3640. function createTickContext(parent, index, tick) {
  3641. return helpers_segment.createContext(parent, {
  3642. tick,
  3643. index,
  3644. type: 'tick'
  3645. });
  3646. }
  3647. function titleAlign(align, position, reverse) {
  3648. let ret = helpers_segment._toLeftRightCenter(align);
  3649. if (reverse && position !== 'right' || !reverse && position === 'right') {
  3650. ret = reverseAlign(ret);
  3651. }
  3652. return ret;
  3653. }
  3654. function titleArgs(scale, offset, position, align) {
  3655. const { top , left , bottom , right , chart } = scale;
  3656. const { chartArea , scales } = chart;
  3657. let rotation = 0;
  3658. let maxWidth, titleX, titleY;
  3659. const height = bottom - top;
  3660. const width = right - left;
  3661. if (scale.isHorizontal()) {
  3662. titleX = helpers_segment._alignStartEnd(align, left, right);
  3663. if (helpers_segment.isObject(position)) {
  3664. const positionAxisID = Object.keys(position)[0];
  3665. const value = position[positionAxisID];
  3666. titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
  3667. } else if (position === 'center') {
  3668. titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
  3669. } else {
  3670. titleY = offsetFromEdge(scale, position, offset);
  3671. }
  3672. maxWidth = right - left;
  3673. } else {
  3674. if (helpers_segment.isObject(position)) {
  3675. const positionAxisID1 = Object.keys(position)[0];
  3676. const value1 = position[positionAxisID1];
  3677. titleX = scales[positionAxisID1].getPixelForValue(value1) - width + offset;
  3678. } else if (position === 'center') {
  3679. titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
  3680. } else {
  3681. titleX = offsetFromEdge(scale, position, offset);
  3682. }
  3683. titleY = helpers_segment._alignStartEnd(align, bottom, top);
  3684. rotation = position === 'left' ? -helpers_segment.HALF_PI : helpers_segment.HALF_PI;
  3685. }
  3686. return {
  3687. titleX,
  3688. titleY,
  3689. maxWidth,
  3690. rotation
  3691. };
  3692. }
  3693. class Scale extends Element {
  3694. constructor(cfg){
  3695. super();
  3696. this.id = cfg.id;
  3697. this.type = cfg.type;
  3698. this.options = undefined;
  3699. this.ctx = cfg.ctx;
  3700. this.chart = cfg.chart;
  3701. this.top = undefined;
  3702. this.bottom = undefined;
  3703. this.left = undefined;
  3704. this.right = undefined;
  3705. this.width = undefined;
  3706. this.height = undefined;
  3707. this._margins = {
  3708. left: 0,
  3709. right: 0,
  3710. top: 0,
  3711. bottom: 0
  3712. };
  3713. this.maxWidth = undefined;
  3714. this.maxHeight = undefined;
  3715. this.paddingTop = undefined;
  3716. this.paddingBottom = undefined;
  3717. this.paddingLeft = undefined;
  3718. this.paddingRight = undefined;
  3719. this.axis = undefined;
  3720. this.labelRotation = undefined;
  3721. this.min = undefined;
  3722. this.max = undefined;
  3723. this._range = undefined;
  3724. this.ticks = [];
  3725. this._gridLineItems = null;
  3726. this._labelItems = null;
  3727. this._labelSizes = null;
  3728. this._length = 0;
  3729. this._maxLength = 0;
  3730. this._longestTextCache = {};
  3731. this._startPixel = undefined;
  3732. this._endPixel = undefined;
  3733. this._reversePixels = false;
  3734. this._userMax = undefined;
  3735. this._userMin = undefined;
  3736. this._suggestedMax = undefined;
  3737. this._suggestedMin = undefined;
  3738. this._ticksLength = 0;
  3739. this._borderValue = 0;
  3740. this._cache = {};
  3741. this._dataLimitsCached = false;
  3742. this.$context = undefined;
  3743. }
  3744. init(options) {
  3745. this.options = options.setContext(this.getContext());
  3746. this.axis = options.axis;
  3747. this._userMin = this.parse(options.min);
  3748. this._userMax = this.parse(options.max);
  3749. this._suggestedMin = this.parse(options.suggestedMin);
  3750. this._suggestedMax = this.parse(options.suggestedMax);
  3751. }
  3752. parse(raw, index) {
  3753. return raw;
  3754. }
  3755. getUserBounds() {
  3756. let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
  3757. _userMin = helpers_segment.finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
  3758. _userMax = helpers_segment.finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
  3759. _suggestedMin = helpers_segment.finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
  3760. _suggestedMax = helpers_segment.finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
  3761. return {
  3762. min: helpers_segment.finiteOrDefault(_userMin, _suggestedMin),
  3763. max: helpers_segment.finiteOrDefault(_userMax, _suggestedMax),
  3764. minDefined: helpers_segment.isNumberFinite(_userMin),
  3765. maxDefined: helpers_segment.isNumberFinite(_userMax)
  3766. };
  3767. }
  3768. getMinMax(canStack) {
  3769. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  3770. let range;
  3771. if (minDefined && maxDefined) {
  3772. return {
  3773. min,
  3774. max
  3775. };
  3776. }
  3777. const metas = this.getMatchingVisibleMetas();
  3778. for(let i = 0, ilen = metas.length; i < ilen; ++i){
  3779. range = metas[i].controller.getMinMax(this, canStack);
  3780. if (!minDefined) {
  3781. min = Math.min(min, range.min);
  3782. }
  3783. if (!maxDefined) {
  3784. max = Math.max(max, range.max);
  3785. }
  3786. }
  3787. min = maxDefined && min > max ? max : min;
  3788. max = minDefined && min > max ? min : max;
  3789. return {
  3790. min: helpers_segment.finiteOrDefault(min, helpers_segment.finiteOrDefault(max, min)),
  3791. max: helpers_segment.finiteOrDefault(max, helpers_segment.finiteOrDefault(min, max))
  3792. };
  3793. }
  3794. getPadding() {
  3795. return {
  3796. left: this.paddingLeft || 0,
  3797. top: this.paddingTop || 0,
  3798. right: this.paddingRight || 0,
  3799. bottom: this.paddingBottom || 0
  3800. };
  3801. }
  3802. getTicks() {
  3803. return this.ticks;
  3804. }
  3805. getLabels() {
  3806. const data = this.chart.data;
  3807. return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
  3808. }
  3809. getLabelItems(chartArea = this.chart.chartArea) {
  3810. const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
  3811. return items;
  3812. }
  3813. beforeLayout() {
  3814. this._cache = {};
  3815. this._dataLimitsCached = false;
  3816. }
  3817. beforeUpdate() {
  3818. helpers_segment.callback(this.options.beforeUpdate, [
  3819. this
  3820. ]);
  3821. }
  3822. update(maxWidth, maxHeight, margins) {
  3823. const { beginAtZero , grace , ticks: tickOpts } = this.options;
  3824. const sampleSize = tickOpts.sampleSize;
  3825. this.beforeUpdate();
  3826. this.maxWidth = maxWidth;
  3827. this.maxHeight = maxHeight;
  3828. this._margins = margins = Object.assign({
  3829. left: 0,
  3830. right: 0,
  3831. top: 0,
  3832. bottom: 0
  3833. }, margins);
  3834. this.ticks = null;
  3835. this._labelSizes = null;
  3836. this._gridLineItems = null;
  3837. this._labelItems = null;
  3838. this.beforeSetDimensions();
  3839. this.setDimensions();
  3840. this.afterSetDimensions();
  3841. this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
  3842. if (!this._dataLimitsCached) {
  3843. this.beforeDataLimits();
  3844. this.determineDataLimits();
  3845. this.afterDataLimits();
  3846. this._range = helpers_segment._addGrace(this, grace, beginAtZero);
  3847. this._dataLimitsCached = true;
  3848. }
  3849. this.beforeBuildTicks();
  3850. this.ticks = this.buildTicks() || [];
  3851. this.afterBuildTicks();
  3852. const samplingEnabled = sampleSize < this.ticks.length;
  3853. this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
  3854. this.configure();
  3855. this.beforeCalculateLabelRotation();
  3856. this.calculateLabelRotation();
  3857. this.afterCalculateLabelRotation();
  3858. if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
  3859. this.ticks = autoSkip(this, this.ticks);
  3860. this._labelSizes = null;
  3861. this.afterAutoSkip();
  3862. }
  3863. if (samplingEnabled) {
  3864. this._convertTicksToLabels(this.ticks);
  3865. }
  3866. this.beforeFit();
  3867. this.fit();
  3868. this.afterFit();
  3869. this.afterUpdate();
  3870. }
  3871. configure() {
  3872. let reversePixels = this.options.reverse;
  3873. let startPixel, endPixel;
  3874. if (this.isHorizontal()) {
  3875. startPixel = this.left;
  3876. endPixel = this.right;
  3877. } else {
  3878. startPixel = this.top;
  3879. endPixel = this.bottom;
  3880. reversePixels = !reversePixels;
  3881. }
  3882. this._startPixel = startPixel;
  3883. this._endPixel = endPixel;
  3884. this._reversePixels = reversePixels;
  3885. this._length = endPixel - startPixel;
  3886. this._alignToPixels = this.options.alignToPixels;
  3887. }
  3888. afterUpdate() {
  3889. helpers_segment.callback(this.options.afterUpdate, [
  3890. this
  3891. ]);
  3892. }
  3893. beforeSetDimensions() {
  3894. helpers_segment.callback(this.options.beforeSetDimensions, [
  3895. this
  3896. ]);
  3897. }
  3898. setDimensions() {
  3899. if (this.isHorizontal()) {
  3900. this.width = this.maxWidth;
  3901. this.left = 0;
  3902. this.right = this.width;
  3903. } else {
  3904. this.height = this.maxHeight;
  3905. this.top = 0;
  3906. this.bottom = this.height;
  3907. }
  3908. this.paddingLeft = 0;
  3909. this.paddingTop = 0;
  3910. this.paddingRight = 0;
  3911. this.paddingBottom = 0;
  3912. }
  3913. afterSetDimensions() {
  3914. helpers_segment.callback(this.options.afterSetDimensions, [
  3915. this
  3916. ]);
  3917. }
  3918. _callHooks(name) {
  3919. this.chart.notifyPlugins(name, this.getContext());
  3920. helpers_segment.callback(this.options[name], [
  3921. this
  3922. ]);
  3923. }
  3924. beforeDataLimits() {
  3925. this._callHooks('beforeDataLimits');
  3926. }
  3927. determineDataLimits() {}
  3928. afterDataLimits() {
  3929. this._callHooks('afterDataLimits');
  3930. }
  3931. beforeBuildTicks() {
  3932. this._callHooks('beforeBuildTicks');
  3933. }
  3934. buildTicks() {
  3935. return [];
  3936. }
  3937. afterBuildTicks() {
  3938. this._callHooks('afterBuildTicks');
  3939. }
  3940. beforeTickToLabelConversion() {
  3941. helpers_segment.callback(this.options.beforeTickToLabelConversion, [
  3942. this
  3943. ]);
  3944. }
  3945. generateTickLabels(ticks) {
  3946. const tickOpts = this.options.ticks;
  3947. let i, ilen, tick;
  3948. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3949. tick = ticks[i];
  3950. tick.label = helpers_segment.callback(tickOpts.callback, [
  3951. tick.value,
  3952. i,
  3953. ticks
  3954. ], this);
  3955. }
  3956. }
  3957. afterTickToLabelConversion() {
  3958. helpers_segment.callback(this.options.afterTickToLabelConversion, [
  3959. this
  3960. ]);
  3961. }
  3962. beforeCalculateLabelRotation() {
  3963. helpers_segment.callback(this.options.beforeCalculateLabelRotation, [
  3964. this
  3965. ]);
  3966. }
  3967. calculateLabelRotation() {
  3968. const options = this.options;
  3969. const tickOpts = options.ticks;
  3970. const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
  3971. const minRotation = tickOpts.minRotation || 0;
  3972. const maxRotation = tickOpts.maxRotation;
  3973. let labelRotation = minRotation;
  3974. let tickWidth, maxHeight, maxLabelDiagonal;
  3975. if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
  3976. this.labelRotation = minRotation;
  3977. return;
  3978. }
  3979. const labelSizes = this._getLabelSizes();
  3980. const maxLabelWidth = labelSizes.widest.width;
  3981. const maxLabelHeight = labelSizes.highest.height;
  3982. const maxWidth = helpers_segment._limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
  3983. tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
  3984. if (maxLabelWidth + 6 > tickWidth) {
  3985. tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
  3986. maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
  3987. maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
  3988. labelRotation = helpers_segment.toDegrees(Math.min(Math.asin(helpers_segment._limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(helpers_segment._limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(helpers_segment._limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
  3989. labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
  3990. }
  3991. this.labelRotation = labelRotation;
  3992. }
  3993. afterCalculateLabelRotation() {
  3994. helpers_segment.callback(this.options.afterCalculateLabelRotation, [
  3995. this
  3996. ]);
  3997. }
  3998. afterAutoSkip() {}
  3999. beforeFit() {
  4000. helpers_segment.callback(this.options.beforeFit, [
  4001. this
  4002. ]);
  4003. }
  4004. fit() {
  4005. const minSize = {
  4006. width: 0,
  4007. height: 0
  4008. };
  4009. const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
  4010. const display = this._isVisible();
  4011. const isHorizontal = this.isHorizontal();
  4012. if (display) {
  4013. const titleHeight = getTitleHeight(titleOpts, chart.options.font);
  4014. if (isHorizontal) {
  4015. minSize.width = this.maxWidth;
  4016. minSize.height = getTickMarkLength(gridOpts) + titleHeight;
  4017. } else {
  4018. minSize.height = this.maxHeight;
  4019. minSize.width = getTickMarkLength(gridOpts) + titleHeight;
  4020. }
  4021. if (tickOpts.display && this.ticks.length) {
  4022. const { first , last , widest , highest } = this._getLabelSizes();
  4023. const tickPadding = tickOpts.padding * 2;
  4024. const angleRadians = helpers_segment.toRadians(this.labelRotation);
  4025. const cos = Math.cos(angleRadians);
  4026. const sin = Math.sin(angleRadians);
  4027. if (isHorizontal) {
  4028. const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
  4029. minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
  4030. } else {
  4031. const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
  4032. minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
  4033. }
  4034. this._calculatePadding(first, last, sin, cos);
  4035. }
  4036. }
  4037. this._handleMargins();
  4038. if (isHorizontal) {
  4039. this.width = this._length = chart.width - this._margins.left - this._margins.right;
  4040. this.height = minSize.height;
  4041. } else {
  4042. this.width = minSize.width;
  4043. this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
  4044. }
  4045. }
  4046. _calculatePadding(first, last, sin, cos) {
  4047. const { ticks: { align , padding } , position } = this.options;
  4048. const isRotated = this.labelRotation !== 0;
  4049. const labelsBelowTicks = position !== 'top' && this.axis === 'x';
  4050. if (this.isHorizontal()) {
  4051. const offsetLeft = this.getPixelForTick(0) - this.left;
  4052. const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
  4053. let paddingLeft = 0;
  4054. let paddingRight = 0;
  4055. if (isRotated) {
  4056. if (labelsBelowTicks) {
  4057. paddingLeft = cos * first.width;
  4058. paddingRight = sin * last.height;
  4059. } else {
  4060. paddingLeft = sin * first.height;
  4061. paddingRight = cos * last.width;
  4062. }
  4063. } else if (align === 'start') {
  4064. paddingRight = last.width;
  4065. } else if (align === 'end') {
  4066. paddingLeft = first.width;
  4067. } else if (align !== 'inner') {
  4068. paddingLeft = first.width / 2;
  4069. paddingRight = last.width / 2;
  4070. }
  4071. this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
  4072. this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
  4073. } else {
  4074. let paddingTop = last.height / 2;
  4075. let paddingBottom = first.height / 2;
  4076. if (align === 'start') {
  4077. paddingTop = 0;
  4078. paddingBottom = first.height;
  4079. } else if (align === 'end') {
  4080. paddingTop = last.height;
  4081. paddingBottom = 0;
  4082. }
  4083. this.paddingTop = paddingTop + padding;
  4084. this.paddingBottom = paddingBottom + padding;
  4085. }
  4086. }
  4087. _handleMargins() {
  4088. if (this._margins) {
  4089. this._margins.left = Math.max(this.paddingLeft, this._margins.left);
  4090. this._margins.top = Math.max(this.paddingTop, this._margins.top);
  4091. this._margins.right = Math.max(this.paddingRight, this._margins.right);
  4092. this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
  4093. }
  4094. }
  4095. afterFit() {
  4096. helpers_segment.callback(this.options.afterFit, [
  4097. this
  4098. ]);
  4099. }
  4100. isHorizontal() {
  4101. const { axis , position } = this.options;
  4102. return position === 'top' || position === 'bottom' || axis === 'x';
  4103. }
  4104. isFullSize() {
  4105. return this.options.fullSize;
  4106. }
  4107. _convertTicksToLabels(ticks) {
  4108. this.beforeTickToLabelConversion();
  4109. this.generateTickLabels(ticks);
  4110. let i, ilen;
  4111. for(i = 0, ilen = ticks.length; i < ilen; i++){
  4112. if (helpers_segment.isNullOrUndef(ticks[i].label)) {
  4113. ticks.splice(i, 1);
  4114. ilen--;
  4115. i--;
  4116. }
  4117. }
  4118. this.afterTickToLabelConversion();
  4119. }
  4120. _getLabelSizes() {
  4121. let labelSizes = this._labelSizes;
  4122. if (!labelSizes) {
  4123. const sampleSize = this.options.ticks.sampleSize;
  4124. let ticks = this.ticks;
  4125. if (sampleSize < ticks.length) {
  4126. ticks = sample(ticks, sampleSize);
  4127. }
  4128. this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
  4129. }
  4130. return labelSizes;
  4131. }
  4132. _computeLabelSizes(ticks, length, maxTicksLimit) {
  4133. const { ctx , _longestTextCache: caches } = this;
  4134. const widths = [];
  4135. const heights = [];
  4136. const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
  4137. let widestLabelSize = 0;
  4138. let highestLabelSize = 0;
  4139. let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
  4140. for(i = 0; i < length; i += increment){
  4141. label = ticks[i].label;
  4142. tickFont = this._resolveTickFontOptions(i);
  4143. ctx.font = fontString = tickFont.string;
  4144. cache = caches[fontString] = caches[fontString] || {
  4145. data: {},
  4146. gc: []
  4147. };
  4148. lineHeight = tickFont.lineHeight;
  4149. width = height = 0;
  4150. if (!helpers_segment.isNullOrUndef(label) && !helpers_segment.isArray(label)) {
  4151. width = helpers_segment._measureText(ctx, cache.data, cache.gc, width, label);
  4152. height = lineHeight;
  4153. } else if (helpers_segment.isArray(label)) {
  4154. for(j = 0, jlen = label.length; j < jlen; ++j){
  4155. nestedLabel = label[j];
  4156. if (!helpers_segment.isNullOrUndef(nestedLabel) && !helpers_segment.isArray(nestedLabel)) {
  4157. width = helpers_segment._measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  4158. height += lineHeight;
  4159. }
  4160. }
  4161. }
  4162. widths.push(width);
  4163. heights.push(height);
  4164. widestLabelSize = Math.max(width, widestLabelSize);
  4165. highestLabelSize = Math.max(height, highestLabelSize);
  4166. }
  4167. garbageCollect(caches, length);
  4168. const widest = widths.indexOf(widestLabelSize);
  4169. const highest = heights.indexOf(highestLabelSize);
  4170. const valueAt = (idx)=>({
  4171. width: widths[idx] || 0,
  4172. height: heights[idx] || 0
  4173. });
  4174. return {
  4175. first: valueAt(0),
  4176. last: valueAt(length - 1),
  4177. widest: valueAt(widest),
  4178. highest: valueAt(highest),
  4179. widths,
  4180. heights
  4181. };
  4182. }
  4183. getLabelForValue(value) {
  4184. return value;
  4185. }
  4186. getPixelForValue(value, index) {
  4187. return NaN;
  4188. }
  4189. getValueForPixel(pixel) {}
  4190. getPixelForTick(index) {
  4191. const ticks = this.ticks;
  4192. if (index < 0 || index > ticks.length - 1) {
  4193. return null;
  4194. }
  4195. return this.getPixelForValue(ticks[index].value);
  4196. }
  4197. getPixelForDecimal(decimal) {
  4198. if (this._reversePixels) {
  4199. decimal = 1 - decimal;
  4200. }
  4201. const pixel = this._startPixel + decimal * this._length;
  4202. return helpers_segment._int16Range(this._alignToPixels ? helpers_segment._alignPixel(this.chart, pixel, 0) : pixel);
  4203. }
  4204. getDecimalForPixel(pixel) {
  4205. const decimal = (pixel - this._startPixel) / this._length;
  4206. return this._reversePixels ? 1 - decimal : decimal;
  4207. }
  4208. getBasePixel() {
  4209. return this.getPixelForValue(this.getBaseValue());
  4210. }
  4211. getBaseValue() {
  4212. const { min , max } = this;
  4213. return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
  4214. }
  4215. getContext(index) {
  4216. const ticks = this.ticks || [];
  4217. if (index >= 0 && index < ticks.length) {
  4218. const tick = ticks[index];
  4219. return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
  4220. }
  4221. return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
  4222. }
  4223. _tickSize() {
  4224. const optionTicks = this.options.ticks;
  4225. const rot = helpers_segment.toRadians(this.labelRotation);
  4226. const cos = Math.abs(Math.cos(rot));
  4227. const sin = Math.abs(Math.sin(rot));
  4228. const labelSizes = this._getLabelSizes();
  4229. const padding = optionTicks.autoSkipPadding || 0;
  4230. const w = labelSizes ? labelSizes.widest.width + padding : 0;
  4231. const h = labelSizes ? labelSizes.highest.height + padding : 0;
  4232. return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
  4233. }
  4234. _isVisible() {
  4235. const display = this.options.display;
  4236. if (display !== 'auto') {
  4237. return !!display;
  4238. }
  4239. return this.getMatchingVisibleMetas().length > 0;
  4240. }
  4241. _computeGridLineItems(chartArea) {
  4242. const axis = this.axis;
  4243. const chart = this.chart;
  4244. const options = this.options;
  4245. const { grid , position , border } = options;
  4246. const offset = grid.offset;
  4247. const isHorizontal = this.isHorizontal();
  4248. const ticks = this.ticks;
  4249. const ticksLength = ticks.length + (offset ? 1 : 0);
  4250. const tl = getTickMarkLength(grid);
  4251. const items = [];
  4252. const borderOpts = border.setContext(this.getContext());
  4253. const axisWidth = borderOpts.display ? borderOpts.width : 0;
  4254. const axisHalfWidth = axisWidth / 2;
  4255. const alignBorderValue = function(pixel) {
  4256. return helpers_segment._alignPixel(chart, pixel, axisWidth);
  4257. };
  4258. let borderValue, i, lineValue, alignedLineValue;
  4259. let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
  4260. if (position === 'top') {
  4261. borderValue = alignBorderValue(this.bottom);
  4262. ty1 = this.bottom - tl;
  4263. ty2 = borderValue - axisHalfWidth;
  4264. y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
  4265. y2 = chartArea.bottom;
  4266. } else if (position === 'bottom') {
  4267. borderValue = alignBorderValue(this.top);
  4268. y1 = chartArea.top;
  4269. y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
  4270. ty1 = borderValue + axisHalfWidth;
  4271. ty2 = this.top + tl;
  4272. } else if (position === 'left') {
  4273. borderValue = alignBorderValue(this.right);
  4274. tx1 = this.right - tl;
  4275. tx2 = borderValue - axisHalfWidth;
  4276. x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
  4277. x2 = chartArea.right;
  4278. } else if (position === 'right') {
  4279. borderValue = alignBorderValue(this.left);
  4280. x1 = chartArea.left;
  4281. x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
  4282. tx1 = borderValue + axisHalfWidth;
  4283. tx2 = this.left + tl;
  4284. } else if (axis === 'x') {
  4285. if (position === 'center') {
  4286. borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
  4287. } else if (helpers_segment.isObject(position)) {
  4288. const positionAxisID = Object.keys(position)[0];
  4289. const value = position[positionAxisID];
  4290. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  4291. }
  4292. y1 = chartArea.top;
  4293. y2 = chartArea.bottom;
  4294. ty1 = borderValue + axisHalfWidth;
  4295. ty2 = ty1 + tl;
  4296. } else if (axis === 'y') {
  4297. if (position === 'center') {
  4298. borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
  4299. } else if (helpers_segment.isObject(position)) {
  4300. const positionAxisID1 = Object.keys(position)[0];
  4301. const value1 = position[positionAxisID1];
  4302. borderValue = alignBorderValue(this.chart.scales[positionAxisID1].getPixelForValue(value1));
  4303. }
  4304. tx1 = borderValue - axisHalfWidth;
  4305. tx2 = tx1 - tl;
  4306. x1 = chartArea.left;
  4307. x2 = chartArea.right;
  4308. }
  4309. const limit = helpers_segment.valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
  4310. const step = Math.max(1, Math.ceil(ticksLength / limit));
  4311. for(i = 0; i < ticksLength; i += step){
  4312. const context = this.getContext(i);
  4313. const optsAtIndex = grid.setContext(context);
  4314. const optsAtIndexBorder = border.setContext(context);
  4315. const lineWidth = optsAtIndex.lineWidth;
  4316. const lineColor = optsAtIndex.color;
  4317. const borderDash = optsAtIndexBorder.dash || [];
  4318. const borderDashOffset = optsAtIndexBorder.dashOffset;
  4319. const tickWidth = optsAtIndex.tickWidth;
  4320. const tickColor = optsAtIndex.tickColor;
  4321. const tickBorderDash = optsAtIndex.tickBorderDash || [];
  4322. const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
  4323. lineValue = getPixelForGridLine(this, i, offset);
  4324. if (lineValue === undefined) {
  4325. continue;
  4326. }
  4327. alignedLineValue = helpers_segment._alignPixel(chart, lineValue, lineWidth);
  4328. if (isHorizontal) {
  4329. tx1 = tx2 = x1 = x2 = alignedLineValue;
  4330. } else {
  4331. ty1 = ty2 = y1 = y2 = alignedLineValue;
  4332. }
  4333. items.push({
  4334. tx1,
  4335. ty1,
  4336. tx2,
  4337. ty2,
  4338. x1,
  4339. y1,
  4340. x2,
  4341. y2,
  4342. width: lineWidth,
  4343. color: lineColor,
  4344. borderDash,
  4345. borderDashOffset,
  4346. tickWidth,
  4347. tickColor,
  4348. tickBorderDash,
  4349. tickBorderDashOffset
  4350. });
  4351. }
  4352. this._ticksLength = ticksLength;
  4353. this._borderValue = borderValue;
  4354. return items;
  4355. }
  4356. _computeLabelItems(chartArea) {
  4357. const axis = this.axis;
  4358. const options = this.options;
  4359. const { position , ticks: optionTicks } = options;
  4360. const isHorizontal = this.isHorizontal();
  4361. const ticks = this.ticks;
  4362. const { align , crossAlign , padding , mirror } = optionTicks;
  4363. const tl = getTickMarkLength(options.grid);
  4364. const tickAndPadding = tl + padding;
  4365. const hTickAndPadding = mirror ? -padding : tickAndPadding;
  4366. const rotation = -helpers_segment.toRadians(this.labelRotation);
  4367. const items = [];
  4368. let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
  4369. let textBaseline = 'middle';
  4370. if (position === 'top') {
  4371. y = this.bottom - hTickAndPadding;
  4372. textAlign = this._getXAxisLabelAlignment();
  4373. } else if (position === 'bottom') {
  4374. y = this.top + hTickAndPadding;
  4375. textAlign = this._getXAxisLabelAlignment();
  4376. } else if (position === 'left') {
  4377. const ret = this._getYAxisLabelAlignment(tl);
  4378. textAlign = ret.textAlign;
  4379. x = ret.x;
  4380. } else if (position === 'right') {
  4381. const ret1 = this._getYAxisLabelAlignment(tl);
  4382. textAlign = ret1.textAlign;
  4383. x = ret1.x;
  4384. } else if (axis === 'x') {
  4385. if (position === 'center') {
  4386. y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
  4387. } else if (helpers_segment.isObject(position)) {
  4388. const positionAxisID = Object.keys(position)[0];
  4389. const value = position[positionAxisID];
  4390. y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
  4391. }
  4392. textAlign = this._getXAxisLabelAlignment();
  4393. } else if (axis === 'y') {
  4394. if (position === 'center') {
  4395. x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
  4396. } else if (helpers_segment.isObject(position)) {
  4397. const positionAxisID1 = Object.keys(position)[0];
  4398. const value1 = position[positionAxisID1];
  4399. x = this.chart.scales[positionAxisID1].getPixelForValue(value1);
  4400. }
  4401. textAlign = this._getYAxisLabelAlignment(tl).textAlign;
  4402. }
  4403. if (axis === 'y') {
  4404. if (align === 'start') {
  4405. textBaseline = 'top';
  4406. } else if (align === 'end') {
  4407. textBaseline = 'bottom';
  4408. }
  4409. }
  4410. const labelSizes = this._getLabelSizes();
  4411. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  4412. tick = ticks[i];
  4413. label = tick.label;
  4414. const optsAtIndex = optionTicks.setContext(this.getContext(i));
  4415. pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
  4416. font = this._resolveTickFontOptions(i);
  4417. lineHeight = font.lineHeight;
  4418. lineCount = helpers_segment.isArray(label) ? label.length : 1;
  4419. const halfCount = lineCount / 2;
  4420. const color = optsAtIndex.color;
  4421. const strokeColor = optsAtIndex.textStrokeColor;
  4422. const strokeWidth = optsAtIndex.textStrokeWidth;
  4423. let tickTextAlign = textAlign;
  4424. if (isHorizontal) {
  4425. x = pixel;
  4426. if (textAlign === 'inner') {
  4427. if (i === ilen - 1) {
  4428. tickTextAlign = !this.options.reverse ? 'right' : 'left';
  4429. } else if (i === 0) {
  4430. tickTextAlign = !this.options.reverse ? 'left' : 'right';
  4431. } else {
  4432. tickTextAlign = 'center';
  4433. }
  4434. }
  4435. if (position === 'top') {
  4436. if (crossAlign === 'near' || rotation !== 0) {
  4437. textOffset = -lineCount * lineHeight + lineHeight / 2;
  4438. } else if (crossAlign === 'center') {
  4439. textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
  4440. } else {
  4441. textOffset = -labelSizes.highest.height + lineHeight / 2;
  4442. }
  4443. } else {
  4444. if (crossAlign === 'near' || rotation !== 0) {
  4445. textOffset = lineHeight / 2;
  4446. } else if (crossAlign === 'center') {
  4447. textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
  4448. } else {
  4449. textOffset = labelSizes.highest.height - lineCount * lineHeight;
  4450. }
  4451. }
  4452. if (mirror) {
  4453. textOffset *= -1;
  4454. }
  4455. if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
  4456. x += lineHeight / 2 * Math.sin(rotation);
  4457. }
  4458. } else {
  4459. y = pixel;
  4460. textOffset = (1 - lineCount) * lineHeight / 2;
  4461. }
  4462. let backdrop;
  4463. if (optsAtIndex.showLabelBackdrop) {
  4464. const labelPadding = helpers_segment.toPadding(optsAtIndex.backdropPadding);
  4465. const height = labelSizes.heights[i];
  4466. const width = labelSizes.widths[i];
  4467. let top = textOffset - labelPadding.top;
  4468. let left = 0 - labelPadding.left;
  4469. switch(textBaseline){
  4470. case 'middle':
  4471. top -= height / 2;
  4472. break;
  4473. case 'bottom':
  4474. top -= height;
  4475. break;
  4476. }
  4477. switch(textAlign){
  4478. case 'center':
  4479. left -= width / 2;
  4480. break;
  4481. case 'right':
  4482. left -= width;
  4483. break;
  4484. }
  4485. backdrop = {
  4486. left,
  4487. top,
  4488. width: width + labelPadding.width,
  4489. height: height + labelPadding.height,
  4490. color: optsAtIndex.backdropColor
  4491. };
  4492. }
  4493. items.push({
  4494. label,
  4495. font,
  4496. textOffset,
  4497. options: {
  4498. rotation,
  4499. color,
  4500. strokeColor,
  4501. strokeWidth,
  4502. textAlign: tickTextAlign,
  4503. textBaseline,
  4504. translation: [
  4505. x,
  4506. y
  4507. ],
  4508. backdrop
  4509. }
  4510. });
  4511. }
  4512. return items;
  4513. }
  4514. _getXAxisLabelAlignment() {
  4515. const { position , ticks } = this.options;
  4516. const rotation = -helpers_segment.toRadians(this.labelRotation);
  4517. if (rotation) {
  4518. return position === 'top' ? 'left' : 'right';
  4519. }
  4520. let align = 'center';
  4521. if (ticks.align === 'start') {
  4522. align = 'left';
  4523. } else if (ticks.align === 'end') {
  4524. align = 'right';
  4525. } else if (ticks.align === 'inner') {
  4526. align = 'inner';
  4527. }
  4528. return align;
  4529. }
  4530. _getYAxisLabelAlignment(tl) {
  4531. const { position , ticks: { crossAlign , mirror , padding } } = this.options;
  4532. const labelSizes = this._getLabelSizes();
  4533. const tickAndPadding = tl + padding;
  4534. const widest = labelSizes.widest.width;
  4535. let textAlign;
  4536. let x;
  4537. if (position === 'left') {
  4538. if (mirror) {
  4539. x = this.right + padding;
  4540. if (crossAlign === 'near') {
  4541. textAlign = 'left';
  4542. } else if (crossAlign === 'center') {
  4543. textAlign = 'center';
  4544. x += widest / 2;
  4545. } else {
  4546. textAlign = 'right';
  4547. x += widest;
  4548. }
  4549. } else {
  4550. x = this.right - tickAndPadding;
  4551. if (crossAlign === 'near') {
  4552. textAlign = 'right';
  4553. } else if (crossAlign === 'center') {
  4554. textAlign = 'center';
  4555. x -= widest / 2;
  4556. } else {
  4557. textAlign = 'left';
  4558. x = this.left;
  4559. }
  4560. }
  4561. } else if (position === 'right') {
  4562. if (mirror) {
  4563. x = this.left + padding;
  4564. if (crossAlign === 'near') {
  4565. textAlign = 'right';
  4566. } else if (crossAlign === 'center') {
  4567. textAlign = 'center';
  4568. x -= widest / 2;
  4569. } else {
  4570. textAlign = 'left';
  4571. x -= widest;
  4572. }
  4573. } else {
  4574. x = this.left + tickAndPadding;
  4575. if (crossAlign === 'near') {
  4576. textAlign = 'left';
  4577. } else if (crossAlign === 'center') {
  4578. textAlign = 'center';
  4579. x += widest / 2;
  4580. } else {
  4581. textAlign = 'right';
  4582. x = this.right;
  4583. }
  4584. }
  4585. } else {
  4586. textAlign = 'right';
  4587. }
  4588. return {
  4589. textAlign,
  4590. x
  4591. };
  4592. }
  4593. _computeLabelArea() {
  4594. if (this.options.ticks.mirror) {
  4595. return;
  4596. }
  4597. const chart = this.chart;
  4598. const position = this.options.position;
  4599. if (position === 'left' || position === 'right') {
  4600. return {
  4601. top: 0,
  4602. left: this.left,
  4603. bottom: chart.height,
  4604. right: this.right
  4605. };
  4606. }
  4607. if (position === 'top' || position === 'bottom') {
  4608. return {
  4609. top: this.top,
  4610. left: 0,
  4611. bottom: this.bottom,
  4612. right: chart.width
  4613. };
  4614. }
  4615. }
  4616. drawBackground() {
  4617. const { ctx , options: { backgroundColor } , left , top , width , height } = this;
  4618. if (backgroundColor) {
  4619. ctx.save();
  4620. ctx.fillStyle = backgroundColor;
  4621. ctx.fillRect(left, top, width, height);
  4622. ctx.restore();
  4623. }
  4624. }
  4625. getLineWidthForValue(value) {
  4626. const grid = this.options.grid;
  4627. if (!this._isVisible() || !grid.display) {
  4628. return 0;
  4629. }
  4630. const ticks = this.ticks;
  4631. const index = ticks.findIndex((t)=>t.value === value);
  4632. if (index >= 0) {
  4633. const opts = grid.setContext(this.getContext(index));
  4634. return opts.lineWidth;
  4635. }
  4636. return 0;
  4637. }
  4638. drawGrid(chartArea) {
  4639. const grid = this.options.grid;
  4640. const ctx = this.ctx;
  4641. const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
  4642. let i, ilen;
  4643. const drawLine = (p1, p2, style)=>{
  4644. if (!style.width || !style.color) {
  4645. return;
  4646. }
  4647. ctx.save();
  4648. ctx.lineWidth = style.width;
  4649. ctx.strokeStyle = style.color;
  4650. ctx.setLineDash(style.borderDash || []);
  4651. ctx.lineDashOffset = style.borderDashOffset;
  4652. ctx.beginPath();
  4653. ctx.moveTo(p1.x, p1.y);
  4654. ctx.lineTo(p2.x, p2.y);
  4655. ctx.stroke();
  4656. ctx.restore();
  4657. };
  4658. if (grid.display) {
  4659. for(i = 0, ilen = items.length; i < ilen; ++i){
  4660. const item = items[i];
  4661. if (grid.drawOnChartArea) {
  4662. drawLine({
  4663. x: item.x1,
  4664. y: item.y1
  4665. }, {
  4666. x: item.x2,
  4667. y: item.y2
  4668. }, item);
  4669. }
  4670. if (grid.drawTicks) {
  4671. drawLine({
  4672. x: item.tx1,
  4673. y: item.ty1
  4674. }, {
  4675. x: item.tx2,
  4676. y: item.ty2
  4677. }, {
  4678. color: item.tickColor,
  4679. width: item.tickWidth,
  4680. borderDash: item.tickBorderDash,
  4681. borderDashOffset: item.tickBorderDashOffset
  4682. });
  4683. }
  4684. }
  4685. }
  4686. }
  4687. drawBorder() {
  4688. const { chart , ctx , options: { border , grid } } = this;
  4689. const borderOpts = border.setContext(this.getContext());
  4690. const axisWidth = border.display ? borderOpts.width : 0;
  4691. if (!axisWidth) {
  4692. return;
  4693. }
  4694. const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
  4695. const borderValue = this._borderValue;
  4696. let x1, x2, y1, y2;
  4697. if (this.isHorizontal()) {
  4698. x1 = helpers_segment._alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
  4699. x2 = helpers_segment._alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
  4700. y1 = y2 = borderValue;
  4701. } else {
  4702. y1 = helpers_segment._alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
  4703. y2 = helpers_segment._alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
  4704. x1 = x2 = borderValue;
  4705. }
  4706. ctx.save();
  4707. ctx.lineWidth = borderOpts.width;
  4708. ctx.strokeStyle = borderOpts.color;
  4709. ctx.beginPath();
  4710. ctx.moveTo(x1, y1);
  4711. ctx.lineTo(x2, y2);
  4712. ctx.stroke();
  4713. ctx.restore();
  4714. }
  4715. drawLabels(chartArea) {
  4716. const optionTicks = this.options.ticks;
  4717. if (!optionTicks.display) {
  4718. return;
  4719. }
  4720. const ctx = this.ctx;
  4721. const area = this._computeLabelArea();
  4722. if (area) {
  4723. helpers_segment.clipArea(ctx, area);
  4724. }
  4725. const items = this.getLabelItems(chartArea);
  4726. for (const item of items){
  4727. const renderTextOptions = item.options;
  4728. const tickFont = item.font;
  4729. const label = item.label;
  4730. const y = item.textOffset;
  4731. helpers_segment.renderText(ctx, label, 0, y, tickFont, renderTextOptions);
  4732. }
  4733. if (area) {
  4734. helpers_segment.unclipArea(ctx);
  4735. }
  4736. }
  4737. drawTitle() {
  4738. const { ctx , options: { position , title , reverse } } = this;
  4739. if (!title.display) {
  4740. return;
  4741. }
  4742. const font = helpers_segment.toFont(title.font);
  4743. const padding = helpers_segment.toPadding(title.padding);
  4744. const align = title.align;
  4745. let offset = font.lineHeight / 2;
  4746. if (position === 'bottom' || position === 'center' || helpers_segment.isObject(position)) {
  4747. offset += padding.bottom;
  4748. if (helpers_segment.isArray(title.text)) {
  4749. offset += font.lineHeight * (title.text.length - 1);
  4750. }
  4751. } else {
  4752. offset += padding.top;
  4753. }
  4754. const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
  4755. helpers_segment.renderText(ctx, title.text, 0, 0, font, {
  4756. color: title.color,
  4757. maxWidth,
  4758. rotation,
  4759. textAlign: titleAlign(align, position, reverse),
  4760. textBaseline: 'middle',
  4761. translation: [
  4762. titleX,
  4763. titleY
  4764. ]
  4765. });
  4766. }
  4767. draw(chartArea) {
  4768. if (!this._isVisible()) {
  4769. return;
  4770. }
  4771. this.drawBackground();
  4772. this.drawGrid(chartArea);
  4773. this.drawBorder();
  4774. this.drawTitle();
  4775. this.drawLabels(chartArea);
  4776. }
  4777. _layers() {
  4778. const opts = this.options;
  4779. const tz = opts.ticks && opts.ticks.z || 0;
  4780. const gz = helpers_segment.valueOrDefault(opts.grid && opts.grid.z, -1);
  4781. const bz = helpers_segment.valueOrDefault(opts.border && opts.border.z, 0);
  4782. if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
  4783. return [
  4784. {
  4785. z: tz,
  4786. draw: (chartArea)=>{
  4787. this.draw(chartArea);
  4788. }
  4789. }
  4790. ];
  4791. }
  4792. return [
  4793. {
  4794. z: gz,
  4795. draw: (chartArea)=>{
  4796. this.drawBackground();
  4797. this.drawGrid(chartArea);
  4798. this.drawTitle();
  4799. }
  4800. },
  4801. {
  4802. z: bz,
  4803. draw: ()=>{
  4804. this.drawBorder();
  4805. }
  4806. },
  4807. {
  4808. z: tz,
  4809. draw: (chartArea)=>{
  4810. this.drawLabels(chartArea);
  4811. }
  4812. }
  4813. ];
  4814. }
  4815. getMatchingVisibleMetas(type) {
  4816. const metas = this.chart.getSortedVisibleDatasetMetas();
  4817. const axisID = this.axis + 'AxisID';
  4818. const result = [];
  4819. let i, ilen;
  4820. for(i = 0, ilen = metas.length; i < ilen; ++i){
  4821. const meta = metas[i];
  4822. if (meta[axisID] === this.id && (!type || meta.type === type)) {
  4823. result.push(meta);
  4824. }
  4825. }
  4826. return result;
  4827. }
  4828. _resolveTickFontOptions(index) {
  4829. const opts = this.options.ticks.setContext(this.getContext(index));
  4830. return helpers_segment.toFont(opts.font);
  4831. }
  4832. _maxDigits() {
  4833. const fontSize = this._resolveTickFontOptions(0).lineHeight;
  4834. return (this.isHorizontal() ? this.width : this.height) / fontSize;
  4835. }
  4836. }
  4837. class TypedRegistry {
  4838. constructor(type, scope, override){
  4839. this.type = type;
  4840. this.scope = scope;
  4841. this.override = override;
  4842. this.items = Object.create(null);
  4843. }
  4844. isForType(type) {
  4845. return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
  4846. }
  4847. register(item) {
  4848. const proto = Object.getPrototypeOf(item);
  4849. let parentScope;
  4850. if (isIChartComponent(proto)) {
  4851. parentScope = this.register(proto);
  4852. }
  4853. const items = this.items;
  4854. const id = item.id;
  4855. const scope = this.scope + '.' + id;
  4856. if (!id) {
  4857. throw new Error('class does not have id: ' + item);
  4858. }
  4859. if (id in items) {
  4860. return scope;
  4861. }
  4862. items[id] = item;
  4863. registerDefaults(item, scope, parentScope);
  4864. if (this.override) {
  4865. helpers_segment.defaults.override(item.id, item.overrides);
  4866. }
  4867. return scope;
  4868. }
  4869. get(id) {
  4870. return this.items[id];
  4871. }
  4872. unregister(item) {
  4873. const items = this.items;
  4874. const id = item.id;
  4875. const scope = this.scope;
  4876. if (id in items) {
  4877. delete items[id];
  4878. }
  4879. if (scope && id in helpers_segment.defaults[scope]) {
  4880. delete helpers_segment.defaults[scope][id];
  4881. if (this.override) {
  4882. delete helpers_segment.overrides[id];
  4883. }
  4884. }
  4885. }
  4886. }
  4887. function registerDefaults(item, scope, parentScope) {
  4888. const itemDefaults = helpers_segment.merge(Object.create(null), [
  4889. parentScope ? helpers_segment.defaults.get(parentScope) : {},
  4890. helpers_segment.defaults.get(scope),
  4891. item.defaults
  4892. ]);
  4893. helpers_segment.defaults.set(scope, itemDefaults);
  4894. if (item.defaultRoutes) {
  4895. routeDefaults(scope, item.defaultRoutes);
  4896. }
  4897. if (item.descriptors) {
  4898. helpers_segment.defaults.describe(scope, item.descriptors);
  4899. }
  4900. }
  4901. function routeDefaults(scope, routes) {
  4902. Object.keys(routes).forEach((property)=>{
  4903. const propertyParts = property.split('.');
  4904. const sourceName = propertyParts.pop();
  4905. const sourceScope = [
  4906. scope
  4907. ].concat(propertyParts).join('.');
  4908. const parts = routes[property].split('.');
  4909. const targetName = parts.pop();
  4910. const targetScope = parts.join('.');
  4911. helpers_segment.defaults.route(sourceScope, sourceName, targetScope, targetName);
  4912. });
  4913. }
  4914. function isIChartComponent(proto) {
  4915. return 'id' in proto && 'defaults' in proto;
  4916. }
  4917. class Registry {
  4918. constructor(){
  4919. this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
  4920. this.elements = new TypedRegistry(Element, 'elements');
  4921. this.plugins = new TypedRegistry(Object, 'plugins');
  4922. this.scales = new TypedRegistry(Scale, 'scales');
  4923. this._typedRegistries = [
  4924. this.controllers,
  4925. this.scales,
  4926. this.elements
  4927. ];
  4928. }
  4929. add(...args) {
  4930. this._each('register', args);
  4931. }
  4932. remove(...args) {
  4933. this._each('unregister', args);
  4934. }
  4935. addControllers(...args) {
  4936. this._each('register', args, this.controllers);
  4937. }
  4938. addElements(...args) {
  4939. this._each('register', args, this.elements);
  4940. }
  4941. addPlugins(...args) {
  4942. this._each('register', args, this.plugins);
  4943. }
  4944. addScales(...args) {
  4945. this._each('register', args, this.scales);
  4946. }
  4947. getController(id) {
  4948. return this._get(id, this.controllers, 'controller');
  4949. }
  4950. getElement(id) {
  4951. return this._get(id, this.elements, 'element');
  4952. }
  4953. getPlugin(id) {
  4954. return this._get(id, this.plugins, 'plugin');
  4955. }
  4956. getScale(id) {
  4957. return this._get(id, this.scales, 'scale');
  4958. }
  4959. removeControllers(...args) {
  4960. this._each('unregister', args, this.controllers);
  4961. }
  4962. removeElements(...args) {
  4963. this._each('unregister', args, this.elements);
  4964. }
  4965. removePlugins(...args) {
  4966. this._each('unregister', args, this.plugins);
  4967. }
  4968. removeScales(...args) {
  4969. this._each('unregister', args, this.scales);
  4970. }
  4971. _each(method, args, typedRegistry) {
  4972. [
  4973. ...args
  4974. ].forEach((arg)=>{
  4975. const reg = typedRegistry || this._getRegistryForType(arg);
  4976. if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
  4977. this._exec(method, reg, arg);
  4978. } else {
  4979. helpers_segment.each(arg, (item)=>{
  4980. const itemReg = typedRegistry || this._getRegistryForType(item);
  4981. this._exec(method, itemReg, item);
  4982. });
  4983. }
  4984. });
  4985. }
  4986. _exec(method, registry, component) {
  4987. const camelMethod = helpers_segment._capitalize(method);
  4988. helpers_segment.callback(component['before' + camelMethod], [], component);
  4989. registry[method](component);
  4990. helpers_segment.callback(component['after' + camelMethod], [], component);
  4991. }
  4992. _getRegistryForType(type) {
  4993. for(let i = 0; i < this._typedRegistries.length; i++){
  4994. const reg = this._typedRegistries[i];
  4995. if (reg.isForType(type)) {
  4996. return reg;
  4997. }
  4998. }
  4999. return this.plugins;
  5000. }
  5001. _get(id, typedRegistry, type) {
  5002. const item = typedRegistry.get(id);
  5003. if (item === undefined) {
  5004. throw new Error('"' + id + '" is not a registered ' + type + '.');
  5005. }
  5006. return item;
  5007. }
  5008. }
  5009. var registry = /* #__PURE__ */ new Registry();
  5010. class PluginService {
  5011. constructor(){
  5012. this._init = [];
  5013. }
  5014. notify(chart, hook, args, filter) {
  5015. if (hook === 'beforeInit') {
  5016. this._init = this._createDescriptors(chart, true);
  5017. this._notify(this._init, chart, 'install');
  5018. }
  5019. const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
  5020. const result = this._notify(descriptors, chart, hook, args);
  5021. if (hook === 'afterDestroy') {
  5022. this._notify(descriptors, chart, 'stop');
  5023. this._notify(this._init, chart, 'uninstall');
  5024. }
  5025. return result;
  5026. }
  5027. _notify(descriptors, chart, hook, args) {
  5028. args = args || {};
  5029. for (const descriptor of descriptors){
  5030. const plugin = descriptor.plugin;
  5031. const method = plugin[hook];
  5032. const params = [
  5033. chart,
  5034. args,
  5035. descriptor.options
  5036. ];
  5037. if (helpers_segment.callback(method, params, plugin) === false && args.cancelable) {
  5038. return false;
  5039. }
  5040. }
  5041. return true;
  5042. }
  5043. invalidate() {
  5044. if (!helpers_segment.isNullOrUndef(this._cache)) {
  5045. this._oldCache = this._cache;
  5046. this._cache = undefined;
  5047. }
  5048. }
  5049. _descriptors(chart) {
  5050. if (this._cache) {
  5051. return this._cache;
  5052. }
  5053. const descriptors = this._cache = this._createDescriptors(chart);
  5054. this._notifyStateChanges(chart);
  5055. return descriptors;
  5056. }
  5057. _createDescriptors(chart, all) {
  5058. const config = chart && chart.config;
  5059. const options = helpers_segment.valueOrDefault(config.options && config.options.plugins, {});
  5060. const plugins = allPlugins(config);
  5061. return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
  5062. }
  5063. _notifyStateChanges(chart) {
  5064. const previousDescriptors = this._oldCache || [];
  5065. const descriptors = this._cache;
  5066. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
  5067. this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
  5068. this._notify(diff(descriptors, previousDescriptors), chart, 'start');
  5069. }
  5070. }
  5071. function allPlugins(config) {
  5072. const localIds = {};
  5073. const plugins = [];
  5074. const keys = Object.keys(registry.plugins.items);
  5075. for(let i = 0; i < keys.length; i++){
  5076. plugins.push(registry.getPlugin(keys[i]));
  5077. }
  5078. const local = config.plugins || [];
  5079. for(let i1 = 0; i1 < local.length; i1++){
  5080. const plugin = local[i1];
  5081. if (plugins.indexOf(plugin) === -1) {
  5082. plugins.push(plugin);
  5083. localIds[plugin.id] = true;
  5084. }
  5085. }
  5086. return {
  5087. plugins,
  5088. localIds
  5089. };
  5090. }
  5091. function getOpts(options, all) {
  5092. if (!all && options === false) {
  5093. return null;
  5094. }
  5095. if (options === true) {
  5096. return {};
  5097. }
  5098. return options;
  5099. }
  5100. function createDescriptors(chart, { plugins , localIds }, options, all) {
  5101. const result = [];
  5102. const context = chart.getContext();
  5103. for (const plugin of plugins){
  5104. const id = plugin.id;
  5105. const opts = getOpts(options[id], all);
  5106. if (opts === null) {
  5107. continue;
  5108. }
  5109. result.push({
  5110. plugin,
  5111. options: pluginOpts(chart.config, {
  5112. plugin,
  5113. local: localIds[id]
  5114. }, opts, context)
  5115. });
  5116. }
  5117. return result;
  5118. }
  5119. function pluginOpts(config, { plugin , local }, opts, context) {
  5120. const keys = config.pluginScopeKeys(plugin);
  5121. const scopes = config.getOptionScopes(opts, keys);
  5122. if (local && plugin.defaults) {
  5123. scopes.push(plugin.defaults);
  5124. }
  5125. return config.createResolver(scopes, context, [
  5126. ''
  5127. ], {
  5128. scriptable: false,
  5129. indexable: false,
  5130. allKeys: true
  5131. });
  5132. }
  5133. function getIndexAxis(type, options) {
  5134. const datasetDefaults = helpers_segment.defaults.datasets[type] || {};
  5135. const datasetOptions = (options.datasets || {})[type] || {};
  5136. return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  5137. }
  5138. function getAxisFromDefaultScaleID(id, indexAxis) {
  5139. let axis = id;
  5140. if (id === '_index_') {
  5141. axis = indexAxis;
  5142. } else if (id === '_value_') {
  5143. axis = indexAxis === 'x' ? 'y' : 'x';
  5144. }
  5145. return axis;
  5146. }
  5147. function getDefaultScaleIDFromAxis(axis, indexAxis) {
  5148. return axis === indexAxis ? '_index_' : '_value_';
  5149. }
  5150. function axisFromPosition(position) {
  5151. if (position === 'top' || position === 'bottom') {
  5152. return 'x';
  5153. }
  5154. if (position === 'left' || position === 'right') {
  5155. return 'y';
  5156. }
  5157. }
  5158. function determineAxis(id, scaleOptions) {
  5159. if (id === 'x' || id === 'y' || id === 'r') {
  5160. return id;
  5161. }
  5162. id = scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.length > 1 && determineAxis(id[0].toLowerCase(), scaleOptions);
  5163. if (id) {
  5164. return id;
  5165. }
  5166. throw new Error(`Cannot determine type of '${name}' axis. Please provide 'axis' or 'position' option.`);
  5167. }
  5168. function mergeScaleConfig(config, options) {
  5169. const chartDefaults = helpers_segment.overrides[config.type] || {
  5170. scales: {}
  5171. };
  5172. const configScales = options.scales || {};
  5173. const chartIndexAxis = getIndexAxis(config.type, options);
  5174. const scales = Object.create(null);
  5175. Object.keys(configScales).forEach((id)=>{
  5176. const scaleConf = configScales[id];
  5177. if (!helpers_segment.isObject(scaleConf)) {
  5178. return console.error(`Invalid scale configuration for scale: ${id}`);
  5179. }
  5180. if (scaleConf._proxy) {
  5181. return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
  5182. }
  5183. const axis = determineAxis(id, scaleConf);
  5184. const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
  5185. const defaultScaleOptions = chartDefaults.scales || {};
  5186. scales[id] = helpers_segment.mergeIf(Object.create(null), [
  5187. {
  5188. axis
  5189. },
  5190. scaleConf,
  5191. defaultScaleOptions[axis],
  5192. defaultScaleOptions[defaultId]
  5193. ]);
  5194. });
  5195. config.data.datasets.forEach((dataset)=>{
  5196. const type = dataset.type || config.type;
  5197. const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
  5198. const datasetDefaults = helpers_segment.overrides[type] || {};
  5199. const defaultScaleOptions = datasetDefaults.scales || {};
  5200. Object.keys(defaultScaleOptions).forEach((defaultID)=>{
  5201. const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
  5202. const id = dataset[axis + 'AxisID'] || axis;
  5203. scales[id] = scales[id] || Object.create(null);
  5204. helpers_segment.mergeIf(scales[id], [
  5205. {
  5206. axis
  5207. },
  5208. configScales[id],
  5209. defaultScaleOptions[defaultID]
  5210. ]);
  5211. });
  5212. });
  5213. Object.keys(scales).forEach((key)=>{
  5214. const scale = scales[key];
  5215. helpers_segment.mergeIf(scale, [
  5216. helpers_segment.defaults.scales[scale.type],
  5217. helpers_segment.defaults.scale
  5218. ]);
  5219. });
  5220. return scales;
  5221. }
  5222. function initOptions(config) {
  5223. const options = config.options || (config.options = {});
  5224. options.plugins = helpers_segment.valueOrDefault(options.plugins, {});
  5225. options.scales = mergeScaleConfig(config, options);
  5226. }
  5227. function initData(data) {
  5228. data = data || {};
  5229. data.datasets = data.datasets || [];
  5230. data.labels = data.labels || [];
  5231. return data;
  5232. }
  5233. function initConfig(config) {
  5234. config = config || {};
  5235. config.data = initData(config.data);
  5236. initOptions(config);
  5237. return config;
  5238. }
  5239. const keyCache = new Map();
  5240. const keysCached = new Set();
  5241. function cachedKeys(cacheKey, generate) {
  5242. let keys = keyCache.get(cacheKey);
  5243. if (!keys) {
  5244. keys = generate();
  5245. keyCache.set(cacheKey, keys);
  5246. keysCached.add(keys);
  5247. }
  5248. return keys;
  5249. }
  5250. const addIfFound = (set, obj, key)=>{
  5251. const opts = helpers_segment.resolveObjectKey(obj, key);
  5252. if (opts !== undefined) {
  5253. set.add(opts);
  5254. }
  5255. };
  5256. class Config {
  5257. constructor(config){
  5258. this._config = initConfig(config);
  5259. this._scopeCache = new Map();
  5260. this._resolverCache = new Map();
  5261. }
  5262. get platform() {
  5263. return this._config.platform;
  5264. }
  5265. get type() {
  5266. return this._config.type;
  5267. }
  5268. set type(type) {
  5269. this._config.type = type;
  5270. }
  5271. get data() {
  5272. return this._config.data;
  5273. }
  5274. set data(data) {
  5275. this._config.data = initData(data);
  5276. }
  5277. get options() {
  5278. return this._config.options;
  5279. }
  5280. set options(options) {
  5281. this._config.options = options;
  5282. }
  5283. get plugins() {
  5284. return this._config.plugins;
  5285. }
  5286. update() {
  5287. const config = this._config;
  5288. this.clearCache();
  5289. initOptions(config);
  5290. }
  5291. clearCache() {
  5292. this._scopeCache.clear();
  5293. this._resolverCache.clear();
  5294. }
  5295. datasetScopeKeys(datasetType) {
  5296. return cachedKeys(datasetType, ()=>[
  5297. [
  5298. `datasets.${datasetType}`,
  5299. ''
  5300. ]
  5301. ]);
  5302. }
  5303. datasetAnimationScopeKeys(datasetType, transition) {
  5304. return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
  5305. [
  5306. `datasets.${datasetType}.transitions.${transition}`,
  5307. `transitions.${transition}`
  5308. ],
  5309. [
  5310. `datasets.${datasetType}`,
  5311. ''
  5312. ]
  5313. ]);
  5314. }
  5315. datasetElementScopeKeys(datasetType, elementType) {
  5316. return cachedKeys(`${datasetType}-${elementType}`, ()=>[
  5317. [
  5318. `datasets.${datasetType}.elements.${elementType}`,
  5319. `datasets.${datasetType}`,
  5320. `elements.${elementType}`,
  5321. ''
  5322. ]
  5323. ]);
  5324. }
  5325. pluginScopeKeys(plugin) {
  5326. const id = plugin.id;
  5327. const type = this.type;
  5328. return cachedKeys(`${type}-plugin-${id}`, ()=>[
  5329. [
  5330. `plugins.${id}`,
  5331. ...plugin.additionalOptionScopes || []
  5332. ]
  5333. ]);
  5334. }
  5335. _cachedScopes(mainScope, resetCache) {
  5336. const _scopeCache = this._scopeCache;
  5337. let cache = _scopeCache.get(mainScope);
  5338. if (!cache || resetCache) {
  5339. cache = new Map();
  5340. _scopeCache.set(mainScope, cache);
  5341. }
  5342. return cache;
  5343. }
  5344. getOptionScopes(mainScope, keyLists, resetCache) {
  5345. const { options , type } = this;
  5346. const cache = this._cachedScopes(mainScope, resetCache);
  5347. const cached = cache.get(keyLists);
  5348. if (cached) {
  5349. return cached;
  5350. }
  5351. const scopes = new Set();
  5352. keyLists.forEach((keys)=>{
  5353. if (mainScope) {
  5354. scopes.add(mainScope);
  5355. keys.forEach((key)=>addIfFound(scopes, mainScope, key));
  5356. }
  5357. keys.forEach((key)=>addIfFound(scopes, options, key));
  5358. keys.forEach((key)=>addIfFound(scopes, helpers_segment.overrides[type] || {}, key));
  5359. keys.forEach((key)=>addIfFound(scopes, helpers_segment.defaults, key));
  5360. keys.forEach((key)=>addIfFound(scopes, helpers_segment.descriptors, key));
  5361. });
  5362. const array = Array.from(scopes);
  5363. if (array.length === 0) {
  5364. array.push(Object.create(null));
  5365. }
  5366. if (keysCached.has(keyLists)) {
  5367. cache.set(keyLists, array);
  5368. }
  5369. return array;
  5370. }
  5371. chartOptionScopes() {
  5372. const { options , type } = this;
  5373. return [
  5374. options,
  5375. helpers_segment.overrides[type] || {},
  5376. helpers_segment.defaults.datasets[type] || {},
  5377. {
  5378. type
  5379. },
  5380. helpers_segment.defaults,
  5381. helpers_segment.descriptors
  5382. ];
  5383. }
  5384. resolveNamedOptions(scopes, names, context, prefixes = [
  5385. ''
  5386. ]) {
  5387. const result = {
  5388. $shared: true
  5389. };
  5390. const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
  5391. let options = resolver;
  5392. if (needContext(resolver, names)) {
  5393. result.$shared = false;
  5394. context = helpers_segment.isFunction(context) ? context() : context;
  5395. const subResolver = this.createResolver(scopes, context, subPrefixes);
  5396. options = helpers_segment._attachContext(resolver, context, subResolver);
  5397. }
  5398. for (const prop of names){
  5399. result[prop] = options[prop];
  5400. }
  5401. return result;
  5402. }
  5403. createResolver(scopes, context, prefixes = [
  5404. ''
  5405. ], descriptorDefaults) {
  5406. const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
  5407. return helpers_segment.isObject(context) ? helpers_segment._attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
  5408. }
  5409. }
  5410. function getResolver(resolverCache, scopes, prefixes) {
  5411. let cache = resolverCache.get(scopes);
  5412. if (!cache) {
  5413. cache = new Map();
  5414. resolverCache.set(scopes, cache);
  5415. }
  5416. const cacheKey = prefixes.join();
  5417. let cached = cache.get(cacheKey);
  5418. if (!cached) {
  5419. const resolver = helpers_segment._createResolver(scopes, prefixes);
  5420. cached = {
  5421. resolver,
  5422. subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
  5423. };
  5424. cache.set(cacheKey, cached);
  5425. }
  5426. return cached;
  5427. }
  5428. const hasFunction = (value)=>helpers_segment.isObject(value) && Object.getOwnPropertyNames(value).reduce((acc, key)=>acc || helpers_segment.isFunction(value[key]), false);
  5429. function needContext(proxy, names) {
  5430. const { isScriptable , isIndexable } = helpers_segment._descriptors(proxy);
  5431. for (const prop of names){
  5432. const scriptable = isScriptable(prop);
  5433. const indexable = isIndexable(prop);
  5434. const value = (indexable || scriptable) && proxy[prop];
  5435. if (scriptable && (helpers_segment.isFunction(value) || hasFunction(value)) || indexable && helpers_segment.isArray(value)) {
  5436. return true;
  5437. }
  5438. }
  5439. return false;
  5440. }
  5441. var version = "4.2.1";
  5442. const KNOWN_POSITIONS = [
  5443. 'top',
  5444. 'bottom',
  5445. 'left',
  5446. 'right',
  5447. 'chartArea'
  5448. ];
  5449. function positionIsHorizontal(position, axis) {
  5450. return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
  5451. }
  5452. function compare2Level(l1, l2) {
  5453. return function(a, b) {
  5454. return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
  5455. };
  5456. }
  5457. function onAnimationsComplete(context) {
  5458. const chart = context.chart;
  5459. const animationOptions = chart.options.animation;
  5460. chart.notifyPlugins('afterRender');
  5461. helpers_segment.callback(animationOptions && animationOptions.onComplete, [
  5462. context
  5463. ], chart);
  5464. }
  5465. function onAnimationProgress(context) {
  5466. const chart = context.chart;
  5467. const animationOptions = chart.options.animation;
  5468. helpers_segment.callback(animationOptions && animationOptions.onProgress, [
  5469. context
  5470. ], chart);
  5471. }
  5472. function getCanvas(item) {
  5473. if (helpers_segment._isDomSupported() && typeof item === 'string') {
  5474. item = document.getElementById(item);
  5475. } else if (item && item.length) {
  5476. item = item[0];
  5477. }
  5478. if (item && item.canvas) {
  5479. item = item.canvas;
  5480. }
  5481. return item;
  5482. }
  5483. const instances = {};
  5484. const getChart = (key)=>{
  5485. const canvas = getCanvas(key);
  5486. return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
  5487. };
  5488. function moveNumericKeys(obj, start, move) {
  5489. const keys = Object.keys(obj);
  5490. for (const key of keys){
  5491. const intKey = +key;
  5492. if (intKey >= start) {
  5493. const value = obj[key];
  5494. delete obj[key];
  5495. if (move > 0 || intKey > start) {
  5496. obj[intKey + move] = value;
  5497. }
  5498. }
  5499. }
  5500. }
  5501. function determineLastEvent(e, lastEvent, inChartArea, isClick) {
  5502. if (!inChartArea || e.type === 'mouseout') {
  5503. return null;
  5504. }
  5505. if (isClick) {
  5506. return lastEvent;
  5507. }
  5508. return e;
  5509. }
  5510. function getDatasetArea(meta) {
  5511. const { xScale , yScale } = meta;
  5512. if (xScale && yScale) {
  5513. return {
  5514. left: xScale.left,
  5515. right: xScale.right,
  5516. top: yScale.top,
  5517. bottom: yScale.bottom
  5518. };
  5519. }
  5520. }
  5521. class Chart {
  5522. static defaults = helpers_segment.defaults;
  5523. static instances = instances;
  5524. static overrides = helpers_segment.overrides;
  5525. static registry = registry;
  5526. static version = version;
  5527. static getChart = getChart;
  5528. static register(...items) {
  5529. registry.add(...items);
  5530. invalidatePlugins();
  5531. }
  5532. static unregister(...items) {
  5533. registry.remove(...items);
  5534. invalidatePlugins();
  5535. }
  5536. constructor(item, userConfig){
  5537. const config = this.config = new Config(userConfig);
  5538. const initialCanvas = getCanvas(item);
  5539. const existingChart = getChart(initialCanvas);
  5540. if (existingChart) {
  5541. throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
  5542. }
  5543. const options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5544. this.platform = new (config.platform || _detectPlatform(initialCanvas))();
  5545. this.platform.updateConfig(config);
  5546. const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
  5547. const canvas = context && context.canvas;
  5548. const height = canvas && canvas.height;
  5549. const width = canvas && canvas.width;
  5550. this.id = helpers_segment.uid();
  5551. this.ctx = context;
  5552. this.canvas = canvas;
  5553. this.width = width;
  5554. this.height = height;
  5555. this._options = options;
  5556. this._aspectRatio = this.aspectRatio;
  5557. this._layers = [];
  5558. this._metasets = [];
  5559. this._stacks = undefined;
  5560. this.boxes = [];
  5561. this.currentDevicePixelRatio = undefined;
  5562. this.chartArea = undefined;
  5563. this._active = [];
  5564. this._lastEvent = undefined;
  5565. this._listeners = {};
  5566. this._responsiveListeners = undefined;
  5567. this._sortedMetasets = [];
  5568. this.scales = {};
  5569. this._plugins = new PluginService();
  5570. this.$proxies = {};
  5571. this._hiddenIndices = {};
  5572. this.attached = false;
  5573. this._animationsDisabled = undefined;
  5574. this.$context = undefined;
  5575. this._doResize = helpers_segment.debounce((mode)=>this.update(mode), options.resizeDelay || 0);
  5576. this._dataChanges = [];
  5577. instances[this.id] = this;
  5578. if (!context || !canvas) {
  5579. console.error("Failed to create chart: can't acquire context from the given item");
  5580. return;
  5581. }
  5582. animator.listen(this, 'complete', onAnimationsComplete);
  5583. animator.listen(this, 'progress', onAnimationProgress);
  5584. this._initialize();
  5585. if (this.attached) {
  5586. this.update();
  5587. }
  5588. }
  5589. get aspectRatio() {
  5590. const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
  5591. if (!helpers_segment.isNullOrUndef(aspectRatio)) {
  5592. return aspectRatio;
  5593. }
  5594. if (maintainAspectRatio && _aspectRatio) {
  5595. return _aspectRatio;
  5596. }
  5597. return height ? width / height : null;
  5598. }
  5599. get data() {
  5600. return this.config.data;
  5601. }
  5602. set data(data) {
  5603. this.config.data = data;
  5604. }
  5605. get options() {
  5606. return this._options;
  5607. }
  5608. set options(options) {
  5609. this.config.options = options;
  5610. }
  5611. get registry() {
  5612. return registry;
  5613. }
  5614. _initialize() {
  5615. this.notifyPlugins('beforeInit');
  5616. if (this.options.responsive) {
  5617. this.resize();
  5618. } else {
  5619. helpers_segment.retinaScale(this, this.options.devicePixelRatio);
  5620. }
  5621. this.bindEvents();
  5622. this.notifyPlugins('afterInit');
  5623. return this;
  5624. }
  5625. clear() {
  5626. helpers_segment.clearCanvas(this.canvas, this.ctx);
  5627. return this;
  5628. }
  5629. stop() {
  5630. animator.stop(this);
  5631. return this;
  5632. }
  5633. resize(width, height) {
  5634. if (!animator.running(this)) {
  5635. this._resize(width, height);
  5636. } else {
  5637. this._resizeBeforeDraw = {
  5638. width,
  5639. height
  5640. };
  5641. }
  5642. }
  5643. _resize(width, height) {
  5644. const options = this.options;
  5645. const canvas = this.canvas;
  5646. const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
  5647. const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
  5648. const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
  5649. const mode = this.width ? 'resize' : 'attach';
  5650. this.width = newSize.width;
  5651. this.height = newSize.height;
  5652. this._aspectRatio = this.aspectRatio;
  5653. if (!helpers_segment.retinaScale(this, newRatio, true)) {
  5654. return;
  5655. }
  5656. this.notifyPlugins('resize', {
  5657. size: newSize
  5658. });
  5659. helpers_segment.callback(options.onResize, [
  5660. this,
  5661. newSize
  5662. ], this);
  5663. if (this.attached) {
  5664. if (this._doResize(mode)) {
  5665. this.render();
  5666. }
  5667. }
  5668. }
  5669. ensureScalesHaveIDs() {
  5670. const options = this.options;
  5671. const scalesOptions = options.scales || {};
  5672. helpers_segment.each(scalesOptions, (axisOptions, axisID)=>{
  5673. axisOptions.id = axisID;
  5674. });
  5675. }
  5676. buildOrUpdateScales() {
  5677. const options = this.options;
  5678. const scaleOpts = options.scales;
  5679. const scales = this.scales;
  5680. const updated = Object.keys(scales).reduce((obj, id)=>{
  5681. obj[id] = false;
  5682. return obj;
  5683. }, {});
  5684. let items = [];
  5685. if (scaleOpts) {
  5686. items = items.concat(Object.keys(scaleOpts).map((id)=>{
  5687. const scaleOptions = scaleOpts[id];
  5688. const axis = determineAxis(id, scaleOptions);
  5689. const isRadial = axis === 'r';
  5690. const isHorizontal = axis === 'x';
  5691. return {
  5692. options: scaleOptions,
  5693. dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
  5694. dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
  5695. };
  5696. }));
  5697. }
  5698. helpers_segment.each(items, (item)=>{
  5699. const scaleOptions = item.options;
  5700. const id = scaleOptions.id;
  5701. const axis = determineAxis(id, scaleOptions);
  5702. const scaleType = helpers_segment.valueOrDefault(scaleOptions.type, item.dtype);
  5703. if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
  5704. scaleOptions.position = item.dposition;
  5705. }
  5706. updated[id] = true;
  5707. let scale = null;
  5708. if (id in scales && scales[id].type === scaleType) {
  5709. scale = scales[id];
  5710. } else {
  5711. const scaleClass = registry.getScale(scaleType);
  5712. scale = new scaleClass({
  5713. id,
  5714. type: scaleType,
  5715. ctx: this.ctx,
  5716. chart: this
  5717. });
  5718. scales[scale.id] = scale;
  5719. }
  5720. scale.init(scaleOptions, options);
  5721. });
  5722. helpers_segment.each(updated, (hasUpdated, id)=>{
  5723. if (!hasUpdated) {
  5724. delete scales[id];
  5725. }
  5726. });
  5727. helpers_segment.each(scales, (scale)=>{
  5728. layouts.configure(this, scale, scale.options);
  5729. layouts.addBox(this, scale);
  5730. });
  5731. }
  5732. _updateMetasets() {
  5733. const metasets = this._metasets;
  5734. const numData = this.data.datasets.length;
  5735. const numMeta = metasets.length;
  5736. metasets.sort((a, b)=>a.index - b.index);
  5737. if (numMeta > numData) {
  5738. for(let i = numData; i < numMeta; ++i){
  5739. this._destroyDatasetMeta(i);
  5740. }
  5741. metasets.splice(numData, numMeta - numData);
  5742. }
  5743. this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
  5744. }
  5745. _removeUnreferencedMetasets() {
  5746. const { _metasets: metasets , data: { datasets } } = this;
  5747. if (metasets.length > datasets.length) {
  5748. delete this._stacks;
  5749. }
  5750. metasets.forEach((meta, index)=>{
  5751. if (datasets.filter((x)=>x === meta._dataset).length === 0) {
  5752. this._destroyDatasetMeta(index);
  5753. }
  5754. });
  5755. }
  5756. buildOrUpdateControllers() {
  5757. const newControllers = [];
  5758. const datasets = this.data.datasets;
  5759. let i, ilen;
  5760. this._removeUnreferencedMetasets();
  5761. for(i = 0, ilen = datasets.length; i < ilen; i++){
  5762. const dataset = datasets[i];
  5763. let meta = this.getDatasetMeta(i);
  5764. const type = dataset.type || this.config.type;
  5765. if (meta.type && meta.type !== type) {
  5766. this._destroyDatasetMeta(i);
  5767. meta = this.getDatasetMeta(i);
  5768. }
  5769. meta.type = type;
  5770. meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
  5771. meta.order = dataset.order || 0;
  5772. meta.index = i;
  5773. meta.label = '' + dataset.label;
  5774. meta.visible = this.isDatasetVisible(i);
  5775. if (meta.controller) {
  5776. meta.controller.updateIndex(i);
  5777. meta.controller.linkScales();
  5778. } else {
  5779. const ControllerClass = registry.getController(type);
  5780. const { datasetElementType , dataElementType } = helpers_segment.defaults.datasets[type];
  5781. Object.assign(ControllerClass, {
  5782. dataElementType: registry.getElement(dataElementType),
  5783. datasetElementType: datasetElementType && registry.getElement(datasetElementType)
  5784. });
  5785. meta.controller = new ControllerClass(this, i);
  5786. newControllers.push(meta.controller);
  5787. }
  5788. }
  5789. this._updateMetasets();
  5790. return newControllers;
  5791. }
  5792. _resetElements() {
  5793. helpers_segment.each(this.data.datasets, (dataset, datasetIndex)=>{
  5794. this.getDatasetMeta(datasetIndex).controller.reset();
  5795. }, this);
  5796. }
  5797. reset() {
  5798. this._resetElements();
  5799. this.notifyPlugins('reset');
  5800. }
  5801. update(mode) {
  5802. const config = this.config;
  5803. config.update();
  5804. const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5805. const animsDisabled = this._animationsDisabled = !options.animation;
  5806. this._updateScales();
  5807. this._checkEventBindings();
  5808. this._updateHiddenIndices();
  5809. this._plugins.invalidate();
  5810. if (this.notifyPlugins('beforeUpdate', {
  5811. mode,
  5812. cancelable: true
  5813. }) === false) {
  5814. return;
  5815. }
  5816. const newControllers = this.buildOrUpdateControllers();
  5817. this.notifyPlugins('beforeElementsUpdate');
  5818. let minPadding = 0;
  5819. for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
  5820. const { controller } = this.getDatasetMeta(i);
  5821. const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
  5822. controller.buildOrUpdateElements(reset);
  5823. minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
  5824. }
  5825. minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
  5826. this._updateLayout(minPadding);
  5827. if (!animsDisabled) {
  5828. helpers_segment.each(newControllers, (controller)=>{
  5829. controller.reset();
  5830. });
  5831. }
  5832. this._updateDatasets(mode);
  5833. this.notifyPlugins('afterUpdate', {
  5834. mode
  5835. });
  5836. this._layers.sort(compare2Level('z', '_idx'));
  5837. const { _active , _lastEvent } = this;
  5838. if (_lastEvent) {
  5839. this._eventHandler(_lastEvent, true);
  5840. } else if (_active.length) {
  5841. this._updateHoverStyles(_active, _active, true);
  5842. }
  5843. this.render();
  5844. }
  5845. _updateScales() {
  5846. helpers_segment.each(this.scales, (scale)=>{
  5847. layouts.removeBox(this, scale);
  5848. });
  5849. this.ensureScalesHaveIDs();
  5850. this.buildOrUpdateScales();
  5851. }
  5852. _checkEventBindings() {
  5853. const options = this.options;
  5854. const existingEvents = new Set(Object.keys(this._listeners));
  5855. const newEvents = new Set(options.events);
  5856. if (!helpers_segment.setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
  5857. this.unbindEvents();
  5858. this.bindEvents();
  5859. }
  5860. }
  5861. _updateHiddenIndices() {
  5862. const { _hiddenIndices } = this;
  5863. const changes = this._getUniformDataChanges() || [];
  5864. for (const { method , start , count } of changes){
  5865. const move = method === '_removeElements' ? -count : count;
  5866. moveNumericKeys(_hiddenIndices, start, move);
  5867. }
  5868. }
  5869. _getUniformDataChanges() {
  5870. const _dataChanges = this._dataChanges;
  5871. if (!_dataChanges || !_dataChanges.length) {
  5872. return;
  5873. }
  5874. this._dataChanges = [];
  5875. const datasetCount = this.data.datasets.length;
  5876. const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
  5877. const changeSet = makeSet(0);
  5878. for(let i = 1; i < datasetCount; i++){
  5879. if (!helpers_segment.setsEqual(changeSet, makeSet(i))) {
  5880. return;
  5881. }
  5882. }
  5883. return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
  5884. method: a[1],
  5885. start: +a[2],
  5886. count: +a[3]
  5887. }));
  5888. }
  5889. _updateLayout(minPadding) {
  5890. if (this.notifyPlugins('beforeLayout', {
  5891. cancelable: true
  5892. }) === false) {
  5893. return;
  5894. }
  5895. layouts.update(this, this.width, this.height, minPadding);
  5896. const area = this.chartArea;
  5897. const noArea = area.width <= 0 || area.height <= 0;
  5898. this._layers = [];
  5899. helpers_segment.each(this.boxes, (box)=>{
  5900. if (noArea && box.position === 'chartArea') {
  5901. return;
  5902. }
  5903. if (box.configure) {
  5904. box.configure();
  5905. }
  5906. this._layers.push(...box._layers());
  5907. }, this);
  5908. this._layers.forEach((item, index)=>{
  5909. item._idx = index;
  5910. });
  5911. this.notifyPlugins('afterLayout');
  5912. }
  5913. _updateDatasets(mode) {
  5914. if (this.notifyPlugins('beforeDatasetsUpdate', {
  5915. mode,
  5916. cancelable: true
  5917. }) === false) {
  5918. return;
  5919. }
  5920. for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  5921. this.getDatasetMeta(i).controller.configure();
  5922. }
  5923. for(let i1 = 0, ilen1 = this.data.datasets.length; i1 < ilen1; ++i1){
  5924. this._updateDataset(i1, helpers_segment.isFunction(mode) ? mode({
  5925. datasetIndex: i1
  5926. }) : mode);
  5927. }
  5928. this.notifyPlugins('afterDatasetsUpdate', {
  5929. mode
  5930. });
  5931. }
  5932. _updateDataset(index, mode) {
  5933. const meta = this.getDatasetMeta(index);
  5934. const args = {
  5935. meta,
  5936. index,
  5937. mode,
  5938. cancelable: true
  5939. };
  5940. if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
  5941. return;
  5942. }
  5943. meta.controller._update(mode);
  5944. args.cancelable = false;
  5945. this.notifyPlugins('afterDatasetUpdate', args);
  5946. }
  5947. render() {
  5948. if (this.notifyPlugins('beforeRender', {
  5949. cancelable: true
  5950. }) === false) {
  5951. return;
  5952. }
  5953. if (animator.has(this)) {
  5954. if (this.attached && !animator.running(this)) {
  5955. animator.start(this);
  5956. }
  5957. } else {
  5958. this.draw();
  5959. onAnimationsComplete({
  5960. chart: this
  5961. });
  5962. }
  5963. }
  5964. draw() {
  5965. let i;
  5966. if (this._resizeBeforeDraw) {
  5967. const { width , height } = this._resizeBeforeDraw;
  5968. this._resize(width, height);
  5969. this._resizeBeforeDraw = null;
  5970. }
  5971. this.clear();
  5972. if (this.width <= 0 || this.height <= 0) {
  5973. return;
  5974. }
  5975. if (this.notifyPlugins('beforeDraw', {
  5976. cancelable: true
  5977. }) === false) {
  5978. return;
  5979. }
  5980. const layers = this._layers;
  5981. for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
  5982. layers[i].draw(this.chartArea);
  5983. }
  5984. this._drawDatasets();
  5985. for(; i < layers.length; ++i){
  5986. layers[i].draw(this.chartArea);
  5987. }
  5988. this.notifyPlugins('afterDraw');
  5989. }
  5990. _getSortedDatasetMetas(filterVisible) {
  5991. const metasets = this._sortedMetasets;
  5992. const result = [];
  5993. let i, ilen;
  5994. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  5995. const meta = metasets[i];
  5996. if (!filterVisible || meta.visible) {
  5997. result.push(meta);
  5998. }
  5999. }
  6000. return result;
  6001. }
  6002. getSortedVisibleDatasetMetas() {
  6003. return this._getSortedDatasetMetas(true);
  6004. }
  6005. _drawDatasets() {
  6006. if (this.notifyPlugins('beforeDatasetsDraw', {
  6007. cancelable: true
  6008. }) === false) {
  6009. return;
  6010. }
  6011. const metasets = this.getSortedVisibleDatasetMetas();
  6012. for(let i = metasets.length - 1; i >= 0; --i){
  6013. this._drawDataset(metasets[i]);
  6014. }
  6015. this.notifyPlugins('afterDatasetsDraw');
  6016. }
  6017. _drawDataset(meta) {
  6018. const ctx = this.ctx;
  6019. const clip = meta._clip;
  6020. const useClip = !clip.disabled;
  6021. const area = getDatasetArea(meta) || this.chartArea;
  6022. const args = {
  6023. meta,
  6024. index: meta.index,
  6025. cancelable: true
  6026. };
  6027. if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
  6028. return;
  6029. }
  6030. if (useClip) {
  6031. helpers_segment.clipArea(ctx, {
  6032. left: clip.left === false ? 0 : area.left - clip.left,
  6033. right: clip.right === false ? this.width : area.right + clip.right,
  6034. top: clip.top === false ? 0 : area.top - clip.top,
  6035. bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
  6036. });
  6037. }
  6038. meta.controller.draw();
  6039. if (useClip) {
  6040. helpers_segment.unclipArea(ctx);
  6041. }
  6042. args.cancelable = false;
  6043. this.notifyPlugins('afterDatasetDraw', args);
  6044. }
  6045. isPointInArea(point) {
  6046. return helpers_segment._isPointInArea(point, this.chartArea, this._minPadding);
  6047. }
  6048. getElementsAtEventForMode(e, mode, options, useFinalPosition) {
  6049. const method = Interaction.modes[mode];
  6050. if (typeof method === 'function') {
  6051. return method(this, e, options, useFinalPosition);
  6052. }
  6053. return [];
  6054. }
  6055. getDatasetMeta(datasetIndex) {
  6056. const dataset = this.data.datasets[datasetIndex];
  6057. const metasets = this._metasets;
  6058. let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
  6059. if (!meta) {
  6060. meta = {
  6061. type: null,
  6062. data: [],
  6063. dataset: null,
  6064. controller: null,
  6065. hidden: null,
  6066. xAxisID: null,
  6067. yAxisID: null,
  6068. order: dataset && dataset.order || 0,
  6069. index: datasetIndex,
  6070. _dataset: dataset,
  6071. _parsed: [],
  6072. _sorted: false
  6073. };
  6074. metasets.push(meta);
  6075. }
  6076. return meta;
  6077. }
  6078. getContext() {
  6079. return this.$context || (this.$context = helpers_segment.createContext(null, {
  6080. chart: this,
  6081. type: 'chart'
  6082. }));
  6083. }
  6084. getVisibleDatasetCount() {
  6085. return this.getSortedVisibleDatasetMetas().length;
  6086. }
  6087. isDatasetVisible(datasetIndex) {
  6088. const dataset = this.data.datasets[datasetIndex];
  6089. if (!dataset) {
  6090. return false;
  6091. }
  6092. const meta = this.getDatasetMeta(datasetIndex);
  6093. return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
  6094. }
  6095. setDatasetVisibility(datasetIndex, visible) {
  6096. const meta = this.getDatasetMeta(datasetIndex);
  6097. meta.hidden = !visible;
  6098. }
  6099. toggleDataVisibility(index) {
  6100. this._hiddenIndices[index] = !this._hiddenIndices[index];
  6101. }
  6102. getDataVisibility(index) {
  6103. return !this._hiddenIndices[index];
  6104. }
  6105. _updateVisibility(datasetIndex, dataIndex, visible) {
  6106. const mode = visible ? 'show' : 'hide';
  6107. const meta = this.getDatasetMeta(datasetIndex);
  6108. const anims = meta.controller._resolveAnimations(undefined, mode);
  6109. if (helpers_segment.defined(dataIndex)) {
  6110. meta.data[dataIndex].hidden = !visible;
  6111. this.update();
  6112. } else {
  6113. this.setDatasetVisibility(datasetIndex, visible);
  6114. anims.update(meta, {
  6115. visible
  6116. });
  6117. this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
  6118. }
  6119. }
  6120. hide(datasetIndex, dataIndex) {
  6121. this._updateVisibility(datasetIndex, dataIndex, false);
  6122. }
  6123. show(datasetIndex, dataIndex) {
  6124. this._updateVisibility(datasetIndex, dataIndex, true);
  6125. }
  6126. _destroyDatasetMeta(datasetIndex) {
  6127. const meta = this._metasets[datasetIndex];
  6128. if (meta && meta.controller) {
  6129. meta.controller._destroy();
  6130. }
  6131. delete this._metasets[datasetIndex];
  6132. }
  6133. _stop() {
  6134. let i, ilen;
  6135. this.stop();
  6136. animator.remove(this);
  6137. for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  6138. this._destroyDatasetMeta(i);
  6139. }
  6140. }
  6141. destroy() {
  6142. this.notifyPlugins('beforeDestroy');
  6143. const { canvas , ctx } = this;
  6144. this._stop();
  6145. this.config.clearCache();
  6146. if (canvas) {
  6147. this.unbindEvents();
  6148. helpers_segment.clearCanvas(canvas, ctx);
  6149. this.platform.releaseContext(ctx);
  6150. this.canvas = null;
  6151. this.ctx = null;
  6152. }
  6153. delete instances[this.id];
  6154. this.notifyPlugins('afterDestroy');
  6155. }
  6156. toBase64Image(...args) {
  6157. return this.canvas.toDataURL(...args);
  6158. }
  6159. bindEvents() {
  6160. this.bindUserEvents();
  6161. if (this.options.responsive) {
  6162. this.bindResponsiveEvents();
  6163. } else {
  6164. this.attached = true;
  6165. }
  6166. }
  6167. bindUserEvents() {
  6168. const listeners = this._listeners;
  6169. const platform = this.platform;
  6170. const _add = (type, listener)=>{
  6171. platform.addEventListener(this, type, listener);
  6172. listeners[type] = listener;
  6173. };
  6174. const listener = (e, x, y)=>{
  6175. e.offsetX = x;
  6176. e.offsetY = y;
  6177. this._eventHandler(e);
  6178. };
  6179. helpers_segment.each(this.options.events, (type)=>_add(type, listener));
  6180. }
  6181. bindResponsiveEvents() {
  6182. if (!this._responsiveListeners) {
  6183. this._responsiveListeners = {};
  6184. }
  6185. const listeners = this._responsiveListeners;
  6186. const platform = this.platform;
  6187. const _add = (type, listener)=>{
  6188. platform.addEventListener(this, type, listener);
  6189. listeners[type] = listener;
  6190. };
  6191. const _remove = (type, listener)=>{
  6192. if (listeners[type]) {
  6193. platform.removeEventListener(this, type, listener);
  6194. delete listeners[type];
  6195. }
  6196. };
  6197. const listener = (width, height)=>{
  6198. if (this.canvas) {
  6199. this.resize(width, height);
  6200. }
  6201. };
  6202. let detached;
  6203. const attached = ()=>{
  6204. _remove('attach', attached);
  6205. this.attached = true;
  6206. this.resize();
  6207. _add('resize', listener);
  6208. _add('detach', detached);
  6209. };
  6210. detached = ()=>{
  6211. this.attached = false;
  6212. _remove('resize', listener);
  6213. this._stop();
  6214. this._resize(0, 0);
  6215. _add('attach', attached);
  6216. };
  6217. if (platform.isAttached(this.canvas)) {
  6218. attached();
  6219. } else {
  6220. detached();
  6221. }
  6222. }
  6223. unbindEvents() {
  6224. helpers_segment.each(this._listeners, (listener, type)=>{
  6225. this.platform.removeEventListener(this, type, listener);
  6226. });
  6227. this._listeners = {};
  6228. helpers_segment.each(this._responsiveListeners, (listener, type)=>{
  6229. this.platform.removeEventListener(this, type, listener);
  6230. });
  6231. this._responsiveListeners = undefined;
  6232. }
  6233. updateHoverStyle(items, mode, enabled) {
  6234. const prefix = enabled ? 'set' : 'remove';
  6235. let meta, item, i, ilen;
  6236. if (mode === 'dataset') {
  6237. meta = this.getDatasetMeta(items[0].datasetIndex);
  6238. meta.controller['_' + prefix + 'DatasetHoverStyle']();
  6239. }
  6240. for(i = 0, ilen = items.length; i < ilen; ++i){
  6241. item = items[i];
  6242. const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
  6243. if (controller) {
  6244. controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
  6245. }
  6246. }
  6247. }
  6248. getActiveElements() {
  6249. return this._active || [];
  6250. }
  6251. setActiveElements(activeElements) {
  6252. const lastActive = this._active || [];
  6253. const active = activeElements.map(({ datasetIndex , index })=>{
  6254. const meta = this.getDatasetMeta(datasetIndex);
  6255. if (!meta) {
  6256. throw new Error('No dataset found at index ' + datasetIndex);
  6257. }
  6258. return {
  6259. datasetIndex,
  6260. element: meta.data[index],
  6261. index
  6262. };
  6263. });
  6264. const changed = !helpers_segment._elementsEqual(active, lastActive);
  6265. if (changed) {
  6266. this._active = active;
  6267. this._lastEvent = null;
  6268. this._updateHoverStyles(active, lastActive);
  6269. }
  6270. }
  6271. notifyPlugins(hook, args, filter) {
  6272. return this._plugins.notify(this, hook, args, filter);
  6273. }
  6274. isPluginEnabled(pluginId) {
  6275. return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
  6276. }
  6277. _updateHoverStyles(active, lastActive, replay) {
  6278. const hoverOptions = this.options.hover;
  6279. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
  6280. const deactivated = diff(lastActive, active);
  6281. const activated = replay ? active : diff(active, lastActive);
  6282. if (deactivated.length) {
  6283. this.updateHoverStyle(deactivated, hoverOptions.mode, false);
  6284. }
  6285. if (activated.length && hoverOptions.mode) {
  6286. this.updateHoverStyle(activated, hoverOptions.mode, true);
  6287. }
  6288. }
  6289. _eventHandler(e, replay) {
  6290. const args = {
  6291. event: e,
  6292. replay,
  6293. cancelable: true,
  6294. inChartArea: this.isPointInArea(e)
  6295. };
  6296. const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
  6297. if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
  6298. return;
  6299. }
  6300. const changed = this._handleEvent(e, replay, args.inChartArea);
  6301. args.cancelable = false;
  6302. this.notifyPlugins('afterEvent', args, eventFilter);
  6303. if (changed || args.changed) {
  6304. this.render();
  6305. }
  6306. return this;
  6307. }
  6308. _handleEvent(e, replay, inChartArea) {
  6309. const { _active: lastActive = [] , options } = this;
  6310. const useFinalPosition = replay;
  6311. const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
  6312. const isClick = helpers_segment._isClickEvent(e);
  6313. const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
  6314. if (inChartArea) {
  6315. this._lastEvent = null;
  6316. helpers_segment.callback(options.onHover, [
  6317. e,
  6318. active,
  6319. this
  6320. ], this);
  6321. if (isClick) {
  6322. helpers_segment.callback(options.onClick, [
  6323. e,
  6324. active,
  6325. this
  6326. ], this);
  6327. }
  6328. }
  6329. const changed = !helpers_segment._elementsEqual(active, lastActive);
  6330. if (changed || replay) {
  6331. this._active = active;
  6332. this._updateHoverStyles(active, lastActive, replay);
  6333. }
  6334. this._lastEvent = lastEvent;
  6335. return changed;
  6336. }
  6337. _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
  6338. if (e.type === 'mouseout') {
  6339. return [];
  6340. }
  6341. if (!inChartArea) {
  6342. return lastActive;
  6343. }
  6344. const hoverOptions = this.options.hover;
  6345. return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
  6346. }
  6347. }
  6348. function invalidatePlugins() {
  6349. return helpers_segment.each(Chart.instances, (chart)=>chart._plugins.invalidate());
  6350. }
  6351. function clipArc(ctx, element, endAngle) {
  6352. const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
  6353. let angleMargin = pixelMargin / outerRadius;
  6354. // Draw an inner border by clipping the arc and drawing a double-width border
  6355. // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
  6356. ctx.beginPath();
  6357. ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
  6358. if (innerRadius > pixelMargin) {
  6359. angleMargin = pixelMargin / innerRadius;
  6360. ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
  6361. } else {
  6362. ctx.arc(x, y, pixelMargin, endAngle + helpers_segment.HALF_PI, startAngle - helpers_segment.HALF_PI);
  6363. }
  6364. ctx.closePath();
  6365. ctx.clip();
  6366. }
  6367. function toRadiusCorners(value) {
  6368. return helpers_segment._readValueToProps(value, [
  6369. 'outerStart',
  6370. 'outerEnd',
  6371. 'innerStart',
  6372. 'innerEnd'
  6373. ]);
  6374. }
  6375. /**
  6376. * Parse border radius from the provided options
  6377. */ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
  6378. const o = toRadiusCorners(arc.options.borderRadius);
  6379. const halfThickness = (outerRadius - innerRadius) / 2;
  6380. const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
  6381. // Outer limits are complicated. We want to compute the available angular distance at
  6382. // a radius of outerRadius - borderRadius because for small angular distances, this term limits.
  6383. // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
  6384. //
  6385. // If the borderRadius is large, that value can become negative.
  6386. // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
  6387. // we know that the thickness term will dominate and compute the limits at that point
  6388. const computeOuterLimit = (val)=>{
  6389. const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
  6390. return helpers_segment._limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
  6391. };
  6392. return {
  6393. outerStart: computeOuterLimit(o.outerStart),
  6394. outerEnd: computeOuterLimit(o.outerEnd),
  6395. innerStart: helpers_segment._limitValue(o.innerStart, 0, innerLimit),
  6396. innerEnd: helpers_segment._limitValue(o.innerEnd, 0, innerLimit)
  6397. };
  6398. }
  6399. /**
  6400. * Convert (r, 𝜃) to (x, y)
  6401. */ function rThetaToXY(r, theta, x, y) {
  6402. return {
  6403. x: x + r * Math.cos(theta),
  6404. y: y + r * Math.sin(theta)
  6405. };
  6406. }
  6407. /**
  6408. * Path the arc, respecting border radius by separating into left and right halves.
  6409. *
  6410. * Start End
  6411. *
  6412. * 1--->a--->2 Outer
  6413. * / \
  6414. * 8 3
  6415. * | |
  6416. * | |
  6417. * 7 4
  6418. * \ /
  6419. * 6<---b<---5 Inner
  6420. */ function pathArc(ctx, element, offset, spacing, end, circular) {
  6421. const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
  6422. const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
  6423. const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
  6424. let spacingOffset = 0;
  6425. const alpha = end - start;
  6426. if (spacing) {
  6427. // When spacing is present, it is the same for all items
  6428. // So we adjust the start and end angle of the arc such that
  6429. // the distance is the same as it would be without the spacing
  6430. const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
  6431. const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
  6432. const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
  6433. const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
  6434. spacingOffset = (alpha - adjustedAngle) / 2;
  6435. }
  6436. const beta = Math.max(0.001, alpha * outerRadius - offset / helpers_segment.PI) / outerRadius;
  6437. const angleOffset = (alpha - beta) / 2;
  6438. const startAngle = start + angleOffset + spacingOffset;
  6439. const endAngle = end - angleOffset - spacingOffset;
  6440. const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
  6441. const outerStartAdjustedRadius = outerRadius - outerStart;
  6442. const outerEndAdjustedRadius = outerRadius - outerEnd;
  6443. const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
  6444. const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
  6445. const innerStartAdjustedRadius = innerRadius + innerStart;
  6446. const innerEndAdjustedRadius = innerRadius + innerEnd;
  6447. const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
  6448. const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
  6449. ctx.beginPath();
  6450. if (circular) {
  6451. // The first arc segments from point 1 to point a to point 2
  6452. const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
  6453. ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
  6454. ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
  6455. // The corner segment from point 2 to point 3
  6456. if (outerEnd > 0) {
  6457. const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
  6458. ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + helpers_segment.HALF_PI);
  6459. }
  6460. // The line from point 3 to point 4
  6461. const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
  6462. ctx.lineTo(p4.x, p4.y);
  6463. // The corner segment from point 4 to point 5
  6464. if (innerEnd > 0) {
  6465. const pCenter1 = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
  6466. ctx.arc(pCenter1.x, pCenter1.y, innerEnd, endAngle + helpers_segment.HALF_PI, innerEndAdjustedAngle + Math.PI);
  6467. }
  6468. // The inner arc from point 5 to point b to point 6
  6469. const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
  6470. ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
  6471. ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
  6472. // The corner segment from point 6 to point 7
  6473. if (innerStart > 0) {
  6474. const pCenter2 = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
  6475. ctx.arc(pCenter2.x, pCenter2.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - helpers_segment.HALF_PI);
  6476. }
  6477. // The line from point 7 to point 8
  6478. const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
  6479. ctx.lineTo(p8.x, p8.y);
  6480. // The corner segment from point 8 to point 1
  6481. if (outerStart > 0) {
  6482. const pCenter3 = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
  6483. ctx.arc(pCenter3.x, pCenter3.y, outerStart, startAngle - helpers_segment.HALF_PI, outerStartAdjustedAngle);
  6484. }
  6485. } else {
  6486. ctx.moveTo(x, y);
  6487. const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
  6488. const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
  6489. ctx.lineTo(outerStartX, outerStartY);
  6490. const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
  6491. const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
  6492. ctx.lineTo(outerEndX, outerEndY);
  6493. }
  6494. ctx.closePath();
  6495. }
  6496. function drawArc(ctx, element, offset, spacing, circular) {
  6497. const { fullCircles , startAngle , circumference } = element;
  6498. let endAngle = element.endAngle;
  6499. if (fullCircles) {
  6500. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6501. for(let i = 0; i < fullCircles; ++i){
  6502. ctx.fill();
  6503. }
  6504. if (!isNaN(circumference)) {
  6505. endAngle = startAngle + (circumference % helpers_segment.TAU || helpers_segment.TAU);
  6506. }
  6507. }
  6508. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6509. ctx.fill();
  6510. return endAngle;
  6511. }
  6512. function drawBorder(ctx, element, offset, spacing, circular) {
  6513. const { fullCircles , startAngle , circumference , options } = element;
  6514. const { borderWidth , borderJoinStyle } = options;
  6515. const inner = options.borderAlign === 'inner';
  6516. if (!borderWidth) {
  6517. return;
  6518. }
  6519. if (inner) {
  6520. ctx.lineWidth = borderWidth * 2;
  6521. ctx.lineJoin = borderJoinStyle || 'round';
  6522. } else {
  6523. ctx.lineWidth = borderWidth;
  6524. ctx.lineJoin = borderJoinStyle || 'bevel';
  6525. }
  6526. let endAngle = element.endAngle;
  6527. if (fullCircles) {
  6528. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6529. for(let i = 0; i < fullCircles; ++i){
  6530. ctx.stroke();
  6531. }
  6532. if (!isNaN(circumference)) {
  6533. endAngle = startAngle + (circumference % helpers_segment.TAU || helpers_segment.TAU);
  6534. }
  6535. }
  6536. if (inner) {
  6537. clipArc(ctx, element, endAngle);
  6538. }
  6539. if (!fullCircles) {
  6540. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6541. ctx.stroke();
  6542. }
  6543. }
  6544. class ArcElement extends Element {
  6545. static id = 'arc';
  6546. static defaults = {
  6547. borderAlign: 'center',
  6548. borderColor: '#fff',
  6549. borderJoinStyle: undefined,
  6550. borderRadius: 0,
  6551. borderWidth: 2,
  6552. offset: 0,
  6553. spacing: 0,
  6554. angle: undefined,
  6555. circular: true
  6556. };
  6557. static defaultRoutes = {
  6558. backgroundColor: 'backgroundColor'
  6559. };
  6560. constructor(cfg){
  6561. super();
  6562. this.options = undefined;
  6563. this.circumference = undefined;
  6564. this.startAngle = undefined;
  6565. this.endAngle = undefined;
  6566. this.innerRadius = undefined;
  6567. this.outerRadius = undefined;
  6568. this.pixelMargin = 0;
  6569. this.fullCircles = 0;
  6570. if (cfg) {
  6571. Object.assign(this, cfg);
  6572. }
  6573. }
  6574. inRange(chartX, chartY, useFinalPosition) {
  6575. const point = this.getProps([
  6576. 'x',
  6577. 'y'
  6578. ], useFinalPosition);
  6579. const { angle , distance } = helpers_segment.getAngleFromPoint(point, {
  6580. x: chartX,
  6581. y: chartY
  6582. });
  6583. const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
  6584. 'startAngle',
  6585. 'endAngle',
  6586. 'innerRadius',
  6587. 'outerRadius',
  6588. 'circumference'
  6589. ], useFinalPosition);
  6590. const rAdjust = this.options.spacing / 2;
  6591. const _circumference = helpers_segment.valueOrDefault(circumference, endAngle - startAngle);
  6592. const betweenAngles = _circumference >= helpers_segment.TAU || helpers_segment._angleBetween(angle, startAngle, endAngle);
  6593. const withinRadius = helpers_segment._isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
  6594. return betweenAngles && withinRadius;
  6595. }
  6596. getCenterPoint(useFinalPosition) {
  6597. const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
  6598. 'x',
  6599. 'y',
  6600. 'startAngle',
  6601. 'endAngle',
  6602. 'innerRadius',
  6603. 'outerRadius'
  6604. ], useFinalPosition);
  6605. const { offset , spacing } = this.options;
  6606. const halfAngle = (startAngle + endAngle) / 2;
  6607. const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
  6608. return {
  6609. x: x + Math.cos(halfAngle) * halfRadius,
  6610. y: y + Math.sin(halfAngle) * halfRadius
  6611. };
  6612. }
  6613. tooltipPosition(useFinalPosition) {
  6614. return this.getCenterPoint(useFinalPosition);
  6615. }
  6616. draw(ctx) {
  6617. const { options , circumference } = this;
  6618. const offset = (options.offset || 0) / 4;
  6619. const spacing = (options.spacing || 0) / 2;
  6620. const circular = options.circular;
  6621. this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
  6622. this.fullCircles = circumference > helpers_segment.TAU ? Math.floor(circumference / helpers_segment.TAU) : 0;
  6623. if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
  6624. return;
  6625. }
  6626. ctx.save();
  6627. const halfAngle = (this.startAngle + this.endAngle) / 2;
  6628. ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
  6629. const fix = 1 - Math.sin(Math.min(helpers_segment.PI, circumference || 0));
  6630. const radiusOffset = offset * fix;
  6631. ctx.fillStyle = options.backgroundColor;
  6632. ctx.strokeStyle = options.borderColor;
  6633. drawArc(ctx, this, radiusOffset, spacing, circular);
  6634. drawBorder(ctx, this, radiusOffset, spacing, circular);
  6635. ctx.restore();
  6636. }
  6637. }
  6638. function setStyle(ctx, options, style = options) {
  6639. ctx.lineCap = helpers_segment.valueOrDefault(style.borderCapStyle, options.borderCapStyle);
  6640. ctx.setLineDash(helpers_segment.valueOrDefault(style.borderDash, options.borderDash));
  6641. ctx.lineDashOffset = helpers_segment.valueOrDefault(style.borderDashOffset, options.borderDashOffset);
  6642. ctx.lineJoin = helpers_segment.valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
  6643. ctx.lineWidth = helpers_segment.valueOrDefault(style.borderWidth, options.borderWidth);
  6644. ctx.strokeStyle = helpers_segment.valueOrDefault(style.borderColor, options.borderColor);
  6645. }
  6646. function lineTo(ctx, previous, target) {
  6647. ctx.lineTo(target.x, target.y);
  6648. }
  6649. function getLineMethod(options) {
  6650. if (options.stepped) {
  6651. return helpers_segment._steppedLineTo;
  6652. }
  6653. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6654. return helpers_segment._bezierCurveTo;
  6655. }
  6656. return lineTo;
  6657. }
  6658. function pathVars(points, segment, params = {}) {
  6659. const count = points.length;
  6660. const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
  6661. const { start: segmentStart , end: segmentEnd } = segment;
  6662. const start = Math.max(paramsStart, segmentStart);
  6663. const end = Math.min(paramsEnd, segmentEnd);
  6664. const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
  6665. return {
  6666. count,
  6667. start,
  6668. loop: segment.loop,
  6669. ilen: end < start && !outside ? count + end - start : end - start
  6670. };
  6671. }
  6672. function pathSegment(ctx, line, segment, params) {
  6673. const { points , options } = line;
  6674. const { count , start , loop , ilen } = pathVars(points, segment, params);
  6675. const lineMethod = getLineMethod(options);
  6676. let { move =true , reverse } = params || {};
  6677. let i, point, prev;
  6678. for(i = 0; i <= ilen; ++i){
  6679. point = points[(start + (reverse ? ilen - i : i)) % count];
  6680. if (point.skip) {
  6681. continue;
  6682. } else if (move) {
  6683. ctx.moveTo(point.x, point.y);
  6684. move = false;
  6685. } else {
  6686. lineMethod(ctx, prev, point, reverse, options.stepped);
  6687. }
  6688. prev = point;
  6689. }
  6690. if (loop) {
  6691. point = points[(start + (reverse ? ilen : 0)) % count];
  6692. lineMethod(ctx, prev, point, reverse, options.stepped);
  6693. }
  6694. return !!loop;
  6695. }
  6696. function fastPathSegment(ctx, line, segment, params) {
  6697. const points = line.points;
  6698. const { count , start , ilen } = pathVars(points, segment, params);
  6699. const { move =true , reverse } = params || {};
  6700. let avgX = 0;
  6701. let countX = 0;
  6702. let i, point, prevX, minY, maxY, lastY;
  6703. const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
  6704. const drawX = ()=>{
  6705. if (minY !== maxY) {
  6706. ctx.lineTo(avgX, maxY);
  6707. ctx.lineTo(avgX, minY);
  6708. ctx.lineTo(avgX, lastY);
  6709. }
  6710. };
  6711. if (move) {
  6712. point = points[pointIndex(0)];
  6713. ctx.moveTo(point.x, point.y);
  6714. }
  6715. for(i = 0; i <= ilen; ++i){
  6716. point = points[pointIndex(i)];
  6717. if (point.skip) {
  6718. continue;
  6719. }
  6720. const x = point.x;
  6721. const y = point.y;
  6722. const truncX = x | 0;
  6723. if (truncX === prevX) {
  6724. if (y < minY) {
  6725. minY = y;
  6726. } else if (y > maxY) {
  6727. maxY = y;
  6728. }
  6729. avgX = (countX * avgX + x) / ++countX;
  6730. } else {
  6731. drawX();
  6732. ctx.lineTo(x, y);
  6733. prevX = truncX;
  6734. countX = 0;
  6735. minY = maxY = y;
  6736. }
  6737. lastY = y;
  6738. }
  6739. drawX();
  6740. }
  6741. function _getSegmentMethod(line) {
  6742. const opts = line.options;
  6743. const borderDash = opts.borderDash && opts.borderDash.length;
  6744. const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
  6745. return useFastPath ? fastPathSegment : pathSegment;
  6746. }
  6747. function _getInterpolationMethod(options) {
  6748. if (options.stepped) {
  6749. return helpers_segment._steppedInterpolation;
  6750. }
  6751. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6752. return helpers_segment._bezierInterpolation;
  6753. }
  6754. return helpers_segment._pointInLine;
  6755. }
  6756. function strokePathWithCache(ctx, line, start, count) {
  6757. let path = line._path;
  6758. if (!path) {
  6759. path = line._path = new Path2D();
  6760. if (line.path(path, start, count)) {
  6761. path.closePath();
  6762. }
  6763. }
  6764. setStyle(ctx, line.options);
  6765. ctx.stroke(path);
  6766. }
  6767. function strokePathDirect(ctx, line, start, count) {
  6768. const { segments , options } = line;
  6769. const segmentMethod = _getSegmentMethod(line);
  6770. for (const segment of segments){
  6771. setStyle(ctx, options, segment.style);
  6772. ctx.beginPath();
  6773. if (segmentMethod(ctx, line, segment, {
  6774. start,
  6775. end: start + count - 1
  6776. })) {
  6777. ctx.closePath();
  6778. }
  6779. ctx.stroke();
  6780. }
  6781. }
  6782. const usePath2D = typeof Path2D === 'function';
  6783. function draw(ctx, line, start, count) {
  6784. if (usePath2D && !line.options.segment) {
  6785. strokePathWithCache(ctx, line, start, count);
  6786. } else {
  6787. strokePathDirect(ctx, line, start, count);
  6788. }
  6789. }
  6790. class LineElement extends Element {
  6791. static id = 'line';
  6792. static defaults = {
  6793. borderCapStyle: 'butt',
  6794. borderDash: [],
  6795. borderDashOffset: 0,
  6796. borderJoinStyle: 'miter',
  6797. borderWidth: 3,
  6798. capBezierPoints: true,
  6799. cubicInterpolationMode: 'default',
  6800. fill: false,
  6801. spanGaps: false,
  6802. stepped: false,
  6803. tension: 0
  6804. };
  6805. static defaultRoutes = {
  6806. backgroundColor: 'backgroundColor',
  6807. borderColor: 'borderColor'
  6808. };
  6809. static descriptors = {
  6810. _scriptable: true,
  6811. _indexable: (name)=>name !== 'borderDash' && name !== 'fill'
  6812. };
  6813. constructor(cfg){
  6814. super();
  6815. this.animated = true;
  6816. this.options = undefined;
  6817. this._chart = undefined;
  6818. this._loop = undefined;
  6819. this._fullLoop = undefined;
  6820. this._path = undefined;
  6821. this._points = undefined;
  6822. this._segments = undefined;
  6823. this._decimated = false;
  6824. this._pointsUpdated = false;
  6825. this._datasetIndex = undefined;
  6826. if (cfg) {
  6827. Object.assign(this, cfg);
  6828. }
  6829. }
  6830. updateControlPoints(chartArea, indexAxis) {
  6831. const options = this.options;
  6832. if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
  6833. const loop = options.spanGaps ? this._loop : this._fullLoop;
  6834. helpers_segment._updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
  6835. this._pointsUpdated = true;
  6836. }
  6837. }
  6838. set points(points) {
  6839. this._points = points;
  6840. delete this._segments;
  6841. delete this._path;
  6842. this._pointsUpdated = false;
  6843. }
  6844. get points() {
  6845. return this._points;
  6846. }
  6847. get segments() {
  6848. return this._segments || (this._segments = helpers_segment._computeSegments(this, this.options.segment));
  6849. }
  6850. first() {
  6851. const segments = this.segments;
  6852. const points = this.points;
  6853. return segments.length && points[segments[0].start];
  6854. }
  6855. last() {
  6856. const segments = this.segments;
  6857. const points = this.points;
  6858. const count = segments.length;
  6859. return count && points[segments[count - 1].end];
  6860. }
  6861. interpolate(point, property) {
  6862. const options = this.options;
  6863. const value = point[property];
  6864. const points = this.points;
  6865. const segments = helpers_segment._boundSegments(this, {
  6866. property,
  6867. start: value,
  6868. end: value
  6869. });
  6870. if (!segments.length) {
  6871. return;
  6872. }
  6873. const result = [];
  6874. const _interpolate = _getInterpolationMethod(options);
  6875. let i, ilen;
  6876. for(i = 0, ilen = segments.length; i < ilen; ++i){
  6877. const { start , end } = segments[i];
  6878. const p1 = points[start];
  6879. const p2 = points[end];
  6880. if (p1 === p2) {
  6881. result.push(p1);
  6882. continue;
  6883. }
  6884. const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
  6885. const interpolated = _interpolate(p1, p2, t, options.stepped);
  6886. interpolated[property] = point[property];
  6887. result.push(interpolated);
  6888. }
  6889. return result.length === 1 ? result[0] : result;
  6890. }
  6891. pathSegment(ctx, segment, params) {
  6892. const segmentMethod = _getSegmentMethod(this);
  6893. return segmentMethod(ctx, this, segment, params);
  6894. }
  6895. path(ctx, start, count) {
  6896. const segments = this.segments;
  6897. const segmentMethod = _getSegmentMethod(this);
  6898. let loop = this._loop;
  6899. start = start || 0;
  6900. count = count || this.points.length - start;
  6901. for (const segment of segments){
  6902. loop &= segmentMethod(ctx, this, segment, {
  6903. start,
  6904. end: start + count - 1
  6905. });
  6906. }
  6907. return !!loop;
  6908. }
  6909. draw(ctx, chartArea, start, count) {
  6910. const options = this.options || {};
  6911. const points = this.points || [];
  6912. if (points.length && options.borderWidth) {
  6913. ctx.save();
  6914. draw(ctx, this, start, count);
  6915. ctx.restore();
  6916. }
  6917. if (this.animated) {
  6918. this._pointsUpdated = false;
  6919. this._path = undefined;
  6920. }
  6921. }
  6922. }
  6923. function inRange$1(el, pos, axis, useFinalPosition) {
  6924. const options = el.options;
  6925. const { [axis]: value } = el.getProps([
  6926. axis
  6927. ], useFinalPosition);
  6928. return Math.abs(pos - value) < options.radius + options.hitRadius;
  6929. }
  6930. class PointElement extends Element {
  6931. static id = 'point';
  6932. /**
  6933. * @type {any}
  6934. */ static defaults = {
  6935. borderWidth: 1,
  6936. hitRadius: 1,
  6937. hoverBorderWidth: 1,
  6938. hoverRadius: 4,
  6939. pointStyle: 'circle',
  6940. radius: 3,
  6941. rotation: 0
  6942. };
  6943. /**
  6944. * @type {any}
  6945. */ static defaultRoutes = {
  6946. backgroundColor: 'backgroundColor',
  6947. borderColor: 'borderColor'
  6948. };
  6949. constructor(cfg){
  6950. super();
  6951. this.options = undefined;
  6952. this.parsed = undefined;
  6953. this.skip = undefined;
  6954. this.stop = undefined;
  6955. if (cfg) {
  6956. Object.assign(this, cfg);
  6957. }
  6958. }
  6959. inRange(mouseX, mouseY, useFinalPosition) {
  6960. const options = this.options;
  6961. const { x , y } = this.getProps([
  6962. 'x',
  6963. 'y'
  6964. ], useFinalPosition);
  6965. return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
  6966. }
  6967. inXRange(mouseX, useFinalPosition) {
  6968. return inRange$1(this, mouseX, 'x', useFinalPosition);
  6969. }
  6970. inYRange(mouseY, useFinalPosition) {
  6971. return inRange$1(this, mouseY, 'y', useFinalPosition);
  6972. }
  6973. getCenterPoint(useFinalPosition) {
  6974. const { x , y } = this.getProps([
  6975. 'x',
  6976. 'y'
  6977. ], useFinalPosition);
  6978. return {
  6979. x,
  6980. y
  6981. };
  6982. }
  6983. size(options) {
  6984. options = options || this.options || {};
  6985. let radius = options.radius || 0;
  6986. radius = Math.max(radius, radius && options.hoverRadius || 0);
  6987. const borderWidth = radius && options.borderWidth || 0;
  6988. return (radius + borderWidth) * 2;
  6989. }
  6990. draw(ctx, area) {
  6991. const options = this.options;
  6992. if (this.skip || options.radius < 0.1 || !helpers_segment._isPointInArea(this, area, this.size(options) / 2)) {
  6993. return;
  6994. }
  6995. ctx.strokeStyle = options.borderColor;
  6996. ctx.lineWidth = options.borderWidth;
  6997. ctx.fillStyle = options.backgroundColor;
  6998. helpers_segment.drawPoint(ctx, options, this.x, this.y);
  6999. }
  7000. getRange() {
  7001. const options = this.options || {};
  7002. // @ts-expect-error Fallbacks should never be hit in practice
  7003. return options.radius + options.hitRadius;
  7004. }
  7005. }
  7006. function getBarBounds(bar, useFinalPosition) {
  7007. const { x , y , base , width , height } = bar.getProps([
  7008. 'x',
  7009. 'y',
  7010. 'base',
  7011. 'width',
  7012. 'height'
  7013. ], useFinalPosition);
  7014. let left, right, top, bottom, half;
  7015. if (bar.horizontal) {
  7016. half = height / 2;
  7017. left = Math.min(x, base);
  7018. right = Math.max(x, base);
  7019. top = y - half;
  7020. bottom = y + half;
  7021. } else {
  7022. half = width / 2;
  7023. left = x - half;
  7024. right = x + half;
  7025. top = Math.min(y, base);
  7026. bottom = Math.max(y, base);
  7027. }
  7028. return {
  7029. left,
  7030. top,
  7031. right,
  7032. bottom
  7033. };
  7034. }
  7035. function skipOrLimit(skip, value, min, max) {
  7036. return skip ? 0 : helpers_segment._limitValue(value, min, max);
  7037. }
  7038. function parseBorderWidth(bar, maxW, maxH) {
  7039. const value = bar.options.borderWidth;
  7040. const skip = bar.borderSkipped;
  7041. const o = helpers_segment.toTRBL(value);
  7042. return {
  7043. t: skipOrLimit(skip.top, o.top, 0, maxH),
  7044. r: skipOrLimit(skip.right, o.right, 0, maxW),
  7045. b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
  7046. l: skipOrLimit(skip.left, o.left, 0, maxW)
  7047. };
  7048. }
  7049. function parseBorderRadius(bar, maxW, maxH) {
  7050. const { enableBorderRadius } = bar.getProps([
  7051. 'enableBorderRadius'
  7052. ]);
  7053. const value = bar.options.borderRadius;
  7054. const o = helpers_segment.toTRBLCorners(value);
  7055. const maxR = Math.min(maxW, maxH);
  7056. const skip = bar.borderSkipped;
  7057. const enableBorder = enableBorderRadius || helpers_segment.isObject(value);
  7058. return {
  7059. topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
  7060. topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
  7061. bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
  7062. bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
  7063. };
  7064. }
  7065. function boundingRects(bar) {
  7066. const bounds = getBarBounds(bar);
  7067. const width = bounds.right - bounds.left;
  7068. const height = bounds.bottom - bounds.top;
  7069. const border = parseBorderWidth(bar, width / 2, height / 2);
  7070. const radius = parseBorderRadius(bar, width / 2, height / 2);
  7071. return {
  7072. outer: {
  7073. x: bounds.left,
  7074. y: bounds.top,
  7075. w: width,
  7076. h: height,
  7077. radius
  7078. },
  7079. inner: {
  7080. x: bounds.left + border.l,
  7081. y: bounds.top + border.t,
  7082. w: width - border.l - border.r,
  7083. h: height - border.t - border.b,
  7084. radius: {
  7085. topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
  7086. topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
  7087. bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
  7088. bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
  7089. }
  7090. }
  7091. };
  7092. }
  7093. function inRange(bar, x, y, useFinalPosition) {
  7094. const skipX = x === null;
  7095. const skipY = y === null;
  7096. const skipBoth = skipX && skipY;
  7097. const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
  7098. return bounds && (skipX || helpers_segment._isBetween(x, bounds.left, bounds.right)) && (skipY || helpers_segment._isBetween(y, bounds.top, bounds.bottom));
  7099. }
  7100. function hasRadius(radius) {
  7101. return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  7102. }
  7103. function addNormalRectPath(ctx, rect) {
  7104. ctx.rect(rect.x, rect.y, rect.w, rect.h);
  7105. }
  7106. function inflateRect(rect, amount, refRect = {}) {
  7107. const x = rect.x !== refRect.x ? -amount : 0;
  7108. const y = rect.y !== refRect.y ? -amount : 0;
  7109. const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
  7110. const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
  7111. return {
  7112. x: rect.x + x,
  7113. y: rect.y + y,
  7114. w: rect.w + w,
  7115. h: rect.h + h,
  7116. radius: rect.radius
  7117. };
  7118. }
  7119. class BarElement extends Element {
  7120. static id = 'bar';
  7121. static defaults = {
  7122. borderSkipped: 'start',
  7123. borderWidth: 0,
  7124. borderRadius: 0,
  7125. inflateAmount: 'auto',
  7126. pointStyle: undefined
  7127. };
  7128. static defaultRoutes = {
  7129. backgroundColor: 'backgroundColor',
  7130. borderColor: 'borderColor'
  7131. };
  7132. constructor(cfg){
  7133. super();
  7134. this.options = undefined;
  7135. this.horizontal = undefined;
  7136. this.base = undefined;
  7137. this.width = undefined;
  7138. this.height = undefined;
  7139. this.inflateAmount = undefined;
  7140. if (cfg) {
  7141. Object.assign(this, cfg);
  7142. }
  7143. }
  7144. draw(ctx) {
  7145. const { inflateAmount , options: { borderColor , backgroundColor } } = this;
  7146. const { inner , outer } = boundingRects(this);
  7147. const addRectPath = hasRadius(outer.radius) ? helpers_segment.addRoundedRectPath : addNormalRectPath;
  7148. ctx.save();
  7149. if (outer.w !== inner.w || outer.h !== inner.h) {
  7150. ctx.beginPath();
  7151. addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
  7152. ctx.clip();
  7153. addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
  7154. ctx.fillStyle = borderColor;
  7155. ctx.fill('evenodd');
  7156. }
  7157. ctx.beginPath();
  7158. addRectPath(ctx, inflateRect(inner, inflateAmount));
  7159. ctx.fillStyle = backgroundColor;
  7160. ctx.fill();
  7161. ctx.restore();
  7162. }
  7163. inRange(mouseX, mouseY, useFinalPosition) {
  7164. return inRange(this, mouseX, mouseY, useFinalPosition);
  7165. }
  7166. inXRange(mouseX, useFinalPosition) {
  7167. return inRange(this, mouseX, null, useFinalPosition);
  7168. }
  7169. inYRange(mouseY, useFinalPosition) {
  7170. return inRange(this, null, mouseY, useFinalPosition);
  7171. }
  7172. getCenterPoint(useFinalPosition) {
  7173. const { x , y , base , horizontal } = this.getProps([
  7174. 'x',
  7175. 'y',
  7176. 'base',
  7177. 'horizontal'
  7178. ], useFinalPosition);
  7179. return {
  7180. x: horizontal ? (x + base) / 2 : x,
  7181. y: horizontal ? y : (y + base) / 2
  7182. };
  7183. }
  7184. getRange(axis) {
  7185. return axis === 'x' ? this.width / 2 : this.height / 2;
  7186. }
  7187. }
  7188. var elements = /*#__PURE__*/Object.freeze({
  7189. __proto__: null,
  7190. ArcElement: ArcElement,
  7191. LineElement: LineElement,
  7192. PointElement: PointElement,
  7193. BarElement: BarElement
  7194. });
  7195. const BORDER_COLORS = [
  7196. 'rgb(54, 162, 235)',
  7197. 'rgb(255, 99, 132)',
  7198. 'rgb(255, 159, 64)',
  7199. 'rgb(255, 205, 86)',
  7200. 'rgb(75, 192, 192)',
  7201. 'rgb(153, 102, 255)',
  7202. 'rgb(201, 203, 207)' // grey
  7203. ];
  7204. // Border colors with 50% transparency
  7205. const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
  7206. function getBorderColor(i) {
  7207. return BORDER_COLORS[i % BORDER_COLORS.length];
  7208. }
  7209. function getBackgroundColor(i) {
  7210. return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
  7211. }
  7212. function colorizeDefaultDataset(dataset, i) {
  7213. dataset.borderColor = getBorderColor(i);
  7214. dataset.backgroundColor = getBackgroundColor(i);
  7215. return ++i;
  7216. }
  7217. function colorizeDoughnutDataset(dataset, i) {
  7218. dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
  7219. return i;
  7220. }
  7221. function colorizePolarAreaDataset(dataset, i) {
  7222. dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
  7223. return i;
  7224. }
  7225. function getColorizer(chart) {
  7226. let i = 0;
  7227. return (dataset, datasetIndex)=>{
  7228. const controller = chart.getDatasetMeta(datasetIndex).controller;
  7229. if (controller instanceof DoughnutController) {
  7230. i = colorizeDoughnutDataset(dataset, i);
  7231. } else if (controller instanceof PolarAreaController) {
  7232. i = colorizePolarAreaDataset(dataset, i);
  7233. } else if (controller) {
  7234. i = colorizeDefaultDataset(dataset, i);
  7235. }
  7236. };
  7237. }
  7238. function containsColorsDefinitions(descriptors) {
  7239. let k;
  7240. for(k in descriptors){
  7241. if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
  7242. return true;
  7243. }
  7244. }
  7245. return false;
  7246. }
  7247. function containsColorsDefinition(descriptor) {
  7248. return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
  7249. }
  7250. var plugin_colors = {
  7251. id: 'colors',
  7252. defaults: {
  7253. enabled: true,
  7254. forceOverride: false
  7255. },
  7256. beforeLayout (chart, _args, options) {
  7257. if (!options.enabled) {
  7258. return;
  7259. }
  7260. const { data: { datasets } , options: chartOptions } = chart.config;
  7261. const { elements } = chartOptions;
  7262. if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) {
  7263. return;
  7264. }
  7265. const colorizer = getColorizer(chart);
  7266. datasets.forEach(colorizer);
  7267. }
  7268. };
  7269. function lttbDecimation(data, start, count, availableWidth, options) {
  7270. const samples = options.samples || availableWidth;
  7271. if (samples >= count) {
  7272. return data.slice(start, start + count);
  7273. }
  7274. const decimated = [];
  7275. const bucketWidth = (count - 2) / (samples - 2);
  7276. let sampledIndex = 0;
  7277. const endIndex = start + count - 1;
  7278. let a = start;
  7279. let i, maxAreaPoint, maxArea, area, nextA;
  7280. decimated[sampledIndex++] = data[a];
  7281. for(i = 0; i < samples - 2; i++){
  7282. let avgX = 0;
  7283. let avgY = 0;
  7284. let j;
  7285. const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
  7286. const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
  7287. const avgRangeLength = avgRangeEnd - avgRangeStart;
  7288. for(j = avgRangeStart; j < avgRangeEnd; j++){
  7289. avgX += data[j].x;
  7290. avgY += data[j].y;
  7291. }
  7292. avgX /= avgRangeLength;
  7293. avgY /= avgRangeLength;
  7294. const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
  7295. const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
  7296. const { x: pointAx , y: pointAy } = data[a];
  7297. maxArea = area = -1;
  7298. for(j = rangeOffs; j < rangeTo; j++){
  7299. area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
  7300. if (area > maxArea) {
  7301. maxArea = area;
  7302. maxAreaPoint = data[j];
  7303. nextA = j;
  7304. }
  7305. }
  7306. decimated[sampledIndex++] = maxAreaPoint;
  7307. a = nextA;
  7308. }
  7309. decimated[sampledIndex++] = data[endIndex];
  7310. return decimated;
  7311. }
  7312. function minMaxDecimation(data, start, count, availableWidth) {
  7313. let avgX = 0;
  7314. let countX = 0;
  7315. let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
  7316. const decimated = [];
  7317. const endIndex = start + count - 1;
  7318. const xMin = data[start].x;
  7319. const xMax = data[endIndex].x;
  7320. const dx = xMax - xMin;
  7321. for(i = start; i < start + count; ++i){
  7322. point = data[i];
  7323. x = (point.x - xMin) / dx * availableWidth;
  7324. y = point.y;
  7325. const truncX = x | 0;
  7326. if (truncX === prevX) {
  7327. if (y < minY) {
  7328. minY = y;
  7329. minIndex = i;
  7330. } else if (y > maxY) {
  7331. maxY = y;
  7332. maxIndex = i;
  7333. }
  7334. avgX = (countX * avgX + point.x) / ++countX;
  7335. } else {
  7336. const lastIndex = i - 1;
  7337. if (!helpers_segment.isNullOrUndef(minIndex) && !helpers_segment.isNullOrUndef(maxIndex)) {
  7338. const intermediateIndex1 = Math.min(minIndex, maxIndex);
  7339. const intermediateIndex2 = Math.max(minIndex, maxIndex);
  7340. if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
  7341. decimated.push({
  7342. ...data[intermediateIndex1],
  7343. x: avgX
  7344. });
  7345. }
  7346. if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
  7347. decimated.push({
  7348. ...data[intermediateIndex2],
  7349. x: avgX
  7350. });
  7351. }
  7352. }
  7353. if (i > 0 && lastIndex !== startIndex) {
  7354. decimated.push(data[lastIndex]);
  7355. }
  7356. decimated.push(point);
  7357. prevX = truncX;
  7358. countX = 0;
  7359. minY = maxY = y;
  7360. minIndex = maxIndex = startIndex = i;
  7361. }
  7362. }
  7363. return decimated;
  7364. }
  7365. function cleanDecimatedDataset(dataset) {
  7366. if (dataset._decimated) {
  7367. const data = dataset._data;
  7368. delete dataset._decimated;
  7369. delete dataset._data;
  7370. Object.defineProperty(dataset, 'data', {
  7371. configurable: true,
  7372. enumerable: true,
  7373. writable: true,
  7374. value: data
  7375. });
  7376. }
  7377. }
  7378. function cleanDecimatedData(chart) {
  7379. chart.data.datasets.forEach((dataset)=>{
  7380. cleanDecimatedDataset(dataset);
  7381. });
  7382. }
  7383. function getStartAndCountOfVisiblePointsSimplified(meta, points) {
  7384. const pointCount = points.length;
  7385. let start = 0;
  7386. let count;
  7387. const { iScale } = meta;
  7388. const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
  7389. if (minDefined) {
  7390. start = helpers_segment._limitValue(helpers_segment._lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
  7391. }
  7392. if (maxDefined) {
  7393. count = helpers_segment._limitValue(helpers_segment._lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
  7394. } else {
  7395. count = pointCount - start;
  7396. }
  7397. return {
  7398. start,
  7399. count
  7400. };
  7401. }
  7402. var plugin_decimation = {
  7403. id: 'decimation',
  7404. defaults: {
  7405. algorithm: 'min-max',
  7406. enabled: false
  7407. },
  7408. beforeElementsUpdate: (chart, args, options)=>{
  7409. if (!options.enabled) {
  7410. cleanDecimatedData(chart);
  7411. return;
  7412. }
  7413. const availableWidth = chart.width;
  7414. chart.data.datasets.forEach((dataset, datasetIndex)=>{
  7415. const { _data , indexAxis } = dataset;
  7416. const meta = chart.getDatasetMeta(datasetIndex);
  7417. const data = _data || dataset.data;
  7418. if (helpers_segment.resolve([
  7419. indexAxis,
  7420. chart.options.indexAxis
  7421. ]) === 'y') {
  7422. return;
  7423. }
  7424. if (!meta.controller.supportsDecimation) {
  7425. return;
  7426. }
  7427. const xAxis = chart.scales[meta.xAxisID];
  7428. if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
  7429. return;
  7430. }
  7431. if (chart.options.parsing) {
  7432. return;
  7433. }
  7434. let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
  7435. const threshold = options.threshold || 4 * availableWidth;
  7436. if (count <= threshold) {
  7437. cleanDecimatedDataset(dataset);
  7438. return;
  7439. }
  7440. if (helpers_segment.isNullOrUndef(_data)) {
  7441. dataset._data = data;
  7442. delete dataset.data;
  7443. Object.defineProperty(dataset, 'data', {
  7444. configurable: true,
  7445. enumerable: true,
  7446. get: function() {
  7447. return this._decimated;
  7448. },
  7449. set: function(d) {
  7450. this._data = d;
  7451. }
  7452. });
  7453. }
  7454. let decimated;
  7455. switch(options.algorithm){
  7456. case 'lttb':
  7457. decimated = lttbDecimation(data, start, count, availableWidth, options);
  7458. break;
  7459. case 'min-max':
  7460. decimated = minMaxDecimation(data, start, count, availableWidth);
  7461. break;
  7462. default:
  7463. throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
  7464. }
  7465. dataset._decimated = decimated;
  7466. });
  7467. },
  7468. destroy (chart) {
  7469. cleanDecimatedData(chart);
  7470. }
  7471. };
  7472. function _segments(line, target, property) {
  7473. const segments = line.segments;
  7474. const points = line.points;
  7475. const tpoints = target.points;
  7476. const parts = [];
  7477. for (const segment of segments){
  7478. let { start , end } = segment;
  7479. end = _findSegmentEnd(start, end, points);
  7480. const bounds = _getBounds(property, points[start], points[end], segment.loop);
  7481. if (!target.segments) {
  7482. parts.push({
  7483. source: segment,
  7484. target: bounds,
  7485. start: points[start],
  7486. end: points[end]
  7487. });
  7488. continue;
  7489. }
  7490. const targetSegments = helpers_segment._boundSegments(target, bounds);
  7491. for (const tgt of targetSegments){
  7492. const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
  7493. const fillSources = helpers_segment._boundSegment(segment, points, subBounds);
  7494. for (const fillSource of fillSources){
  7495. parts.push({
  7496. source: fillSource,
  7497. target: tgt,
  7498. start: {
  7499. [property]: _getEdge(bounds, subBounds, 'start', Math.max)
  7500. },
  7501. end: {
  7502. [property]: _getEdge(bounds, subBounds, 'end', Math.min)
  7503. }
  7504. });
  7505. }
  7506. }
  7507. }
  7508. return parts;
  7509. }
  7510. function _getBounds(property, first, last, loop) {
  7511. if (loop) {
  7512. return;
  7513. }
  7514. let start = first[property];
  7515. let end = last[property];
  7516. if (property === 'angle') {
  7517. start = helpers_segment._normalizeAngle(start);
  7518. end = helpers_segment._normalizeAngle(end);
  7519. }
  7520. return {
  7521. property,
  7522. start,
  7523. end
  7524. };
  7525. }
  7526. function _pointsFromSegments(boundary, line) {
  7527. const { x =null , y =null } = boundary || {};
  7528. const linePoints = line.points;
  7529. const points = [];
  7530. line.segments.forEach(({ start , end })=>{
  7531. end = _findSegmentEnd(start, end, linePoints);
  7532. const first = linePoints[start];
  7533. const last = linePoints[end];
  7534. if (y !== null) {
  7535. points.push({
  7536. x: first.x,
  7537. y
  7538. });
  7539. points.push({
  7540. x: last.x,
  7541. y
  7542. });
  7543. } else if (x !== null) {
  7544. points.push({
  7545. x,
  7546. y: first.y
  7547. });
  7548. points.push({
  7549. x,
  7550. y: last.y
  7551. });
  7552. }
  7553. });
  7554. return points;
  7555. }
  7556. function _findSegmentEnd(start, end, points) {
  7557. for(; end > start; end--){
  7558. const point = points[end];
  7559. if (!isNaN(point.x) && !isNaN(point.y)) {
  7560. break;
  7561. }
  7562. }
  7563. return end;
  7564. }
  7565. function _getEdge(a, b, prop, fn) {
  7566. if (a && b) {
  7567. return fn(a[prop], b[prop]);
  7568. }
  7569. return a ? a[prop] : b ? b[prop] : 0;
  7570. }
  7571. function _createBoundaryLine(boundary, line) {
  7572. let points = [];
  7573. let _loop = false;
  7574. if (helpers_segment.isArray(boundary)) {
  7575. _loop = true;
  7576. points = boundary;
  7577. } else {
  7578. points = _pointsFromSegments(boundary, line);
  7579. }
  7580. return points.length ? new LineElement({
  7581. points,
  7582. options: {
  7583. tension: 0
  7584. },
  7585. _loop,
  7586. _fullLoop: _loop
  7587. }) : null;
  7588. }
  7589. function _shouldApplyFill(source) {
  7590. return source && source.fill !== false;
  7591. }
  7592. function _resolveTarget(sources, index, propagate) {
  7593. const source = sources[index];
  7594. let fill = source.fill;
  7595. const visited = [
  7596. index
  7597. ];
  7598. let target;
  7599. if (!propagate) {
  7600. return fill;
  7601. }
  7602. while(fill !== false && visited.indexOf(fill) === -1){
  7603. if (!helpers_segment.isNumberFinite(fill)) {
  7604. return fill;
  7605. }
  7606. target = sources[fill];
  7607. if (!target) {
  7608. return false;
  7609. }
  7610. if (target.visible) {
  7611. return fill;
  7612. }
  7613. visited.push(fill);
  7614. fill = target.fill;
  7615. }
  7616. return false;
  7617. }
  7618. function _decodeFill(line, index, count) {
  7619. const fill = parseFillOption(line);
  7620. if (helpers_segment.isObject(fill)) {
  7621. return isNaN(fill.value) ? false : fill;
  7622. }
  7623. let target = parseFloat(fill);
  7624. if (helpers_segment.isNumberFinite(target) && Math.floor(target) === target) {
  7625. return decodeTargetIndex(fill[0], index, target, count);
  7626. }
  7627. return [
  7628. 'origin',
  7629. 'start',
  7630. 'end',
  7631. 'stack',
  7632. 'shape'
  7633. ].indexOf(fill) >= 0 && fill;
  7634. }
  7635. function decodeTargetIndex(firstCh, index, target, count) {
  7636. if (firstCh === '-' || firstCh === '+') {
  7637. target = index + target;
  7638. }
  7639. if (target === index || target < 0 || target >= count) {
  7640. return false;
  7641. }
  7642. return target;
  7643. }
  7644. function _getTargetPixel(fill, scale) {
  7645. let pixel = null;
  7646. if (fill === 'start') {
  7647. pixel = scale.bottom;
  7648. } else if (fill === 'end') {
  7649. pixel = scale.top;
  7650. } else if (helpers_segment.isObject(fill)) {
  7651. pixel = scale.getPixelForValue(fill.value);
  7652. } else if (scale.getBasePixel) {
  7653. pixel = scale.getBasePixel();
  7654. }
  7655. return pixel;
  7656. }
  7657. function _getTargetValue(fill, scale, startValue) {
  7658. let value;
  7659. if (fill === 'start') {
  7660. value = startValue;
  7661. } else if (fill === 'end') {
  7662. value = scale.options.reverse ? scale.min : scale.max;
  7663. } else if (helpers_segment.isObject(fill)) {
  7664. value = fill.value;
  7665. } else {
  7666. value = scale.getBaseValue();
  7667. }
  7668. return value;
  7669. }
  7670. function parseFillOption(line) {
  7671. const options = line.options;
  7672. const fillOption = options.fill;
  7673. let fill = helpers_segment.valueOrDefault(fillOption && fillOption.target, fillOption);
  7674. if (fill === undefined) {
  7675. fill = !!options.backgroundColor;
  7676. }
  7677. if (fill === false || fill === null) {
  7678. return false;
  7679. }
  7680. if (fill === true) {
  7681. return 'origin';
  7682. }
  7683. return fill;
  7684. }
  7685. function _buildStackLine(source) {
  7686. const { scale , index , line } = source;
  7687. const points = [];
  7688. const segments = line.segments;
  7689. const sourcePoints = line.points;
  7690. const linesBelow = getLinesBelow(scale, index);
  7691. linesBelow.push(_createBoundaryLine({
  7692. x: null,
  7693. y: scale.bottom
  7694. }, line));
  7695. for(let i = 0; i < segments.length; i++){
  7696. const segment = segments[i];
  7697. for(let j = segment.start; j <= segment.end; j++){
  7698. addPointsBelow(points, sourcePoints[j], linesBelow);
  7699. }
  7700. }
  7701. return new LineElement({
  7702. points,
  7703. options: {}
  7704. });
  7705. }
  7706. function getLinesBelow(scale, index) {
  7707. const below = [];
  7708. const metas = scale.getMatchingVisibleMetas('line');
  7709. for(let i = 0; i < metas.length; i++){
  7710. const meta = metas[i];
  7711. if (meta.index === index) {
  7712. break;
  7713. }
  7714. if (!meta.hidden) {
  7715. below.unshift(meta.dataset);
  7716. }
  7717. }
  7718. return below;
  7719. }
  7720. function addPointsBelow(points, sourcePoint, linesBelow) {
  7721. const postponed = [];
  7722. for(let j = 0; j < linesBelow.length; j++){
  7723. const line = linesBelow[j];
  7724. const { first , last , point } = findPoint(line, sourcePoint, 'x');
  7725. if (!point || first && last) {
  7726. continue;
  7727. }
  7728. if (first) {
  7729. postponed.unshift(point);
  7730. } else {
  7731. points.push(point);
  7732. if (!last) {
  7733. break;
  7734. }
  7735. }
  7736. }
  7737. points.push(...postponed);
  7738. }
  7739. function findPoint(line, sourcePoint, property) {
  7740. const point = line.interpolate(sourcePoint, property);
  7741. if (!point) {
  7742. return {};
  7743. }
  7744. const pointValue = point[property];
  7745. const segments = line.segments;
  7746. const linePoints = line.points;
  7747. let first = false;
  7748. let last = false;
  7749. for(let i = 0; i < segments.length; i++){
  7750. const segment = segments[i];
  7751. const firstValue = linePoints[segment.start][property];
  7752. const lastValue = linePoints[segment.end][property];
  7753. if (helpers_segment._isBetween(pointValue, firstValue, lastValue)) {
  7754. first = pointValue === firstValue;
  7755. last = pointValue === lastValue;
  7756. break;
  7757. }
  7758. }
  7759. return {
  7760. first,
  7761. last,
  7762. point
  7763. };
  7764. }
  7765. class simpleArc {
  7766. constructor(opts){
  7767. this.x = opts.x;
  7768. this.y = opts.y;
  7769. this.radius = opts.radius;
  7770. }
  7771. pathSegment(ctx, bounds, opts) {
  7772. const { x , y , radius } = this;
  7773. bounds = bounds || {
  7774. start: 0,
  7775. end: helpers_segment.TAU
  7776. };
  7777. ctx.arc(x, y, radius, bounds.end, bounds.start, true);
  7778. return !opts.bounds;
  7779. }
  7780. interpolate(point) {
  7781. const { x , y , radius } = this;
  7782. const angle = point.angle;
  7783. return {
  7784. x: x + Math.cos(angle) * radius,
  7785. y: y + Math.sin(angle) * radius,
  7786. angle
  7787. };
  7788. }
  7789. }
  7790. function _getTarget(source) {
  7791. const { chart , fill , line } = source;
  7792. if (helpers_segment.isNumberFinite(fill)) {
  7793. return getLineByIndex(chart, fill);
  7794. }
  7795. if (fill === 'stack') {
  7796. return _buildStackLine(source);
  7797. }
  7798. if (fill === 'shape') {
  7799. return true;
  7800. }
  7801. const boundary = computeBoundary(source);
  7802. if (boundary instanceof simpleArc) {
  7803. return boundary;
  7804. }
  7805. return _createBoundaryLine(boundary, line);
  7806. }
  7807. function getLineByIndex(chart, index) {
  7808. const meta = chart.getDatasetMeta(index);
  7809. const visible = meta && chart.isDatasetVisible(index);
  7810. return visible ? meta.dataset : null;
  7811. }
  7812. function computeBoundary(source) {
  7813. const scale = source.scale || {};
  7814. if (scale.getPointPositionForValue) {
  7815. return computeCircularBoundary(source);
  7816. }
  7817. return computeLinearBoundary(source);
  7818. }
  7819. function computeLinearBoundary(source) {
  7820. const { scale ={} , fill } = source;
  7821. const pixel = _getTargetPixel(fill, scale);
  7822. if (helpers_segment.isNumberFinite(pixel)) {
  7823. const horizontal = scale.isHorizontal();
  7824. return {
  7825. x: horizontal ? pixel : null,
  7826. y: horizontal ? null : pixel
  7827. };
  7828. }
  7829. return null;
  7830. }
  7831. function computeCircularBoundary(source) {
  7832. const { scale , fill } = source;
  7833. const options = scale.options;
  7834. const length = scale.getLabels().length;
  7835. const start = options.reverse ? scale.max : scale.min;
  7836. const value = _getTargetValue(fill, scale, start);
  7837. const target = [];
  7838. if (options.grid.circular) {
  7839. const center = scale.getPointPositionForValue(0, start);
  7840. return new simpleArc({
  7841. x: center.x,
  7842. y: center.y,
  7843. radius: scale.getDistanceFromCenterForValue(value)
  7844. });
  7845. }
  7846. for(let i = 0; i < length; ++i){
  7847. target.push(scale.getPointPositionForValue(i, value));
  7848. }
  7849. return target;
  7850. }
  7851. function _drawfill(ctx, source, area) {
  7852. const target = _getTarget(source);
  7853. const { line , scale , axis } = source;
  7854. const lineOpts = line.options;
  7855. const fillOption = lineOpts.fill;
  7856. const color = lineOpts.backgroundColor;
  7857. const { above =color , below =color } = fillOption || {};
  7858. if (target && line.points.length) {
  7859. helpers_segment.clipArea(ctx, area);
  7860. doFill(ctx, {
  7861. line,
  7862. target,
  7863. above,
  7864. below,
  7865. area,
  7866. scale,
  7867. axis
  7868. });
  7869. helpers_segment.unclipArea(ctx);
  7870. }
  7871. }
  7872. function doFill(ctx, cfg) {
  7873. const { line , target , above , below , area , scale } = cfg;
  7874. const property = line._loop ? 'angle' : cfg.axis;
  7875. ctx.save();
  7876. if (property === 'x' && below !== above) {
  7877. clipVertical(ctx, target, area.top);
  7878. fill(ctx, {
  7879. line,
  7880. target,
  7881. color: above,
  7882. scale,
  7883. property
  7884. });
  7885. ctx.restore();
  7886. ctx.save();
  7887. clipVertical(ctx, target, area.bottom);
  7888. }
  7889. fill(ctx, {
  7890. line,
  7891. target,
  7892. color: below,
  7893. scale,
  7894. property
  7895. });
  7896. ctx.restore();
  7897. }
  7898. function clipVertical(ctx, target, clipY) {
  7899. const { segments , points } = target;
  7900. let first = true;
  7901. let lineLoop = false;
  7902. ctx.beginPath();
  7903. for (const segment of segments){
  7904. const { start , end } = segment;
  7905. const firstPoint = points[start];
  7906. const lastPoint = points[_findSegmentEnd(start, end, points)];
  7907. if (first) {
  7908. ctx.moveTo(firstPoint.x, firstPoint.y);
  7909. first = false;
  7910. } else {
  7911. ctx.lineTo(firstPoint.x, clipY);
  7912. ctx.lineTo(firstPoint.x, firstPoint.y);
  7913. }
  7914. lineLoop = !!target.pathSegment(ctx, segment, {
  7915. move: lineLoop
  7916. });
  7917. if (lineLoop) {
  7918. ctx.closePath();
  7919. } else {
  7920. ctx.lineTo(lastPoint.x, clipY);
  7921. }
  7922. }
  7923. ctx.lineTo(target.first().x, clipY);
  7924. ctx.closePath();
  7925. ctx.clip();
  7926. }
  7927. function fill(ctx, cfg) {
  7928. const { line , target , property , color , scale } = cfg;
  7929. const segments = _segments(line, target, property);
  7930. for (const { source: src , target: tgt , start , end } of segments){
  7931. const { style: { backgroundColor =color } = {} } = src;
  7932. const notShape = target !== true;
  7933. ctx.save();
  7934. ctx.fillStyle = backgroundColor;
  7935. clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
  7936. ctx.beginPath();
  7937. const lineLoop = !!line.pathSegment(ctx, src);
  7938. let loop;
  7939. if (notShape) {
  7940. if (lineLoop) {
  7941. ctx.closePath();
  7942. } else {
  7943. interpolatedLineTo(ctx, target, end, property);
  7944. }
  7945. const targetLoop = !!target.pathSegment(ctx, tgt, {
  7946. move: lineLoop,
  7947. reverse: true
  7948. });
  7949. loop = lineLoop && targetLoop;
  7950. if (!loop) {
  7951. interpolatedLineTo(ctx, target, start, property);
  7952. }
  7953. }
  7954. ctx.closePath();
  7955. ctx.fill(loop ? 'evenodd' : 'nonzero');
  7956. ctx.restore();
  7957. }
  7958. }
  7959. function clipBounds(ctx, scale, bounds) {
  7960. const { top , bottom } = scale.chart.chartArea;
  7961. const { property , start , end } = bounds || {};
  7962. if (property === 'x') {
  7963. ctx.beginPath();
  7964. ctx.rect(start, top, end - start, bottom - top);
  7965. ctx.clip();
  7966. }
  7967. }
  7968. function interpolatedLineTo(ctx, target, point, property) {
  7969. const interpolatedPoint = target.interpolate(point, property);
  7970. if (interpolatedPoint) {
  7971. ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  7972. }
  7973. }
  7974. var index = {
  7975. id: 'filler',
  7976. afterDatasetsUpdate (chart, _args, options) {
  7977. const count = (chart.data.datasets || []).length;
  7978. const sources = [];
  7979. let meta, i, line, source;
  7980. for(i = 0; i < count; ++i){
  7981. meta = chart.getDatasetMeta(i);
  7982. line = meta.dataset;
  7983. source = null;
  7984. if (line && line.options && line instanceof LineElement) {
  7985. source = {
  7986. visible: chart.isDatasetVisible(i),
  7987. index: i,
  7988. fill: _decodeFill(line, i, count),
  7989. chart,
  7990. axis: meta.controller.options.indexAxis,
  7991. scale: meta.vScale,
  7992. line
  7993. };
  7994. }
  7995. meta.$filler = source;
  7996. sources.push(source);
  7997. }
  7998. for(i = 0; i < count; ++i){
  7999. source = sources[i];
  8000. if (!source || source.fill === false) {
  8001. continue;
  8002. }
  8003. source.fill = _resolveTarget(sources, i, options.propagate);
  8004. }
  8005. },
  8006. beforeDraw (chart, _args, options) {
  8007. const draw = options.drawTime === 'beforeDraw';
  8008. const metasets = chart.getSortedVisibleDatasetMetas();
  8009. const area = chart.chartArea;
  8010. for(let i = metasets.length - 1; i >= 0; --i){
  8011. const source = metasets[i].$filler;
  8012. if (!source) {
  8013. continue;
  8014. }
  8015. source.line.updateControlPoints(area, source.axis);
  8016. if (draw && source.fill) {
  8017. _drawfill(chart.ctx, source, area);
  8018. }
  8019. }
  8020. },
  8021. beforeDatasetsDraw (chart, _args, options) {
  8022. if (options.drawTime !== 'beforeDatasetsDraw') {
  8023. return;
  8024. }
  8025. const metasets = chart.getSortedVisibleDatasetMetas();
  8026. for(let i = metasets.length - 1; i >= 0; --i){
  8027. const source = metasets[i].$filler;
  8028. if (_shouldApplyFill(source)) {
  8029. _drawfill(chart.ctx, source, chart.chartArea);
  8030. }
  8031. }
  8032. },
  8033. beforeDatasetDraw (chart, args, options) {
  8034. const source = args.meta.$filler;
  8035. if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
  8036. return;
  8037. }
  8038. _drawfill(chart.ctx, source, chart.chartArea);
  8039. },
  8040. defaults: {
  8041. propagate: true,
  8042. drawTime: 'beforeDatasetDraw'
  8043. }
  8044. };
  8045. const getBoxSize = (labelOpts, fontSize)=>{
  8046. let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
  8047. if (labelOpts.usePointStyle) {
  8048. boxHeight = Math.min(boxHeight, fontSize);
  8049. boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
  8050. }
  8051. return {
  8052. boxWidth,
  8053. boxHeight,
  8054. itemHeight: Math.max(fontSize, boxHeight)
  8055. };
  8056. };
  8057. const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  8058. class Legend extends Element {
  8059. constructor(config){
  8060. super();
  8061. this._added = false;
  8062. this.legendHitBoxes = [];
  8063. this._hoveredItem = null;
  8064. this.doughnutMode = false;
  8065. this.chart = config.chart;
  8066. this.options = config.options;
  8067. this.ctx = config.ctx;
  8068. this.legendItems = undefined;
  8069. this.columnSizes = undefined;
  8070. this.lineWidths = undefined;
  8071. this.maxHeight = undefined;
  8072. this.maxWidth = undefined;
  8073. this.top = undefined;
  8074. this.bottom = undefined;
  8075. this.left = undefined;
  8076. this.right = undefined;
  8077. this.height = undefined;
  8078. this.width = undefined;
  8079. this._margins = undefined;
  8080. this.position = undefined;
  8081. this.weight = undefined;
  8082. this.fullSize = undefined;
  8083. }
  8084. update(maxWidth, maxHeight, margins) {
  8085. this.maxWidth = maxWidth;
  8086. this.maxHeight = maxHeight;
  8087. this._margins = margins;
  8088. this.setDimensions();
  8089. this.buildLabels();
  8090. this.fit();
  8091. }
  8092. setDimensions() {
  8093. if (this.isHorizontal()) {
  8094. this.width = this.maxWidth;
  8095. this.left = this._margins.left;
  8096. this.right = this.width;
  8097. } else {
  8098. this.height = this.maxHeight;
  8099. this.top = this._margins.top;
  8100. this.bottom = this.height;
  8101. }
  8102. }
  8103. buildLabels() {
  8104. const labelOpts = this.options.labels || {};
  8105. let legendItems = helpers_segment.callback(labelOpts.generateLabels, [
  8106. this.chart
  8107. ], this) || [];
  8108. if (labelOpts.filter) {
  8109. legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
  8110. }
  8111. if (labelOpts.sort) {
  8112. legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
  8113. }
  8114. if (this.options.reverse) {
  8115. legendItems.reverse();
  8116. }
  8117. this.legendItems = legendItems;
  8118. }
  8119. fit() {
  8120. const { options , ctx } = this;
  8121. if (!options.display) {
  8122. this.width = this.height = 0;
  8123. return;
  8124. }
  8125. const labelOpts = options.labels;
  8126. const labelFont = helpers_segment.toFont(labelOpts.font);
  8127. const fontSize = labelFont.size;
  8128. const titleHeight = this._computeTitleHeight();
  8129. const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
  8130. let width, height;
  8131. ctx.font = labelFont.string;
  8132. if (this.isHorizontal()) {
  8133. width = this.maxWidth;
  8134. height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  8135. } else {
  8136. height = this.maxHeight;
  8137. width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
  8138. }
  8139. this.width = Math.min(width, options.maxWidth || this.maxWidth);
  8140. this.height = Math.min(height, options.maxHeight || this.maxHeight);
  8141. }
  8142. _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
  8143. const { ctx , maxWidth , options: { labels: { padding } } } = this;
  8144. const hitboxes = this.legendHitBoxes = [];
  8145. const lineWidths = this.lineWidths = [
  8146. 0
  8147. ];
  8148. const lineHeight = itemHeight + padding;
  8149. let totalHeight = titleHeight;
  8150. ctx.textAlign = 'left';
  8151. ctx.textBaseline = 'middle';
  8152. let row = -1;
  8153. let top = -lineHeight;
  8154. this.legendItems.forEach((legendItem, i)=>{
  8155. const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
  8156. if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
  8157. totalHeight += lineHeight;
  8158. lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
  8159. top += lineHeight;
  8160. row++;
  8161. }
  8162. hitboxes[i] = {
  8163. left: 0,
  8164. top,
  8165. row,
  8166. width: itemWidth,
  8167. height: itemHeight
  8168. };
  8169. lineWidths[lineWidths.length - 1] += itemWidth + padding;
  8170. });
  8171. return totalHeight;
  8172. }
  8173. _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
  8174. const { ctx , maxHeight , options: { labels: { padding } } } = this;
  8175. const hitboxes = this.legendHitBoxes = [];
  8176. const columnSizes = this.columnSizes = [];
  8177. const heightLimit = maxHeight - titleHeight;
  8178. let totalWidth = padding;
  8179. let currentColWidth = 0;
  8180. let currentColHeight = 0;
  8181. let left = 0;
  8182. let col = 0;
  8183. this.legendItems.forEach((legendItem, i)=>{
  8184. const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
  8185. if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
  8186. totalWidth += currentColWidth + padding;
  8187. columnSizes.push({
  8188. width: currentColWidth,
  8189. height: currentColHeight
  8190. });
  8191. left += currentColWidth + padding;
  8192. col++;
  8193. currentColWidth = currentColHeight = 0;
  8194. }
  8195. hitboxes[i] = {
  8196. left,
  8197. top: currentColHeight,
  8198. col,
  8199. width: itemWidth,
  8200. height: itemHeight
  8201. };
  8202. currentColWidth = Math.max(currentColWidth, itemWidth);
  8203. currentColHeight += itemHeight + padding;
  8204. });
  8205. totalWidth += currentColWidth;
  8206. columnSizes.push({
  8207. width: currentColWidth,
  8208. height: currentColHeight
  8209. });
  8210. return totalWidth;
  8211. }
  8212. adjustHitBoxes() {
  8213. if (!this.options.display) {
  8214. return;
  8215. }
  8216. const titleHeight = this._computeTitleHeight();
  8217. const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
  8218. const rtlHelper = helpers_segment.getRtlAdapter(rtl, this.left, this.width);
  8219. if (this.isHorizontal()) {
  8220. let row = 0;
  8221. let left = helpers_segment._alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8222. for (const hitbox of hitboxes){
  8223. if (row !== hitbox.row) {
  8224. row = hitbox.row;
  8225. left = helpers_segment._alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8226. }
  8227. hitbox.top += this.top + titleHeight + padding;
  8228. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
  8229. left += hitbox.width + padding;
  8230. }
  8231. } else {
  8232. let col = 0;
  8233. let top = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8234. for (const hitbox1 of hitboxes){
  8235. if (hitbox1.col !== col) {
  8236. col = hitbox1.col;
  8237. top = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8238. }
  8239. hitbox1.top = top;
  8240. hitbox1.left += this.left + padding;
  8241. hitbox1.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox1.left), hitbox1.width);
  8242. top += hitbox1.height + padding;
  8243. }
  8244. }
  8245. }
  8246. isHorizontal() {
  8247. return this.options.position === 'top' || this.options.position === 'bottom';
  8248. }
  8249. draw() {
  8250. if (this.options.display) {
  8251. const ctx = this.ctx;
  8252. helpers_segment.clipArea(ctx, this);
  8253. this._draw();
  8254. helpers_segment.unclipArea(ctx);
  8255. }
  8256. }
  8257. _draw() {
  8258. const { options: opts , columnSizes , lineWidths , ctx } = this;
  8259. const { align , labels: labelOpts } = opts;
  8260. const defaultColor = helpers_segment.defaults.color;
  8261. const rtlHelper = helpers_segment.getRtlAdapter(opts.rtl, this.left, this.width);
  8262. const labelFont = helpers_segment.toFont(labelOpts.font);
  8263. const { padding } = labelOpts;
  8264. const fontSize = labelFont.size;
  8265. const halfFontSize = fontSize / 2;
  8266. let cursor;
  8267. this.drawTitle();
  8268. ctx.textAlign = rtlHelper.textAlign('left');
  8269. ctx.textBaseline = 'middle';
  8270. ctx.lineWidth = 0.5;
  8271. ctx.font = labelFont.string;
  8272. const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
  8273. const drawLegendBox = function(x, y, legendItem) {
  8274. if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
  8275. return;
  8276. }
  8277. ctx.save();
  8278. const lineWidth = helpers_segment.valueOrDefault(legendItem.lineWidth, 1);
  8279. ctx.fillStyle = helpers_segment.valueOrDefault(legendItem.fillStyle, defaultColor);
  8280. ctx.lineCap = helpers_segment.valueOrDefault(legendItem.lineCap, 'butt');
  8281. ctx.lineDashOffset = helpers_segment.valueOrDefault(legendItem.lineDashOffset, 0);
  8282. ctx.lineJoin = helpers_segment.valueOrDefault(legendItem.lineJoin, 'miter');
  8283. ctx.lineWidth = lineWidth;
  8284. ctx.strokeStyle = helpers_segment.valueOrDefault(legendItem.strokeStyle, defaultColor);
  8285. ctx.setLineDash(helpers_segment.valueOrDefault(legendItem.lineDash, []));
  8286. if (labelOpts.usePointStyle) {
  8287. const drawOptions = {
  8288. radius: boxHeight * Math.SQRT2 / 2,
  8289. pointStyle: legendItem.pointStyle,
  8290. rotation: legendItem.rotation,
  8291. borderWidth: lineWidth
  8292. };
  8293. const centerX = rtlHelper.xPlus(x, boxWidth / 2);
  8294. const centerY = y + halfFontSize;
  8295. helpers_segment.drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
  8296. } else {
  8297. const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
  8298. const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
  8299. const borderRadius = helpers_segment.toTRBLCorners(legendItem.borderRadius);
  8300. ctx.beginPath();
  8301. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  8302. helpers_segment.addRoundedRectPath(ctx, {
  8303. x: xBoxLeft,
  8304. y: yBoxTop,
  8305. w: boxWidth,
  8306. h: boxHeight,
  8307. radius: borderRadius
  8308. });
  8309. } else {
  8310. ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
  8311. }
  8312. ctx.fill();
  8313. if (lineWidth !== 0) {
  8314. ctx.stroke();
  8315. }
  8316. }
  8317. ctx.restore();
  8318. };
  8319. const fillText = function(x, y, legendItem) {
  8320. helpers_segment.renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
  8321. strikethrough: legendItem.hidden,
  8322. textAlign: rtlHelper.textAlign(legendItem.textAlign)
  8323. });
  8324. };
  8325. const isHorizontal = this.isHorizontal();
  8326. const titleHeight = this._computeTitleHeight();
  8327. if (isHorizontal) {
  8328. cursor = {
  8329. x: helpers_segment._alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
  8330. y: this.top + padding + titleHeight,
  8331. line: 0
  8332. };
  8333. } else {
  8334. cursor = {
  8335. x: this.left + padding,
  8336. y: helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
  8337. line: 0
  8338. };
  8339. }
  8340. helpers_segment.overrideTextDirection(this.ctx, opts.textDirection);
  8341. const lineHeight = itemHeight + padding;
  8342. this.legendItems.forEach((legendItem, i)=>{
  8343. ctx.strokeStyle = legendItem.fontColor;
  8344. ctx.fillStyle = legendItem.fontColor;
  8345. const textWidth = ctx.measureText(legendItem.text).width;
  8346. const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
  8347. const width = boxWidth + halfFontSize + textWidth;
  8348. let x = cursor.x;
  8349. let y = cursor.y;
  8350. rtlHelper.setWidth(this.width);
  8351. if (isHorizontal) {
  8352. if (i > 0 && x + width + padding > this.right) {
  8353. y = cursor.y += lineHeight;
  8354. cursor.line++;
  8355. x = cursor.x = helpers_segment._alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
  8356. }
  8357. } else if (i > 0 && y + lineHeight > this.bottom) {
  8358. x = cursor.x = x + columnSizes[cursor.line].width + padding;
  8359. cursor.line++;
  8360. y = cursor.y = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
  8361. }
  8362. const realX = rtlHelper.x(x);
  8363. drawLegendBox(realX, y, legendItem);
  8364. x = helpers_segment._textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
  8365. fillText(rtlHelper.x(x), y, legendItem);
  8366. if (isHorizontal) {
  8367. cursor.x += width + padding;
  8368. } else if (typeof legendItem.text !== 'string') {
  8369. const fontLineHeight = labelFont.lineHeight;
  8370. cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight);
  8371. } else {
  8372. cursor.y += lineHeight;
  8373. }
  8374. });
  8375. helpers_segment.restoreTextDirection(this.ctx, opts.textDirection);
  8376. }
  8377. drawTitle() {
  8378. const opts = this.options;
  8379. const titleOpts = opts.title;
  8380. const titleFont = helpers_segment.toFont(titleOpts.font);
  8381. const titlePadding = helpers_segment.toPadding(titleOpts.padding);
  8382. if (!titleOpts.display) {
  8383. return;
  8384. }
  8385. const rtlHelper = helpers_segment.getRtlAdapter(opts.rtl, this.left, this.width);
  8386. const ctx = this.ctx;
  8387. const position = titleOpts.position;
  8388. const halfFontSize = titleFont.size / 2;
  8389. const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
  8390. let y;
  8391. let left = this.left;
  8392. let maxWidth = this.width;
  8393. if (this.isHorizontal()) {
  8394. maxWidth = Math.max(...this.lineWidths);
  8395. y = this.top + topPaddingPlusHalfFontSize;
  8396. left = helpers_segment._alignStartEnd(opts.align, left, this.right - maxWidth);
  8397. } else {
  8398. const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
  8399. y = topPaddingPlusHalfFontSize + helpers_segment._alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
  8400. }
  8401. const x = helpers_segment._alignStartEnd(position, left, left + maxWidth);
  8402. ctx.textAlign = rtlHelper.textAlign(helpers_segment._toLeftRightCenter(position));
  8403. ctx.textBaseline = 'middle';
  8404. ctx.strokeStyle = titleOpts.color;
  8405. ctx.fillStyle = titleOpts.color;
  8406. ctx.font = titleFont.string;
  8407. helpers_segment.renderText(ctx, titleOpts.text, x, y, titleFont);
  8408. }
  8409. _computeTitleHeight() {
  8410. const titleOpts = this.options.title;
  8411. const titleFont = helpers_segment.toFont(titleOpts.font);
  8412. const titlePadding = helpers_segment.toPadding(titleOpts.padding);
  8413. return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
  8414. }
  8415. _getLegendItemAt(x, y) {
  8416. let i, hitBox, lh;
  8417. if (helpers_segment._isBetween(x, this.left, this.right) && helpers_segment._isBetween(y, this.top, this.bottom)) {
  8418. lh = this.legendHitBoxes;
  8419. for(i = 0; i < lh.length; ++i){
  8420. hitBox = lh[i];
  8421. if (helpers_segment._isBetween(x, hitBox.left, hitBox.left + hitBox.width) && helpers_segment._isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
  8422. return this.legendItems[i];
  8423. }
  8424. }
  8425. }
  8426. return null;
  8427. }
  8428. handleEvent(e) {
  8429. const opts = this.options;
  8430. if (!isListened(e.type, opts)) {
  8431. return;
  8432. }
  8433. const hoveredItem = this._getLegendItemAt(e.x, e.y);
  8434. if (e.type === 'mousemove' || e.type === 'mouseout') {
  8435. const previous = this._hoveredItem;
  8436. const sameItem = itemsEqual(previous, hoveredItem);
  8437. if (previous && !sameItem) {
  8438. helpers_segment.callback(opts.onLeave, [
  8439. e,
  8440. previous,
  8441. this
  8442. ], this);
  8443. }
  8444. this._hoveredItem = hoveredItem;
  8445. if (hoveredItem && !sameItem) {
  8446. helpers_segment.callback(opts.onHover, [
  8447. e,
  8448. hoveredItem,
  8449. this
  8450. ], this);
  8451. }
  8452. } else if (hoveredItem) {
  8453. helpers_segment.callback(opts.onClick, [
  8454. e,
  8455. hoveredItem,
  8456. this
  8457. ], this);
  8458. }
  8459. }
  8460. }
  8461. function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
  8462. const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
  8463. const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
  8464. return {
  8465. itemWidth,
  8466. itemHeight
  8467. };
  8468. }
  8469. function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
  8470. let legendItemText = legendItem.text;
  8471. if (legendItemText && typeof legendItemText !== 'string') {
  8472. legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
  8473. }
  8474. return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
  8475. }
  8476. function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
  8477. let itemHeight = _itemHeight;
  8478. if (typeof legendItem.text !== 'string') {
  8479. itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
  8480. }
  8481. return itemHeight;
  8482. }
  8483. function calculateLegendItemHeight(legendItem, fontLineHeight) {
  8484. const labelHeight = legendItem.text ? legendItem.text.length + 0.5 : 0;
  8485. return fontLineHeight * labelHeight;
  8486. }
  8487. function isListened(type, opts) {
  8488. if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
  8489. return true;
  8490. }
  8491. if (opts.onClick && (type === 'click' || type === 'mouseup')) {
  8492. return true;
  8493. }
  8494. return false;
  8495. }
  8496. var plugin_legend = {
  8497. id: 'legend',
  8498. _element: Legend,
  8499. start (chart, _args, options) {
  8500. const legend = chart.legend = new Legend({
  8501. ctx: chart.ctx,
  8502. options,
  8503. chart
  8504. });
  8505. layouts.configure(chart, legend, options);
  8506. layouts.addBox(chart, legend);
  8507. },
  8508. stop (chart) {
  8509. layouts.removeBox(chart, chart.legend);
  8510. delete chart.legend;
  8511. },
  8512. beforeUpdate (chart, _args, options) {
  8513. const legend = chart.legend;
  8514. layouts.configure(chart, legend, options);
  8515. legend.options = options;
  8516. },
  8517. afterUpdate (chart) {
  8518. const legend = chart.legend;
  8519. legend.buildLabels();
  8520. legend.adjustHitBoxes();
  8521. },
  8522. afterEvent (chart, args) {
  8523. if (!args.replay) {
  8524. chart.legend.handleEvent(args.event);
  8525. }
  8526. },
  8527. defaults: {
  8528. display: true,
  8529. position: 'top',
  8530. align: 'center',
  8531. fullSize: true,
  8532. reverse: false,
  8533. weight: 1000,
  8534. onClick (e, legendItem, legend) {
  8535. const index = legendItem.datasetIndex;
  8536. const ci = legend.chart;
  8537. if (ci.isDatasetVisible(index)) {
  8538. ci.hide(index);
  8539. legendItem.hidden = true;
  8540. } else {
  8541. ci.show(index);
  8542. legendItem.hidden = false;
  8543. }
  8544. },
  8545. onHover: null,
  8546. onLeave: null,
  8547. labels: {
  8548. color: (ctx)=>ctx.chart.options.color,
  8549. boxWidth: 40,
  8550. padding: 10,
  8551. generateLabels (chart) {
  8552. const datasets = chart.data.datasets;
  8553. const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
  8554. return chart._getSortedDatasetMetas().map((meta)=>{
  8555. const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
  8556. const borderWidth = helpers_segment.toPadding(style.borderWidth);
  8557. return {
  8558. text: datasets[meta.index].label,
  8559. fillStyle: style.backgroundColor,
  8560. fontColor: color,
  8561. hidden: !meta.visible,
  8562. lineCap: style.borderCapStyle,
  8563. lineDash: style.borderDash,
  8564. lineDashOffset: style.borderDashOffset,
  8565. lineJoin: style.borderJoinStyle,
  8566. lineWidth: (borderWidth.width + borderWidth.height) / 4,
  8567. strokeStyle: style.borderColor,
  8568. pointStyle: pointStyle || style.pointStyle,
  8569. rotation: style.rotation,
  8570. textAlign: textAlign || style.textAlign,
  8571. borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
  8572. datasetIndex: meta.index
  8573. };
  8574. }, this);
  8575. }
  8576. },
  8577. title: {
  8578. color: (ctx)=>ctx.chart.options.color,
  8579. display: false,
  8580. position: 'center',
  8581. text: ''
  8582. }
  8583. },
  8584. descriptors: {
  8585. _scriptable: (name)=>!name.startsWith('on'),
  8586. labels: {
  8587. _scriptable: (name)=>![
  8588. 'generateLabels',
  8589. 'filter',
  8590. 'sort'
  8591. ].includes(name)
  8592. }
  8593. }
  8594. };
  8595. class Title extends Element {
  8596. constructor(config){
  8597. super();
  8598. this.chart = config.chart;
  8599. this.options = config.options;
  8600. this.ctx = config.ctx;
  8601. this._padding = undefined;
  8602. this.top = undefined;
  8603. this.bottom = undefined;
  8604. this.left = undefined;
  8605. this.right = undefined;
  8606. this.width = undefined;
  8607. this.height = undefined;
  8608. this.position = undefined;
  8609. this.weight = undefined;
  8610. this.fullSize = undefined;
  8611. }
  8612. update(maxWidth, maxHeight) {
  8613. const opts = this.options;
  8614. this.left = 0;
  8615. this.top = 0;
  8616. if (!opts.display) {
  8617. this.width = this.height = this.right = this.bottom = 0;
  8618. return;
  8619. }
  8620. this.width = this.right = maxWidth;
  8621. this.height = this.bottom = maxHeight;
  8622. const lineCount = helpers_segment.isArray(opts.text) ? opts.text.length : 1;
  8623. this._padding = helpers_segment.toPadding(opts.padding);
  8624. const textSize = lineCount * helpers_segment.toFont(opts.font).lineHeight + this._padding.height;
  8625. if (this.isHorizontal()) {
  8626. this.height = textSize;
  8627. } else {
  8628. this.width = textSize;
  8629. }
  8630. }
  8631. isHorizontal() {
  8632. const pos = this.options.position;
  8633. return pos === 'top' || pos === 'bottom';
  8634. }
  8635. _drawArgs(offset) {
  8636. const { top , left , bottom , right , options } = this;
  8637. const align = options.align;
  8638. let rotation = 0;
  8639. let maxWidth, titleX, titleY;
  8640. if (this.isHorizontal()) {
  8641. titleX = helpers_segment._alignStartEnd(align, left, right);
  8642. titleY = top + offset;
  8643. maxWidth = right - left;
  8644. } else {
  8645. if (options.position === 'left') {
  8646. titleX = left + offset;
  8647. titleY = helpers_segment._alignStartEnd(align, bottom, top);
  8648. rotation = helpers_segment.PI * -0.5;
  8649. } else {
  8650. titleX = right - offset;
  8651. titleY = helpers_segment._alignStartEnd(align, top, bottom);
  8652. rotation = helpers_segment.PI * 0.5;
  8653. }
  8654. maxWidth = bottom - top;
  8655. }
  8656. return {
  8657. titleX,
  8658. titleY,
  8659. maxWidth,
  8660. rotation
  8661. };
  8662. }
  8663. draw() {
  8664. const ctx = this.ctx;
  8665. const opts = this.options;
  8666. if (!opts.display) {
  8667. return;
  8668. }
  8669. const fontOpts = helpers_segment.toFont(opts.font);
  8670. const lineHeight = fontOpts.lineHeight;
  8671. const offset = lineHeight / 2 + this._padding.top;
  8672. const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
  8673. helpers_segment.renderText(ctx, opts.text, 0, 0, fontOpts, {
  8674. color: opts.color,
  8675. maxWidth,
  8676. rotation,
  8677. textAlign: helpers_segment._toLeftRightCenter(opts.align),
  8678. textBaseline: 'middle',
  8679. translation: [
  8680. titleX,
  8681. titleY
  8682. ]
  8683. });
  8684. }
  8685. }
  8686. function createTitle(chart, titleOpts) {
  8687. const title = new Title({
  8688. ctx: chart.ctx,
  8689. options: titleOpts,
  8690. chart
  8691. });
  8692. layouts.configure(chart, title, titleOpts);
  8693. layouts.addBox(chart, title);
  8694. chart.titleBlock = title;
  8695. }
  8696. var plugin_title = {
  8697. id: 'title',
  8698. _element: Title,
  8699. start (chart, _args, options) {
  8700. createTitle(chart, options);
  8701. },
  8702. stop (chart) {
  8703. const titleBlock = chart.titleBlock;
  8704. layouts.removeBox(chart, titleBlock);
  8705. delete chart.titleBlock;
  8706. },
  8707. beforeUpdate (chart, _args, options) {
  8708. const title = chart.titleBlock;
  8709. layouts.configure(chart, title, options);
  8710. title.options = options;
  8711. },
  8712. defaults: {
  8713. align: 'center',
  8714. display: false,
  8715. font: {
  8716. weight: 'bold'
  8717. },
  8718. fullSize: true,
  8719. padding: 10,
  8720. position: 'top',
  8721. text: '',
  8722. weight: 2000
  8723. },
  8724. defaultRoutes: {
  8725. color: 'color'
  8726. },
  8727. descriptors: {
  8728. _scriptable: true,
  8729. _indexable: false
  8730. }
  8731. };
  8732. const map = new WeakMap();
  8733. var plugin_subtitle = {
  8734. id: 'subtitle',
  8735. start (chart, _args, options) {
  8736. const title = new Title({
  8737. ctx: chart.ctx,
  8738. options,
  8739. chart
  8740. });
  8741. layouts.configure(chart, title, options);
  8742. layouts.addBox(chart, title);
  8743. map.set(chart, title);
  8744. },
  8745. stop (chart) {
  8746. layouts.removeBox(chart, map.get(chart));
  8747. map.delete(chart);
  8748. },
  8749. beforeUpdate (chart, _args, options) {
  8750. const title = map.get(chart);
  8751. layouts.configure(chart, title, options);
  8752. title.options = options;
  8753. },
  8754. defaults: {
  8755. align: 'center',
  8756. display: false,
  8757. font: {
  8758. weight: 'normal'
  8759. },
  8760. fullSize: true,
  8761. padding: 0,
  8762. position: 'top',
  8763. text: '',
  8764. weight: 1500
  8765. },
  8766. defaultRoutes: {
  8767. color: 'color'
  8768. },
  8769. descriptors: {
  8770. _scriptable: true,
  8771. _indexable: false
  8772. }
  8773. };
  8774. const positioners = {
  8775. average (items) {
  8776. if (!items.length) {
  8777. return false;
  8778. }
  8779. let i, len;
  8780. let x = 0;
  8781. let y = 0;
  8782. let count = 0;
  8783. for(i = 0, len = items.length; i < len; ++i){
  8784. const el = items[i].element;
  8785. if (el && el.hasValue()) {
  8786. const pos = el.tooltipPosition();
  8787. x += pos.x;
  8788. y += pos.y;
  8789. ++count;
  8790. }
  8791. }
  8792. return {
  8793. x: x / count,
  8794. y: y / count
  8795. };
  8796. },
  8797. nearest (items, eventPosition) {
  8798. if (!items.length) {
  8799. return false;
  8800. }
  8801. let x = eventPosition.x;
  8802. let y = eventPosition.y;
  8803. let minDistance = Number.POSITIVE_INFINITY;
  8804. let i, len, nearestElement;
  8805. for(i = 0, len = items.length; i < len; ++i){
  8806. const el = items[i].element;
  8807. if (el && el.hasValue()) {
  8808. const center = el.getCenterPoint();
  8809. const d = helpers_segment.distanceBetweenPoints(eventPosition, center);
  8810. if (d < minDistance) {
  8811. minDistance = d;
  8812. nearestElement = el;
  8813. }
  8814. }
  8815. }
  8816. if (nearestElement) {
  8817. const tp = nearestElement.tooltipPosition();
  8818. x = tp.x;
  8819. y = tp.y;
  8820. }
  8821. return {
  8822. x,
  8823. y
  8824. };
  8825. }
  8826. };
  8827. function pushOrConcat(base, toPush) {
  8828. if (toPush) {
  8829. if (helpers_segment.isArray(toPush)) {
  8830. Array.prototype.push.apply(base, toPush);
  8831. } else {
  8832. base.push(toPush);
  8833. }
  8834. }
  8835. return base;
  8836. }
  8837. function splitNewlines(str) {
  8838. if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  8839. return str.split('\n');
  8840. }
  8841. return str;
  8842. }
  8843. function createTooltipItem(chart, item) {
  8844. const { element , datasetIndex , index } = item;
  8845. const controller = chart.getDatasetMeta(datasetIndex).controller;
  8846. const { label , value } = controller.getLabelAndValue(index);
  8847. return {
  8848. chart,
  8849. label,
  8850. parsed: controller.getParsed(index),
  8851. raw: chart.data.datasets[datasetIndex].data[index],
  8852. formattedValue: value,
  8853. dataset: controller.getDataset(),
  8854. dataIndex: index,
  8855. datasetIndex,
  8856. element
  8857. };
  8858. }
  8859. function getTooltipSize(tooltip, options) {
  8860. const ctx = tooltip.chart.ctx;
  8861. const { body , footer , title } = tooltip;
  8862. const { boxWidth , boxHeight } = options;
  8863. const bodyFont = helpers_segment.toFont(options.bodyFont);
  8864. const titleFont = helpers_segment.toFont(options.titleFont);
  8865. const footerFont = helpers_segment.toFont(options.footerFont);
  8866. const titleLineCount = title.length;
  8867. const footerLineCount = footer.length;
  8868. const bodyLineItemCount = body.length;
  8869. const padding = helpers_segment.toPadding(options.padding);
  8870. let height = padding.height;
  8871. let width = 0;
  8872. let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
  8873. combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
  8874. if (titleLineCount) {
  8875. height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
  8876. }
  8877. if (combinedBodyLength) {
  8878. const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
  8879. height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
  8880. }
  8881. if (footerLineCount) {
  8882. height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
  8883. }
  8884. let widthPadding = 0;
  8885. const maxLineWidth = function(line) {
  8886. width = Math.max(width, ctx.measureText(line).width + widthPadding);
  8887. };
  8888. ctx.save();
  8889. ctx.font = titleFont.string;
  8890. helpers_segment.each(tooltip.title, maxLineWidth);
  8891. ctx.font = bodyFont.string;
  8892. helpers_segment.each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
  8893. widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
  8894. helpers_segment.each(body, (bodyItem)=>{
  8895. helpers_segment.each(bodyItem.before, maxLineWidth);
  8896. helpers_segment.each(bodyItem.lines, maxLineWidth);
  8897. helpers_segment.each(bodyItem.after, maxLineWidth);
  8898. });
  8899. widthPadding = 0;
  8900. ctx.font = footerFont.string;
  8901. helpers_segment.each(tooltip.footer, maxLineWidth);
  8902. ctx.restore();
  8903. width += padding.width;
  8904. return {
  8905. width,
  8906. height
  8907. };
  8908. }
  8909. function determineYAlign(chart, size) {
  8910. const { y , height } = size;
  8911. if (y < height / 2) {
  8912. return 'top';
  8913. } else if (y > chart.height - height / 2) {
  8914. return 'bottom';
  8915. }
  8916. return 'center';
  8917. }
  8918. function doesNotFitWithAlign(xAlign, chart, options, size) {
  8919. const { x , width } = size;
  8920. const caret = options.caretSize + options.caretPadding;
  8921. if (xAlign === 'left' && x + width + caret > chart.width) {
  8922. return true;
  8923. }
  8924. if (xAlign === 'right' && x - width - caret < 0) {
  8925. return true;
  8926. }
  8927. }
  8928. function determineXAlign(chart, options, size, yAlign) {
  8929. const { x , width } = size;
  8930. const { width: chartWidth , chartArea: { left , right } } = chart;
  8931. let xAlign = 'center';
  8932. if (yAlign === 'center') {
  8933. xAlign = x <= (left + right) / 2 ? 'left' : 'right';
  8934. } else if (x <= width / 2) {
  8935. xAlign = 'left';
  8936. } else if (x >= chartWidth - width / 2) {
  8937. xAlign = 'right';
  8938. }
  8939. if (doesNotFitWithAlign(xAlign, chart, options, size)) {
  8940. xAlign = 'center';
  8941. }
  8942. return xAlign;
  8943. }
  8944. function determineAlignment(chart, options, size) {
  8945. const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
  8946. return {
  8947. xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
  8948. yAlign
  8949. };
  8950. }
  8951. function alignX(size, xAlign) {
  8952. let { x , width } = size;
  8953. if (xAlign === 'right') {
  8954. x -= width;
  8955. } else if (xAlign === 'center') {
  8956. x -= width / 2;
  8957. }
  8958. return x;
  8959. }
  8960. function alignY(size, yAlign, paddingAndSize) {
  8961. let { y , height } = size;
  8962. if (yAlign === 'top') {
  8963. y += paddingAndSize;
  8964. } else if (yAlign === 'bottom') {
  8965. y -= height + paddingAndSize;
  8966. } else {
  8967. y -= height / 2;
  8968. }
  8969. return y;
  8970. }
  8971. function getBackgroundPoint(options, size, alignment, chart) {
  8972. const { caretSize , caretPadding , cornerRadius } = options;
  8973. const { xAlign , yAlign } = alignment;
  8974. const paddingAndSize = caretSize + caretPadding;
  8975. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(cornerRadius);
  8976. let x = alignX(size, xAlign);
  8977. const y = alignY(size, yAlign, paddingAndSize);
  8978. if (yAlign === 'center') {
  8979. if (xAlign === 'left') {
  8980. x += paddingAndSize;
  8981. } else if (xAlign === 'right') {
  8982. x -= paddingAndSize;
  8983. }
  8984. } else if (xAlign === 'left') {
  8985. x -= Math.max(topLeft, bottomLeft) + caretSize;
  8986. } else if (xAlign === 'right') {
  8987. x += Math.max(topRight, bottomRight) + caretSize;
  8988. }
  8989. return {
  8990. x: helpers_segment._limitValue(x, 0, chart.width - size.width),
  8991. y: helpers_segment._limitValue(y, 0, chart.height - size.height)
  8992. };
  8993. }
  8994. function getAlignedX(tooltip, align, options) {
  8995. const padding = helpers_segment.toPadding(options.padding);
  8996. return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
  8997. }
  8998. function getBeforeAfterBodyLines(callback) {
  8999. return pushOrConcat([], splitNewlines(callback));
  9000. }
  9001. function createTooltipContext(parent, tooltip, tooltipItems) {
  9002. return helpers_segment.createContext(parent, {
  9003. tooltip,
  9004. tooltipItems,
  9005. type: 'tooltip'
  9006. });
  9007. }
  9008. function overrideCallbacks(callbacks, context) {
  9009. const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  9010. return override ? callbacks.override(override) : callbacks;
  9011. }
  9012. const defaultCallbacks = {
  9013. beforeTitle: helpers_segment.noop,
  9014. title (tooltipItems) {
  9015. if (tooltipItems.length > 0) {
  9016. const item = tooltipItems[0];
  9017. const labels = item.chart.data.labels;
  9018. const labelCount = labels ? labels.length : 0;
  9019. if (this && this.options && this.options.mode === 'dataset') {
  9020. return item.dataset.label || '';
  9021. } else if (item.label) {
  9022. return item.label;
  9023. } else if (labelCount > 0 && item.dataIndex < labelCount) {
  9024. return labels[item.dataIndex];
  9025. }
  9026. }
  9027. return '';
  9028. },
  9029. afterTitle: helpers_segment.noop,
  9030. beforeBody: helpers_segment.noop,
  9031. beforeLabel: helpers_segment.noop,
  9032. label (tooltipItem) {
  9033. if (this && this.options && this.options.mode === 'dataset') {
  9034. return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
  9035. }
  9036. let label = tooltipItem.dataset.label || '';
  9037. if (label) {
  9038. label += ': ';
  9039. }
  9040. const value = tooltipItem.formattedValue;
  9041. if (!helpers_segment.isNullOrUndef(value)) {
  9042. label += value;
  9043. }
  9044. return label;
  9045. },
  9046. labelColor (tooltipItem) {
  9047. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9048. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9049. return {
  9050. borderColor: options.borderColor,
  9051. backgroundColor: options.backgroundColor,
  9052. borderWidth: options.borderWidth,
  9053. borderDash: options.borderDash,
  9054. borderDashOffset: options.borderDashOffset,
  9055. borderRadius: 0
  9056. };
  9057. },
  9058. labelTextColor () {
  9059. return this.options.bodyColor;
  9060. },
  9061. labelPointStyle (tooltipItem) {
  9062. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9063. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9064. return {
  9065. pointStyle: options.pointStyle,
  9066. rotation: options.rotation
  9067. };
  9068. },
  9069. afterLabel: helpers_segment.noop,
  9070. afterBody: helpers_segment.noop,
  9071. beforeFooter: helpers_segment.noop,
  9072. footer: helpers_segment.noop,
  9073. afterFooter: helpers_segment.noop
  9074. };
  9075. function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
  9076. const result = callbacks[name].call(ctx, arg);
  9077. if (typeof result === 'undefined') {
  9078. return defaultCallbacks[name].call(ctx, arg);
  9079. }
  9080. return result;
  9081. }
  9082. class Tooltip extends Element {
  9083. static positioners = positioners;
  9084. constructor(config){
  9085. super();
  9086. this.opacity = 0;
  9087. this._active = [];
  9088. this._eventPosition = undefined;
  9089. this._size = undefined;
  9090. this._cachedAnimations = undefined;
  9091. this._tooltipItems = [];
  9092. this.$animations = undefined;
  9093. this.$context = undefined;
  9094. this.chart = config.chart;
  9095. this.options = config.options;
  9096. this.dataPoints = undefined;
  9097. this.title = undefined;
  9098. this.beforeBody = undefined;
  9099. this.body = undefined;
  9100. this.afterBody = undefined;
  9101. this.footer = undefined;
  9102. this.xAlign = undefined;
  9103. this.yAlign = undefined;
  9104. this.x = undefined;
  9105. this.y = undefined;
  9106. this.height = undefined;
  9107. this.width = undefined;
  9108. this.caretX = undefined;
  9109. this.caretY = undefined;
  9110. this.labelColors = undefined;
  9111. this.labelPointStyles = undefined;
  9112. this.labelTextColors = undefined;
  9113. }
  9114. initialize(options) {
  9115. this.options = options;
  9116. this._cachedAnimations = undefined;
  9117. this.$context = undefined;
  9118. }
  9119. _resolveAnimations() {
  9120. const cached = this._cachedAnimations;
  9121. if (cached) {
  9122. return cached;
  9123. }
  9124. const chart = this.chart;
  9125. const options = this.options.setContext(this.getContext());
  9126. const opts = options.enabled && chart.options.animation && options.animations;
  9127. const animations = new Animations(this.chart, opts);
  9128. if (opts._cacheable) {
  9129. this._cachedAnimations = Object.freeze(animations);
  9130. }
  9131. return animations;
  9132. }
  9133. getContext() {
  9134. return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
  9135. }
  9136. getTitle(context, options) {
  9137. const { callbacks } = options;
  9138. const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
  9139. const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
  9140. const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
  9141. let lines = [];
  9142. lines = pushOrConcat(lines, splitNewlines(beforeTitle));
  9143. lines = pushOrConcat(lines, splitNewlines(title));
  9144. lines = pushOrConcat(lines, splitNewlines(afterTitle));
  9145. return lines;
  9146. }
  9147. getBeforeBody(tooltipItems, options) {
  9148. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
  9149. }
  9150. getBody(tooltipItems, options) {
  9151. const { callbacks } = options;
  9152. const bodyItems = [];
  9153. helpers_segment.each(tooltipItems, (context)=>{
  9154. const bodyItem = {
  9155. before: [],
  9156. lines: [],
  9157. after: []
  9158. };
  9159. const scoped = overrideCallbacks(callbacks, context);
  9160. pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
  9161. pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
  9162. pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
  9163. bodyItems.push(bodyItem);
  9164. });
  9165. return bodyItems;
  9166. }
  9167. getAfterBody(tooltipItems, options) {
  9168. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
  9169. }
  9170. getFooter(tooltipItems, options) {
  9171. const { callbacks } = options;
  9172. const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
  9173. const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
  9174. const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
  9175. let lines = [];
  9176. lines = pushOrConcat(lines, splitNewlines(beforeFooter));
  9177. lines = pushOrConcat(lines, splitNewlines(footer));
  9178. lines = pushOrConcat(lines, splitNewlines(afterFooter));
  9179. return lines;
  9180. }
  9181. _createItems(options) {
  9182. const active = this._active;
  9183. const data = this.chart.data;
  9184. const labelColors = [];
  9185. const labelPointStyles = [];
  9186. const labelTextColors = [];
  9187. let tooltipItems = [];
  9188. let i, len;
  9189. for(i = 0, len = active.length; i < len; ++i){
  9190. tooltipItems.push(createTooltipItem(this.chart, active[i]));
  9191. }
  9192. if (options.filter) {
  9193. tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
  9194. }
  9195. if (options.itemSort) {
  9196. tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
  9197. }
  9198. helpers_segment.each(tooltipItems, (context)=>{
  9199. const scoped = overrideCallbacks(options.callbacks, context);
  9200. labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
  9201. labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
  9202. labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
  9203. });
  9204. this.labelColors = labelColors;
  9205. this.labelPointStyles = labelPointStyles;
  9206. this.labelTextColors = labelTextColors;
  9207. this.dataPoints = tooltipItems;
  9208. return tooltipItems;
  9209. }
  9210. update(changed, replay) {
  9211. const options = this.options.setContext(this.getContext());
  9212. const active = this._active;
  9213. let properties;
  9214. let tooltipItems = [];
  9215. if (!active.length) {
  9216. if (this.opacity !== 0) {
  9217. properties = {
  9218. opacity: 0
  9219. };
  9220. }
  9221. } else {
  9222. const position = positioners[options.position].call(this, active, this._eventPosition);
  9223. tooltipItems = this._createItems(options);
  9224. this.title = this.getTitle(tooltipItems, options);
  9225. this.beforeBody = this.getBeforeBody(tooltipItems, options);
  9226. this.body = this.getBody(tooltipItems, options);
  9227. this.afterBody = this.getAfterBody(tooltipItems, options);
  9228. this.footer = this.getFooter(tooltipItems, options);
  9229. const size = this._size = getTooltipSize(this, options);
  9230. const positionAndSize = Object.assign({}, position, size);
  9231. const alignment = determineAlignment(this.chart, options, positionAndSize);
  9232. const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
  9233. this.xAlign = alignment.xAlign;
  9234. this.yAlign = alignment.yAlign;
  9235. properties = {
  9236. opacity: 1,
  9237. x: backgroundPoint.x,
  9238. y: backgroundPoint.y,
  9239. width: size.width,
  9240. height: size.height,
  9241. caretX: position.x,
  9242. caretY: position.y
  9243. };
  9244. }
  9245. this._tooltipItems = tooltipItems;
  9246. this.$context = undefined;
  9247. if (properties) {
  9248. this._resolveAnimations().update(this, properties);
  9249. }
  9250. if (changed && options.external) {
  9251. options.external.call(this, {
  9252. chart: this.chart,
  9253. tooltip: this,
  9254. replay
  9255. });
  9256. }
  9257. }
  9258. drawCaret(tooltipPoint, ctx, size, options) {
  9259. const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
  9260. ctx.lineTo(caretPosition.x1, caretPosition.y1);
  9261. ctx.lineTo(caretPosition.x2, caretPosition.y2);
  9262. ctx.lineTo(caretPosition.x3, caretPosition.y3);
  9263. }
  9264. getCaretPosition(tooltipPoint, size, options) {
  9265. const { xAlign , yAlign } = this;
  9266. const { caretSize , cornerRadius } = options;
  9267. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(cornerRadius);
  9268. const { x: ptX , y: ptY } = tooltipPoint;
  9269. const { width , height } = size;
  9270. let x1, x2, x3, y1, y2, y3;
  9271. if (yAlign === 'center') {
  9272. y2 = ptY + height / 2;
  9273. if (xAlign === 'left') {
  9274. x1 = ptX;
  9275. x2 = x1 - caretSize;
  9276. y1 = y2 + caretSize;
  9277. y3 = y2 - caretSize;
  9278. } else {
  9279. x1 = ptX + width;
  9280. x2 = x1 + caretSize;
  9281. y1 = y2 - caretSize;
  9282. y3 = y2 + caretSize;
  9283. }
  9284. x3 = x1;
  9285. } else {
  9286. if (xAlign === 'left') {
  9287. x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
  9288. } else if (xAlign === 'right') {
  9289. x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
  9290. } else {
  9291. x2 = this.caretX;
  9292. }
  9293. if (yAlign === 'top') {
  9294. y1 = ptY;
  9295. y2 = y1 - caretSize;
  9296. x1 = x2 - caretSize;
  9297. x3 = x2 + caretSize;
  9298. } else {
  9299. y1 = ptY + height;
  9300. y2 = y1 + caretSize;
  9301. x1 = x2 + caretSize;
  9302. x3 = x2 - caretSize;
  9303. }
  9304. y3 = y1;
  9305. }
  9306. return {
  9307. x1,
  9308. x2,
  9309. x3,
  9310. y1,
  9311. y2,
  9312. y3
  9313. };
  9314. }
  9315. drawTitle(pt, ctx, options) {
  9316. const title = this.title;
  9317. const length = title.length;
  9318. let titleFont, titleSpacing, i;
  9319. if (length) {
  9320. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9321. pt.x = getAlignedX(this, options.titleAlign, options);
  9322. ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
  9323. ctx.textBaseline = 'middle';
  9324. titleFont = helpers_segment.toFont(options.titleFont);
  9325. titleSpacing = options.titleSpacing;
  9326. ctx.fillStyle = options.titleColor;
  9327. ctx.font = titleFont.string;
  9328. for(i = 0; i < length; ++i){
  9329. ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
  9330. pt.y += titleFont.lineHeight + titleSpacing;
  9331. if (i + 1 === length) {
  9332. pt.y += options.titleMarginBottom - titleSpacing;
  9333. }
  9334. }
  9335. }
  9336. }
  9337. _drawColorBox(ctx, pt, i, rtlHelper, options) {
  9338. const labelColors = this.labelColors[i];
  9339. const labelPointStyle = this.labelPointStyles[i];
  9340. const { boxHeight , boxWidth , boxPadding } = options;
  9341. const bodyFont = helpers_segment.toFont(options.bodyFont);
  9342. const colorX = getAlignedX(this, 'left', options);
  9343. const rtlColorX = rtlHelper.x(colorX);
  9344. const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
  9345. const colorY = pt.y + yOffSet;
  9346. if (options.usePointStyle) {
  9347. const drawOptions = {
  9348. radius: Math.min(boxWidth, boxHeight) / 2,
  9349. pointStyle: labelPointStyle.pointStyle,
  9350. rotation: labelPointStyle.rotation,
  9351. borderWidth: 1
  9352. };
  9353. const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
  9354. const centerY = colorY + boxHeight / 2;
  9355. ctx.strokeStyle = options.multiKeyBackground;
  9356. ctx.fillStyle = options.multiKeyBackground;
  9357. helpers_segment.drawPoint(ctx, drawOptions, centerX, centerY);
  9358. ctx.strokeStyle = labelColors.borderColor;
  9359. ctx.fillStyle = labelColors.backgroundColor;
  9360. helpers_segment.drawPoint(ctx, drawOptions, centerX, centerY);
  9361. } else {
  9362. ctx.lineWidth = helpers_segment.isObject(labelColors.borderWidth) ? Math.max(...Object.values(labelColors.borderWidth)) : labelColors.borderWidth || 1;
  9363. ctx.strokeStyle = labelColors.borderColor;
  9364. ctx.setLineDash(labelColors.borderDash || []);
  9365. ctx.lineDashOffset = labelColors.borderDashOffset || 0;
  9366. const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth - boxPadding);
  9367. const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - boxPadding - 2);
  9368. const borderRadius = helpers_segment.toTRBLCorners(labelColors.borderRadius);
  9369. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  9370. ctx.beginPath();
  9371. ctx.fillStyle = options.multiKeyBackground;
  9372. helpers_segment.addRoundedRectPath(ctx, {
  9373. x: outerX,
  9374. y: colorY,
  9375. w: boxWidth,
  9376. h: boxHeight,
  9377. radius: borderRadius
  9378. });
  9379. ctx.fill();
  9380. ctx.stroke();
  9381. ctx.fillStyle = labelColors.backgroundColor;
  9382. ctx.beginPath();
  9383. helpers_segment.addRoundedRectPath(ctx, {
  9384. x: innerX,
  9385. y: colorY + 1,
  9386. w: boxWidth - 2,
  9387. h: boxHeight - 2,
  9388. radius: borderRadius
  9389. });
  9390. ctx.fill();
  9391. } else {
  9392. ctx.fillStyle = options.multiKeyBackground;
  9393. ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
  9394. ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
  9395. ctx.fillStyle = labelColors.backgroundColor;
  9396. ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
  9397. }
  9398. }
  9399. ctx.fillStyle = this.labelTextColors[i];
  9400. }
  9401. drawBody(pt, ctx, options) {
  9402. const { body } = this;
  9403. const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
  9404. const bodyFont = helpers_segment.toFont(options.bodyFont);
  9405. let bodyLineHeight = bodyFont.lineHeight;
  9406. let xLinePadding = 0;
  9407. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9408. const fillLineOfText = function(line) {
  9409. ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
  9410. pt.y += bodyLineHeight + bodySpacing;
  9411. };
  9412. const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
  9413. let bodyItem, textColor, lines, i, j, ilen, jlen;
  9414. ctx.textAlign = bodyAlign;
  9415. ctx.textBaseline = 'middle';
  9416. ctx.font = bodyFont.string;
  9417. pt.x = getAlignedX(this, bodyAlignForCalculation, options);
  9418. ctx.fillStyle = options.bodyColor;
  9419. helpers_segment.each(this.beforeBody, fillLineOfText);
  9420. xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
  9421. for(i = 0, ilen = body.length; i < ilen; ++i){
  9422. bodyItem = body[i];
  9423. textColor = this.labelTextColors[i];
  9424. ctx.fillStyle = textColor;
  9425. helpers_segment.each(bodyItem.before, fillLineOfText);
  9426. lines = bodyItem.lines;
  9427. if (displayColors && lines.length) {
  9428. this._drawColorBox(ctx, pt, i, rtlHelper, options);
  9429. bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
  9430. }
  9431. for(j = 0, jlen = lines.length; j < jlen; ++j){
  9432. fillLineOfText(lines[j]);
  9433. bodyLineHeight = bodyFont.lineHeight;
  9434. }
  9435. helpers_segment.each(bodyItem.after, fillLineOfText);
  9436. }
  9437. xLinePadding = 0;
  9438. bodyLineHeight = bodyFont.lineHeight;
  9439. helpers_segment.each(this.afterBody, fillLineOfText);
  9440. pt.y -= bodySpacing;
  9441. }
  9442. drawFooter(pt, ctx, options) {
  9443. const footer = this.footer;
  9444. const length = footer.length;
  9445. let footerFont, i;
  9446. if (length) {
  9447. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9448. pt.x = getAlignedX(this, options.footerAlign, options);
  9449. pt.y += options.footerMarginTop;
  9450. ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
  9451. ctx.textBaseline = 'middle';
  9452. footerFont = helpers_segment.toFont(options.footerFont);
  9453. ctx.fillStyle = options.footerColor;
  9454. ctx.font = footerFont.string;
  9455. for(i = 0; i < length; ++i){
  9456. ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
  9457. pt.y += footerFont.lineHeight + options.footerSpacing;
  9458. }
  9459. }
  9460. }
  9461. drawBackground(pt, ctx, tooltipSize, options) {
  9462. const { xAlign , yAlign } = this;
  9463. const { x , y } = pt;
  9464. const { width , height } = tooltipSize;
  9465. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(options.cornerRadius);
  9466. ctx.fillStyle = options.backgroundColor;
  9467. ctx.strokeStyle = options.borderColor;
  9468. ctx.lineWidth = options.borderWidth;
  9469. ctx.beginPath();
  9470. ctx.moveTo(x + topLeft, y);
  9471. if (yAlign === 'top') {
  9472. this.drawCaret(pt, ctx, tooltipSize, options);
  9473. }
  9474. ctx.lineTo(x + width - topRight, y);
  9475. ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
  9476. if (yAlign === 'center' && xAlign === 'right') {
  9477. this.drawCaret(pt, ctx, tooltipSize, options);
  9478. }
  9479. ctx.lineTo(x + width, y + height - bottomRight);
  9480. ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
  9481. if (yAlign === 'bottom') {
  9482. this.drawCaret(pt, ctx, tooltipSize, options);
  9483. }
  9484. ctx.lineTo(x + bottomLeft, y + height);
  9485. ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
  9486. if (yAlign === 'center' && xAlign === 'left') {
  9487. this.drawCaret(pt, ctx, tooltipSize, options);
  9488. }
  9489. ctx.lineTo(x, y + topLeft);
  9490. ctx.quadraticCurveTo(x, y, x + topLeft, y);
  9491. ctx.closePath();
  9492. ctx.fill();
  9493. if (options.borderWidth > 0) {
  9494. ctx.stroke();
  9495. }
  9496. }
  9497. _updateAnimationTarget(options) {
  9498. const chart = this.chart;
  9499. const anims = this.$animations;
  9500. const animX = anims && anims.x;
  9501. const animY = anims && anims.y;
  9502. if (animX || animY) {
  9503. const position = positioners[options.position].call(this, this._active, this._eventPosition);
  9504. if (!position) {
  9505. return;
  9506. }
  9507. const size = this._size = getTooltipSize(this, options);
  9508. const positionAndSize = Object.assign({}, position, this._size);
  9509. const alignment = determineAlignment(chart, options, positionAndSize);
  9510. const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
  9511. if (animX._to !== point.x || animY._to !== point.y) {
  9512. this.xAlign = alignment.xAlign;
  9513. this.yAlign = alignment.yAlign;
  9514. this.width = size.width;
  9515. this.height = size.height;
  9516. this.caretX = position.x;
  9517. this.caretY = position.y;
  9518. this._resolveAnimations().update(this, point);
  9519. }
  9520. }
  9521. }
  9522. _willRender() {
  9523. return !!this.opacity;
  9524. }
  9525. draw(ctx) {
  9526. const options = this.options.setContext(this.getContext());
  9527. let opacity = this.opacity;
  9528. if (!opacity) {
  9529. return;
  9530. }
  9531. this._updateAnimationTarget(options);
  9532. const tooltipSize = {
  9533. width: this.width,
  9534. height: this.height
  9535. };
  9536. const pt = {
  9537. x: this.x,
  9538. y: this.y
  9539. };
  9540. opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
  9541. const padding = helpers_segment.toPadding(options.padding);
  9542. const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
  9543. if (options.enabled && hasTooltipContent) {
  9544. ctx.save();
  9545. ctx.globalAlpha = opacity;
  9546. this.drawBackground(pt, ctx, tooltipSize, options);
  9547. helpers_segment.overrideTextDirection(ctx, options.textDirection);
  9548. pt.y += padding.top;
  9549. this.drawTitle(pt, ctx, options);
  9550. this.drawBody(pt, ctx, options);
  9551. this.drawFooter(pt, ctx, options);
  9552. helpers_segment.restoreTextDirection(ctx, options.textDirection);
  9553. ctx.restore();
  9554. }
  9555. }
  9556. getActiveElements() {
  9557. return this._active || [];
  9558. }
  9559. setActiveElements(activeElements, eventPosition) {
  9560. const lastActive = this._active;
  9561. const active = activeElements.map(({ datasetIndex , index })=>{
  9562. const meta = this.chart.getDatasetMeta(datasetIndex);
  9563. if (!meta) {
  9564. throw new Error('Cannot find a dataset at index ' + datasetIndex);
  9565. }
  9566. return {
  9567. datasetIndex,
  9568. element: meta.data[index],
  9569. index
  9570. };
  9571. });
  9572. const changed = !helpers_segment._elementsEqual(lastActive, active);
  9573. const positionChanged = this._positionChanged(active, eventPosition);
  9574. if (changed || positionChanged) {
  9575. this._active = active;
  9576. this._eventPosition = eventPosition;
  9577. this._ignoreReplayEvents = true;
  9578. this.update(true);
  9579. }
  9580. }
  9581. handleEvent(e, replay, inChartArea = true) {
  9582. if (replay && this._ignoreReplayEvents) {
  9583. return false;
  9584. }
  9585. this._ignoreReplayEvents = false;
  9586. const options = this.options;
  9587. const lastActive = this._active || [];
  9588. const active = this._getActiveElements(e, lastActive, replay, inChartArea);
  9589. const positionChanged = this._positionChanged(active, e);
  9590. const changed = replay || !helpers_segment._elementsEqual(active, lastActive) || positionChanged;
  9591. if (changed) {
  9592. this._active = active;
  9593. if (options.enabled || options.external) {
  9594. this._eventPosition = {
  9595. x: e.x,
  9596. y: e.y
  9597. };
  9598. this.update(true, replay);
  9599. }
  9600. }
  9601. return changed;
  9602. }
  9603. _getActiveElements(e, lastActive, replay, inChartArea) {
  9604. const options = this.options;
  9605. if (e.type === 'mouseout') {
  9606. return [];
  9607. }
  9608. if (!inChartArea) {
  9609. return lastActive;
  9610. }
  9611. const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
  9612. if (options.reverse) {
  9613. active.reverse();
  9614. }
  9615. return active;
  9616. }
  9617. _positionChanged(active, e) {
  9618. const { caretX , caretY , options } = this;
  9619. const position = positioners[options.position].call(this, active, e);
  9620. return position !== false && (caretX !== position.x || caretY !== position.y);
  9621. }
  9622. }
  9623. var plugin_tooltip = {
  9624. id: 'tooltip',
  9625. _element: Tooltip,
  9626. positioners,
  9627. afterInit (chart, _args, options) {
  9628. if (options) {
  9629. chart.tooltip = new Tooltip({
  9630. chart,
  9631. options
  9632. });
  9633. }
  9634. },
  9635. beforeUpdate (chart, _args, options) {
  9636. if (chart.tooltip) {
  9637. chart.tooltip.initialize(options);
  9638. }
  9639. },
  9640. reset (chart, _args, options) {
  9641. if (chart.tooltip) {
  9642. chart.tooltip.initialize(options);
  9643. }
  9644. },
  9645. afterDraw (chart) {
  9646. const tooltip = chart.tooltip;
  9647. if (tooltip && tooltip._willRender()) {
  9648. const args = {
  9649. tooltip
  9650. };
  9651. if (chart.notifyPlugins('beforeTooltipDraw', {
  9652. ...args,
  9653. cancelable: true
  9654. }) === false) {
  9655. return;
  9656. }
  9657. tooltip.draw(chart.ctx);
  9658. chart.notifyPlugins('afterTooltipDraw', args);
  9659. }
  9660. },
  9661. afterEvent (chart, args) {
  9662. if (chart.tooltip) {
  9663. const useFinalPosition = args.replay;
  9664. if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
  9665. args.changed = true;
  9666. }
  9667. }
  9668. },
  9669. defaults: {
  9670. enabled: true,
  9671. external: null,
  9672. position: 'average',
  9673. backgroundColor: 'rgba(0,0,0,0.8)',
  9674. titleColor: '#fff',
  9675. titleFont: {
  9676. weight: 'bold'
  9677. },
  9678. titleSpacing: 2,
  9679. titleMarginBottom: 6,
  9680. titleAlign: 'left',
  9681. bodyColor: '#fff',
  9682. bodySpacing: 2,
  9683. bodyFont: {},
  9684. bodyAlign: 'left',
  9685. footerColor: '#fff',
  9686. footerSpacing: 2,
  9687. footerMarginTop: 6,
  9688. footerFont: {
  9689. weight: 'bold'
  9690. },
  9691. footerAlign: 'left',
  9692. padding: 6,
  9693. caretPadding: 2,
  9694. caretSize: 5,
  9695. cornerRadius: 6,
  9696. boxHeight: (ctx, opts)=>opts.bodyFont.size,
  9697. boxWidth: (ctx, opts)=>opts.bodyFont.size,
  9698. multiKeyBackground: '#fff',
  9699. displayColors: true,
  9700. boxPadding: 0,
  9701. borderColor: 'rgba(0,0,0,0)',
  9702. borderWidth: 0,
  9703. animation: {
  9704. duration: 400,
  9705. easing: 'easeOutQuart'
  9706. },
  9707. animations: {
  9708. numbers: {
  9709. type: 'number',
  9710. properties: [
  9711. 'x',
  9712. 'y',
  9713. 'width',
  9714. 'height',
  9715. 'caretX',
  9716. 'caretY'
  9717. ]
  9718. },
  9719. opacity: {
  9720. easing: 'linear',
  9721. duration: 200
  9722. }
  9723. },
  9724. callbacks: defaultCallbacks
  9725. },
  9726. defaultRoutes: {
  9727. bodyFont: 'font',
  9728. footerFont: 'font',
  9729. titleFont: 'font'
  9730. },
  9731. descriptors: {
  9732. _scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
  9733. _indexable: false,
  9734. callbacks: {
  9735. _scriptable: false,
  9736. _indexable: false
  9737. },
  9738. animation: {
  9739. _fallback: false
  9740. },
  9741. animations: {
  9742. _fallback: 'animation'
  9743. }
  9744. },
  9745. additionalOptionScopes: [
  9746. 'interaction'
  9747. ]
  9748. };
  9749. var plugins = /*#__PURE__*/Object.freeze({
  9750. __proto__: null,
  9751. Colors: plugin_colors,
  9752. Decimation: plugin_decimation,
  9753. Filler: index,
  9754. Legend: plugin_legend,
  9755. SubTitle: plugin_subtitle,
  9756. Title: plugin_title,
  9757. Tooltip: plugin_tooltip
  9758. });
  9759. const addIfString = (labels, raw, index, addedLabels)=>{
  9760. if (typeof raw === 'string') {
  9761. index = labels.push(raw) - 1;
  9762. addedLabels.unshift({
  9763. index,
  9764. label: raw
  9765. });
  9766. } else if (isNaN(raw)) {
  9767. index = null;
  9768. }
  9769. return index;
  9770. };
  9771. function findOrAddLabel(labels, raw, index, addedLabels) {
  9772. const first = labels.indexOf(raw);
  9773. if (first === -1) {
  9774. return addIfString(labels, raw, index, addedLabels);
  9775. }
  9776. const last = labels.lastIndexOf(raw);
  9777. return first !== last ? index : first;
  9778. }
  9779. const validIndex = (index, max)=>index === null ? null : helpers_segment._limitValue(Math.round(index), 0, max);
  9780. function _getLabelForValue(value) {
  9781. const labels = this.getLabels();
  9782. if (value >= 0 && value < labels.length) {
  9783. return labels[value];
  9784. }
  9785. return value;
  9786. }
  9787. class CategoryScale extends Scale {
  9788. static id = 'category';
  9789. static defaults = {
  9790. ticks: {
  9791. callback: _getLabelForValue
  9792. }
  9793. };
  9794. constructor(cfg){
  9795. super(cfg);
  9796. this._startValue = undefined;
  9797. this._valueRange = 0;
  9798. this._addedLabels = [];
  9799. }
  9800. init(scaleOptions) {
  9801. const added = this._addedLabels;
  9802. if (added.length) {
  9803. const labels = this.getLabels();
  9804. for (const { index , label } of added){
  9805. if (labels[index] === label) {
  9806. labels.splice(index, 1);
  9807. }
  9808. }
  9809. this._addedLabels = [];
  9810. }
  9811. super.init(scaleOptions);
  9812. }
  9813. parse(raw, index) {
  9814. if (helpers_segment.isNullOrUndef(raw)) {
  9815. return null;
  9816. }
  9817. const labels = this.getLabels();
  9818. index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, helpers_segment.valueOrDefault(index, raw), this._addedLabels);
  9819. return validIndex(index, labels.length - 1);
  9820. }
  9821. determineDataLimits() {
  9822. const { minDefined , maxDefined } = this.getUserBounds();
  9823. let { min , max } = this.getMinMax(true);
  9824. if (this.options.bounds === 'ticks') {
  9825. if (!minDefined) {
  9826. min = 0;
  9827. }
  9828. if (!maxDefined) {
  9829. max = this.getLabels().length - 1;
  9830. }
  9831. }
  9832. this.min = min;
  9833. this.max = max;
  9834. }
  9835. buildTicks() {
  9836. const min = this.min;
  9837. const max = this.max;
  9838. const offset = this.options.offset;
  9839. const ticks = [];
  9840. let labels = this.getLabels();
  9841. labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
  9842. this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
  9843. this._startValue = this.min - (offset ? 0.5 : 0);
  9844. for(let value = min; value <= max; value++){
  9845. ticks.push({
  9846. value
  9847. });
  9848. }
  9849. return ticks;
  9850. }
  9851. getLabelForValue(value) {
  9852. return _getLabelForValue.call(this, value);
  9853. }
  9854. configure() {
  9855. super.configure();
  9856. if (!this.isHorizontal()) {
  9857. this._reversePixels = !this._reversePixels;
  9858. }
  9859. }
  9860. getPixelForValue(value) {
  9861. if (typeof value !== 'number') {
  9862. value = this.parse(value);
  9863. }
  9864. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  9865. }
  9866. getPixelForTick(index) {
  9867. const ticks = this.ticks;
  9868. if (index < 0 || index > ticks.length - 1) {
  9869. return null;
  9870. }
  9871. return this.getPixelForValue(ticks[index].value);
  9872. }
  9873. getValueForPixel(pixel) {
  9874. return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
  9875. }
  9876. getBasePixel() {
  9877. return this.bottom;
  9878. }
  9879. }
  9880. function generateTicks$1(generationOptions, dataRange) {
  9881. const ticks = [];
  9882. const MIN_SPACING = 1e-14;
  9883. const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
  9884. const unit = step || 1;
  9885. const maxSpaces = maxTicks - 1;
  9886. const { min: rmin , max: rmax } = dataRange;
  9887. const minDefined = !helpers_segment.isNullOrUndef(min);
  9888. const maxDefined = !helpers_segment.isNullOrUndef(max);
  9889. const countDefined = !helpers_segment.isNullOrUndef(count);
  9890. const minSpacing = (rmax - rmin) / (maxDigits + 1);
  9891. let spacing = helpers_segment.niceNum((rmax - rmin) / maxSpaces / unit) * unit;
  9892. let factor, niceMin, niceMax, numSpaces;
  9893. if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
  9894. return [
  9895. {
  9896. value: rmin
  9897. },
  9898. {
  9899. value: rmax
  9900. }
  9901. ];
  9902. }
  9903. numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
  9904. if (numSpaces > maxSpaces) {
  9905. spacing = helpers_segment.niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
  9906. }
  9907. if (!helpers_segment.isNullOrUndef(precision)) {
  9908. factor = Math.pow(10, precision);
  9909. spacing = Math.ceil(spacing * factor) / factor;
  9910. }
  9911. if (bounds === 'ticks') {
  9912. niceMin = Math.floor(rmin / spacing) * spacing;
  9913. niceMax = Math.ceil(rmax / spacing) * spacing;
  9914. } else {
  9915. niceMin = rmin;
  9916. niceMax = rmax;
  9917. }
  9918. if (minDefined && maxDefined && step && helpers_segment.almostWhole((max - min) / step, spacing / 1000)) {
  9919. numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
  9920. spacing = (max - min) / numSpaces;
  9921. niceMin = min;
  9922. niceMax = max;
  9923. } else if (countDefined) {
  9924. niceMin = minDefined ? min : niceMin;
  9925. niceMax = maxDefined ? max : niceMax;
  9926. numSpaces = count - 1;
  9927. spacing = (niceMax - niceMin) / numSpaces;
  9928. } else {
  9929. numSpaces = (niceMax - niceMin) / spacing;
  9930. if (helpers_segment.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  9931. numSpaces = Math.round(numSpaces);
  9932. } else {
  9933. numSpaces = Math.ceil(numSpaces);
  9934. }
  9935. }
  9936. const decimalPlaces = Math.max(helpers_segment._decimalPlaces(spacing), helpers_segment._decimalPlaces(niceMin));
  9937. factor = Math.pow(10, helpers_segment.isNullOrUndef(precision) ? decimalPlaces : precision);
  9938. niceMin = Math.round(niceMin * factor) / factor;
  9939. niceMax = Math.round(niceMax * factor) / factor;
  9940. let j = 0;
  9941. if (minDefined) {
  9942. if (includeBounds && niceMin !== min) {
  9943. ticks.push({
  9944. value: min
  9945. });
  9946. if (niceMin < min) {
  9947. j++;
  9948. }
  9949. if (helpers_segment.almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
  9950. j++;
  9951. }
  9952. } else if (niceMin < min) {
  9953. j++;
  9954. }
  9955. }
  9956. for(; j < numSpaces; ++j){
  9957. ticks.push({
  9958. value: Math.round((niceMin + j * spacing) * factor) / factor
  9959. });
  9960. }
  9961. if (maxDefined && includeBounds && niceMax !== max) {
  9962. if (ticks.length && helpers_segment.almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
  9963. ticks[ticks.length - 1].value = max;
  9964. } else {
  9965. ticks.push({
  9966. value: max
  9967. });
  9968. }
  9969. } else if (!maxDefined || niceMax === max) {
  9970. ticks.push({
  9971. value: niceMax
  9972. });
  9973. }
  9974. return ticks;
  9975. }
  9976. function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
  9977. const rad = helpers_segment.toRadians(minRotation);
  9978. const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
  9979. const length = 0.75 * minSpacing * ('' + value).length;
  9980. return Math.min(minSpacing / ratio, length);
  9981. }
  9982. class LinearScaleBase extends Scale {
  9983. constructor(cfg){
  9984. super(cfg);
  9985. this.start = undefined;
  9986. this.end = undefined;
  9987. this._startValue = undefined;
  9988. this._endValue = undefined;
  9989. this._valueRange = 0;
  9990. }
  9991. parse(raw, index) {
  9992. if (helpers_segment.isNullOrUndef(raw)) {
  9993. return null;
  9994. }
  9995. if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
  9996. return null;
  9997. }
  9998. return +raw;
  9999. }
  10000. handleTickRangeOptions() {
  10001. const { beginAtZero } = this.options;
  10002. const { minDefined , maxDefined } = this.getUserBounds();
  10003. let { min , max } = this;
  10004. const setMin = (v)=>min = minDefined ? min : v;
  10005. const setMax = (v)=>max = maxDefined ? max : v;
  10006. if (beginAtZero) {
  10007. const minSign = helpers_segment.sign(min);
  10008. const maxSign = helpers_segment.sign(max);
  10009. if (minSign < 0 && maxSign < 0) {
  10010. setMax(0);
  10011. } else if (minSign > 0 && maxSign > 0) {
  10012. setMin(0);
  10013. }
  10014. }
  10015. if (min === max) {
  10016. let offset = max === 0 ? 1 : Math.abs(max * 0.05);
  10017. setMax(max + offset);
  10018. if (!beginAtZero) {
  10019. setMin(min - offset);
  10020. }
  10021. }
  10022. this.min = min;
  10023. this.max = max;
  10024. }
  10025. getTickLimit() {
  10026. const tickOpts = this.options.ticks;
  10027. let { maxTicksLimit , stepSize } = tickOpts;
  10028. let maxTicks;
  10029. if (stepSize) {
  10030. maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
  10031. if (maxTicks > 1000) {
  10032. console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
  10033. maxTicks = 1000;
  10034. }
  10035. } else {
  10036. maxTicks = this.computeTickLimit();
  10037. maxTicksLimit = maxTicksLimit || 11;
  10038. }
  10039. if (maxTicksLimit) {
  10040. maxTicks = Math.min(maxTicksLimit, maxTicks);
  10041. }
  10042. return maxTicks;
  10043. }
  10044. computeTickLimit() {
  10045. return Number.POSITIVE_INFINITY;
  10046. }
  10047. buildTicks() {
  10048. const opts = this.options;
  10049. const tickOpts = opts.ticks;
  10050. let maxTicks = this.getTickLimit();
  10051. maxTicks = Math.max(2, maxTicks);
  10052. const numericGeneratorOptions = {
  10053. maxTicks,
  10054. bounds: opts.bounds,
  10055. min: opts.min,
  10056. max: opts.max,
  10057. precision: tickOpts.precision,
  10058. step: tickOpts.stepSize,
  10059. count: tickOpts.count,
  10060. maxDigits: this._maxDigits(),
  10061. horizontal: this.isHorizontal(),
  10062. minRotation: tickOpts.minRotation || 0,
  10063. includeBounds: tickOpts.includeBounds !== false
  10064. };
  10065. const dataRange = this._range || this;
  10066. const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
  10067. if (opts.bounds === 'ticks') {
  10068. helpers_segment._setMinAndMaxByKey(ticks, this, 'value');
  10069. }
  10070. if (opts.reverse) {
  10071. ticks.reverse();
  10072. this.start = this.max;
  10073. this.end = this.min;
  10074. } else {
  10075. this.start = this.min;
  10076. this.end = this.max;
  10077. }
  10078. return ticks;
  10079. }
  10080. configure() {
  10081. const ticks = this.ticks;
  10082. let start = this.min;
  10083. let end = this.max;
  10084. super.configure();
  10085. if (this.options.offset && ticks.length) {
  10086. const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
  10087. start -= offset;
  10088. end += offset;
  10089. }
  10090. this._startValue = start;
  10091. this._endValue = end;
  10092. this._valueRange = end - start;
  10093. }
  10094. getLabelForValue(value) {
  10095. return helpers_segment.formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10096. }
  10097. }
  10098. class LinearScale extends LinearScaleBase {
  10099. static id = 'linear';
  10100. static defaults = {
  10101. ticks: {
  10102. callback: helpers_segment.Ticks.formatters.numeric
  10103. }
  10104. };
  10105. determineDataLimits() {
  10106. const { min , max } = this.getMinMax(true);
  10107. this.min = helpers_segment.isNumberFinite(min) ? min : 0;
  10108. this.max = helpers_segment.isNumberFinite(max) ? max : 1;
  10109. this.handleTickRangeOptions();
  10110. }
  10111. computeTickLimit() {
  10112. const horizontal = this.isHorizontal();
  10113. const length = horizontal ? this.width : this.height;
  10114. const minRotation = helpers_segment.toRadians(this.options.ticks.minRotation);
  10115. const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
  10116. const tickFont = this._resolveTickFontOptions(0);
  10117. return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
  10118. }
  10119. getPixelForValue(value) {
  10120. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  10121. }
  10122. getValueForPixel(pixel) {
  10123. return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
  10124. }
  10125. }
  10126. const log10Floor = (v)=>Math.floor(helpers_segment.log10(v));
  10127. const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
  10128. function isMajor(tickVal) {
  10129. const remain = tickVal / Math.pow(10, log10Floor(tickVal));
  10130. return remain === 1;
  10131. }
  10132. function steps(min, max, rangeExp) {
  10133. const rangeStep = Math.pow(10, rangeExp);
  10134. const start = Math.floor(min / rangeStep);
  10135. const end = Math.ceil(max / rangeStep);
  10136. return end - start;
  10137. }
  10138. function startExp(min, max) {
  10139. const range = max - min;
  10140. let rangeExp = log10Floor(range);
  10141. while(steps(min, max, rangeExp) > 10){
  10142. rangeExp++;
  10143. }
  10144. while(steps(min, max, rangeExp) < 10){
  10145. rangeExp--;
  10146. }
  10147. return Math.min(rangeExp, log10Floor(min));
  10148. }
  10149. function generateTicks(generationOptions, { min , max }) {
  10150. min = helpers_segment.finiteOrDefault(generationOptions.min, min);
  10151. const ticks = [];
  10152. const minExp = log10Floor(min);
  10153. let exp = startExp(min, max);
  10154. let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
  10155. const stepSize = Math.pow(10, exp);
  10156. const base = minExp > exp ? Math.pow(10, minExp) : 0;
  10157. const start = Math.round((min - base) * precision) / precision;
  10158. const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
  10159. let significand = Math.floor((start - offset) / Math.pow(10, exp));
  10160. let value = helpers_segment.finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
  10161. while(value < max){
  10162. ticks.push({
  10163. value,
  10164. major: isMajor(value),
  10165. significand
  10166. });
  10167. if (significand >= 10) {
  10168. significand = significand < 15 ? 15 : 20;
  10169. } else {
  10170. significand++;
  10171. }
  10172. if (significand >= 20) {
  10173. exp++;
  10174. significand = 2;
  10175. precision = exp >= 0 ? 1 : precision;
  10176. }
  10177. value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
  10178. }
  10179. const lastTick = helpers_segment.finiteOrDefault(generationOptions.max, value);
  10180. ticks.push({
  10181. value: lastTick,
  10182. major: isMajor(lastTick),
  10183. significand
  10184. });
  10185. return ticks;
  10186. }
  10187. class LogarithmicScale extends Scale {
  10188. static id = 'logarithmic';
  10189. static defaults = {
  10190. ticks: {
  10191. callback: helpers_segment.Ticks.formatters.logarithmic,
  10192. major: {
  10193. enabled: true
  10194. }
  10195. }
  10196. };
  10197. constructor(cfg){
  10198. super(cfg);
  10199. this.start = undefined;
  10200. this.end = undefined;
  10201. this._startValue = undefined;
  10202. this._valueRange = 0;
  10203. }
  10204. parse(raw, index) {
  10205. const value = LinearScaleBase.prototype.parse.apply(this, [
  10206. raw,
  10207. index
  10208. ]);
  10209. if (value === 0) {
  10210. this._zero = true;
  10211. return undefined;
  10212. }
  10213. return helpers_segment.isNumberFinite(value) && value > 0 ? value : null;
  10214. }
  10215. determineDataLimits() {
  10216. const { min , max } = this.getMinMax(true);
  10217. this.min = helpers_segment.isNumberFinite(min) ? Math.max(0, min) : null;
  10218. this.max = helpers_segment.isNumberFinite(max) ? Math.max(0, max) : null;
  10219. if (this.options.beginAtZero) {
  10220. this._zero = true;
  10221. }
  10222. if (this._zero && this.min !== this._suggestedMin && !helpers_segment.isNumberFinite(this._userMin)) {
  10223. this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
  10224. }
  10225. this.handleTickRangeOptions();
  10226. }
  10227. handleTickRangeOptions() {
  10228. const { minDefined , maxDefined } = this.getUserBounds();
  10229. let min = this.min;
  10230. let max = this.max;
  10231. const setMin = (v)=>min = minDefined ? min : v;
  10232. const setMax = (v)=>max = maxDefined ? max : v;
  10233. if (min === max) {
  10234. if (min <= 0) {
  10235. setMin(1);
  10236. setMax(10);
  10237. } else {
  10238. setMin(changeExponent(min, -1));
  10239. setMax(changeExponent(max, +1));
  10240. }
  10241. }
  10242. if (min <= 0) {
  10243. setMin(changeExponent(max, -1));
  10244. }
  10245. if (max <= 0) {
  10246. setMax(changeExponent(min, +1));
  10247. }
  10248. this.min = min;
  10249. this.max = max;
  10250. }
  10251. buildTicks() {
  10252. const opts = this.options;
  10253. const generationOptions = {
  10254. min: this._userMin,
  10255. max: this._userMax
  10256. };
  10257. const ticks = generateTicks(generationOptions, this);
  10258. if (opts.bounds === 'ticks') {
  10259. helpers_segment._setMinAndMaxByKey(ticks, this, 'value');
  10260. }
  10261. if (opts.reverse) {
  10262. ticks.reverse();
  10263. this.start = this.max;
  10264. this.end = this.min;
  10265. } else {
  10266. this.start = this.min;
  10267. this.end = this.max;
  10268. }
  10269. return ticks;
  10270. }
  10271. getLabelForValue(value) {
  10272. return value === undefined ? '0' : helpers_segment.formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10273. }
  10274. configure() {
  10275. const start = this.min;
  10276. super.configure();
  10277. this._startValue = helpers_segment.log10(start);
  10278. this._valueRange = helpers_segment.log10(this.max) - helpers_segment.log10(start);
  10279. }
  10280. getPixelForValue(value) {
  10281. if (value === undefined || value === 0) {
  10282. value = this.min;
  10283. }
  10284. if (value === null || isNaN(value)) {
  10285. return NaN;
  10286. }
  10287. return this.getPixelForDecimal(value === this.min ? 0 : (helpers_segment.log10(value) - this._startValue) / this._valueRange);
  10288. }
  10289. getValueForPixel(pixel) {
  10290. const decimal = this.getDecimalForPixel(pixel);
  10291. return Math.pow(10, this._startValue + decimal * this._valueRange);
  10292. }
  10293. }
  10294. function getTickBackdropHeight(opts) {
  10295. const tickOpts = opts.ticks;
  10296. if (tickOpts.display && opts.display) {
  10297. const padding = helpers_segment.toPadding(tickOpts.backdropPadding);
  10298. return helpers_segment.valueOrDefault(tickOpts.font && tickOpts.font.size, helpers_segment.defaults.font.size) + padding.height;
  10299. }
  10300. return 0;
  10301. }
  10302. function measureLabelSize(ctx, font, label) {
  10303. label = helpers_segment.isArray(label) ? label : [
  10304. label
  10305. ];
  10306. return {
  10307. w: helpers_segment._longestText(ctx, font.string, label),
  10308. h: label.length * font.lineHeight
  10309. };
  10310. }
  10311. function determineLimits(angle, pos, size, min, max) {
  10312. if (angle === min || angle === max) {
  10313. return {
  10314. start: pos - size / 2,
  10315. end: pos + size / 2
  10316. };
  10317. } else if (angle < min || angle > max) {
  10318. return {
  10319. start: pos - size,
  10320. end: pos
  10321. };
  10322. }
  10323. return {
  10324. start: pos,
  10325. end: pos + size
  10326. };
  10327. }
  10328. function fitWithPointLabels(scale) {
  10329. const orig = {
  10330. l: scale.left + scale._padding.left,
  10331. r: scale.right - scale._padding.right,
  10332. t: scale.top + scale._padding.top,
  10333. b: scale.bottom - scale._padding.bottom
  10334. };
  10335. const limits = Object.assign({}, orig);
  10336. const labelSizes = [];
  10337. const padding = [];
  10338. const valueCount = scale._pointLabels.length;
  10339. const pointLabelOpts = scale.options.pointLabels;
  10340. const additionalAngle = pointLabelOpts.centerPointLabels ? helpers_segment.PI / valueCount : 0;
  10341. for(let i = 0; i < valueCount; i++){
  10342. const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
  10343. padding[i] = opts.padding;
  10344. const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
  10345. const plFont = helpers_segment.toFont(opts.font);
  10346. const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
  10347. labelSizes[i] = textSize;
  10348. const angleRadians = helpers_segment._normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
  10349. const angle = Math.round(helpers_segment.toDegrees(angleRadians));
  10350. const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  10351. const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  10352. updateLimits(limits, orig, angleRadians, hLimits, vLimits);
  10353. }
  10354. scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
  10355. scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  10356. }
  10357. function updateLimits(limits, orig, angle, hLimits, vLimits) {
  10358. const sin = Math.abs(Math.sin(angle));
  10359. const cos = Math.abs(Math.cos(angle));
  10360. let x = 0;
  10361. let y = 0;
  10362. if (hLimits.start < orig.l) {
  10363. x = (orig.l - hLimits.start) / sin;
  10364. limits.l = Math.min(limits.l, orig.l - x);
  10365. } else if (hLimits.end > orig.r) {
  10366. x = (hLimits.end - orig.r) / sin;
  10367. limits.r = Math.max(limits.r, orig.r + x);
  10368. }
  10369. if (vLimits.start < orig.t) {
  10370. y = (orig.t - vLimits.start) / cos;
  10371. limits.t = Math.min(limits.t, orig.t - y);
  10372. } else if (vLimits.end > orig.b) {
  10373. y = (vLimits.end - orig.b) / cos;
  10374. limits.b = Math.max(limits.b, orig.b + y);
  10375. }
  10376. }
  10377. function buildPointLabelItems(scale, labelSizes, padding) {
  10378. const items = [];
  10379. const valueCount = scale._pointLabels.length;
  10380. const opts = scale.options;
  10381. const extra = getTickBackdropHeight(opts) / 2;
  10382. const outerDistance = scale.drawingArea;
  10383. const additionalAngle = opts.pointLabels.centerPointLabels ? helpers_segment.PI / valueCount : 0;
  10384. for(let i = 0; i < valueCount; i++){
  10385. const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle);
  10386. const angle = Math.round(helpers_segment.toDegrees(helpers_segment._normalizeAngle(pointLabelPosition.angle + helpers_segment.HALF_PI)));
  10387. const size = labelSizes[i];
  10388. const y = yForAngle(pointLabelPosition.y, size.h, angle);
  10389. const textAlign = getTextAlignForAngle(angle);
  10390. const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
  10391. items.push({
  10392. x: pointLabelPosition.x,
  10393. y,
  10394. textAlign,
  10395. left,
  10396. top: y,
  10397. right: left + size.w,
  10398. bottom: y + size.h
  10399. });
  10400. }
  10401. return items;
  10402. }
  10403. function getTextAlignForAngle(angle) {
  10404. if (angle === 0 || angle === 180) {
  10405. return 'center';
  10406. } else if (angle < 180) {
  10407. return 'left';
  10408. }
  10409. return 'right';
  10410. }
  10411. function leftForTextAlign(x, w, align) {
  10412. if (align === 'right') {
  10413. x -= w;
  10414. } else if (align === 'center') {
  10415. x -= w / 2;
  10416. }
  10417. return x;
  10418. }
  10419. function yForAngle(y, h, angle) {
  10420. if (angle === 90 || angle === 270) {
  10421. y -= h / 2;
  10422. } else if (angle > 270 || angle < 90) {
  10423. y -= h;
  10424. }
  10425. return y;
  10426. }
  10427. function drawPointLabels(scale, labelCount) {
  10428. const { ctx , options: { pointLabels } } = scale;
  10429. for(let i = labelCount - 1; i >= 0; i--){
  10430. const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
  10431. const plFont = helpers_segment.toFont(optsAtIndex.font);
  10432. const { x , y , textAlign , left , top , right , bottom } = scale._pointLabelItems[i];
  10433. const { backdropColor } = optsAtIndex;
  10434. if (!helpers_segment.isNullOrUndef(backdropColor)) {
  10435. const borderRadius = helpers_segment.toTRBLCorners(optsAtIndex.borderRadius);
  10436. const padding = helpers_segment.toPadding(optsAtIndex.backdropPadding);
  10437. ctx.fillStyle = backdropColor;
  10438. const backdropLeft = left - padding.left;
  10439. const backdropTop = top - padding.top;
  10440. const backdropWidth = right - left + padding.width;
  10441. const backdropHeight = bottom - top + padding.height;
  10442. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  10443. ctx.beginPath();
  10444. helpers_segment.addRoundedRectPath(ctx, {
  10445. x: backdropLeft,
  10446. y: backdropTop,
  10447. w: backdropWidth,
  10448. h: backdropHeight,
  10449. radius: borderRadius
  10450. });
  10451. ctx.fill();
  10452. } else {
  10453. ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
  10454. }
  10455. }
  10456. helpers_segment.renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
  10457. color: optsAtIndex.color,
  10458. textAlign: textAlign,
  10459. textBaseline: 'middle'
  10460. });
  10461. }
  10462. }
  10463. function pathRadiusLine(scale, radius, circular, labelCount) {
  10464. const { ctx } = scale;
  10465. if (circular) {
  10466. ctx.arc(scale.xCenter, scale.yCenter, radius, 0, helpers_segment.TAU);
  10467. } else {
  10468. let pointPosition = scale.getPointPosition(0, radius);
  10469. ctx.moveTo(pointPosition.x, pointPosition.y);
  10470. for(let i = 1; i < labelCount; i++){
  10471. pointPosition = scale.getPointPosition(i, radius);
  10472. ctx.lineTo(pointPosition.x, pointPosition.y);
  10473. }
  10474. }
  10475. }
  10476. function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
  10477. const ctx = scale.ctx;
  10478. const circular = gridLineOpts.circular;
  10479. const { color , lineWidth } = gridLineOpts;
  10480. if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
  10481. return;
  10482. }
  10483. ctx.save();
  10484. ctx.strokeStyle = color;
  10485. ctx.lineWidth = lineWidth;
  10486. ctx.setLineDash(borderOpts.dash);
  10487. ctx.lineDashOffset = borderOpts.dashOffset;
  10488. ctx.beginPath();
  10489. pathRadiusLine(scale, radius, circular, labelCount);
  10490. ctx.closePath();
  10491. ctx.stroke();
  10492. ctx.restore();
  10493. }
  10494. function createPointLabelContext(parent, index, label) {
  10495. return helpers_segment.createContext(parent, {
  10496. label,
  10497. index,
  10498. type: 'pointLabel'
  10499. });
  10500. }
  10501. class RadialLinearScale extends LinearScaleBase {
  10502. static id = 'radialLinear';
  10503. static defaults = {
  10504. display: true,
  10505. animate: true,
  10506. position: 'chartArea',
  10507. angleLines: {
  10508. display: true,
  10509. lineWidth: 1,
  10510. borderDash: [],
  10511. borderDashOffset: 0.0
  10512. },
  10513. grid: {
  10514. circular: false
  10515. },
  10516. startAngle: 0,
  10517. ticks: {
  10518. showLabelBackdrop: true,
  10519. callback: helpers_segment.Ticks.formatters.numeric
  10520. },
  10521. pointLabels: {
  10522. backdropColor: undefined,
  10523. backdropPadding: 2,
  10524. display: true,
  10525. font: {
  10526. size: 10
  10527. },
  10528. callback (label) {
  10529. return label;
  10530. },
  10531. padding: 5,
  10532. centerPointLabels: false
  10533. }
  10534. };
  10535. static defaultRoutes = {
  10536. 'angleLines.color': 'borderColor',
  10537. 'pointLabels.color': 'color',
  10538. 'ticks.color': 'color'
  10539. };
  10540. static descriptors = {
  10541. angleLines: {
  10542. _fallback: 'grid'
  10543. }
  10544. };
  10545. constructor(cfg){
  10546. super(cfg);
  10547. this.xCenter = undefined;
  10548. this.yCenter = undefined;
  10549. this.drawingArea = undefined;
  10550. this._pointLabels = [];
  10551. this._pointLabelItems = [];
  10552. }
  10553. setDimensions() {
  10554. const padding = this._padding = helpers_segment.toPadding(getTickBackdropHeight(this.options) / 2);
  10555. const w = this.width = this.maxWidth - padding.width;
  10556. const h = this.height = this.maxHeight - padding.height;
  10557. this.xCenter = Math.floor(this.left + w / 2 + padding.left);
  10558. this.yCenter = Math.floor(this.top + h / 2 + padding.top);
  10559. this.drawingArea = Math.floor(Math.min(w, h) / 2);
  10560. }
  10561. determineDataLimits() {
  10562. const { min , max } = this.getMinMax(false);
  10563. this.min = helpers_segment.isNumberFinite(min) && !isNaN(min) ? min : 0;
  10564. this.max = helpers_segment.isNumberFinite(max) && !isNaN(max) ? max : 0;
  10565. this.handleTickRangeOptions();
  10566. }
  10567. computeTickLimit() {
  10568. return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  10569. }
  10570. generateTickLabels(ticks) {
  10571. LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
  10572. this._pointLabels = this.getLabels().map((value, index)=>{
  10573. const label = helpers_segment.callback(this.options.pointLabels.callback, [
  10574. value,
  10575. index
  10576. ], this);
  10577. return label || label === 0 ? label : '';
  10578. }).filter((v, i)=>this.chart.getDataVisibility(i));
  10579. }
  10580. fit() {
  10581. const opts = this.options;
  10582. if (opts.display && opts.pointLabels.display) {
  10583. fitWithPointLabels(this);
  10584. } else {
  10585. this.setCenterPoint(0, 0, 0, 0);
  10586. }
  10587. }
  10588. setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
  10589. this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
  10590. this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
  10591. this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
  10592. }
  10593. getIndexAngle(index) {
  10594. const angleMultiplier = helpers_segment.TAU / (this._pointLabels.length || 1);
  10595. const startAngle = this.options.startAngle || 0;
  10596. return helpers_segment._normalizeAngle(index * angleMultiplier + helpers_segment.toRadians(startAngle));
  10597. }
  10598. getDistanceFromCenterForValue(value) {
  10599. if (helpers_segment.isNullOrUndef(value)) {
  10600. return NaN;
  10601. }
  10602. const scalingFactor = this.drawingArea / (this.max - this.min);
  10603. if (this.options.reverse) {
  10604. return (this.max - value) * scalingFactor;
  10605. }
  10606. return (value - this.min) * scalingFactor;
  10607. }
  10608. getValueForDistanceFromCenter(distance) {
  10609. if (helpers_segment.isNullOrUndef(distance)) {
  10610. return NaN;
  10611. }
  10612. const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
  10613. return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
  10614. }
  10615. getPointLabelContext(index) {
  10616. const pointLabels = this._pointLabels || [];
  10617. if (index >= 0 && index < pointLabels.length) {
  10618. const pointLabel = pointLabels[index];
  10619. return createPointLabelContext(this.getContext(), index, pointLabel);
  10620. }
  10621. }
  10622. getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
  10623. const angle = this.getIndexAngle(index) - helpers_segment.HALF_PI + additionalAngle;
  10624. return {
  10625. x: Math.cos(angle) * distanceFromCenter + this.xCenter,
  10626. y: Math.sin(angle) * distanceFromCenter + this.yCenter,
  10627. angle
  10628. };
  10629. }
  10630. getPointPositionForValue(index, value) {
  10631. return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  10632. }
  10633. getBasePosition(index) {
  10634. return this.getPointPositionForValue(index || 0, this.getBaseValue());
  10635. }
  10636. getPointLabelPosition(index) {
  10637. const { left , top , right , bottom } = this._pointLabelItems[index];
  10638. return {
  10639. left,
  10640. top,
  10641. right,
  10642. bottom
  10643. };
  10644. }
  10645. drawBackground() {
  10646. const { backgroundColor , grid: { circular } } = this.options;
  10647. if (backgroundColor) {
  10648. const ctx = this.ctx;
  10649. ctx.save();
  10650. ctx.beginPath();
  10651. pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
  10652. ctx.closePath();
  10653. ctx.fillStyle = backgroundColor;
  10654. ctx.fill();
  10655. ctx.restore();
  10656. }
  10657. }
  10658. drawGrid() {
  10659. const ctx = this.ctx;
  10660. const opts = this.options;
  10661. const { angleLines , grid , border } = opts;
  10662. const labelCount = this._pointLabels.length;
  10663. let i, offset, position;
  10664. if (opts.pointLabels.display) {
  10665. drawPointLabels(this, labelCount);
  10666. }
  10667. if (grid.display) {
  10668. this.ticks.forEach((tick, index)=>{
  10669. if (index !== 0) {
  10670. offset = this.getDistanceFromCenterForValue(tick.value);
  10671. const context = this.getContext(index);
  10672. const optsAtIndex = grid.setContext(context);
  10673. const optsAtIndexBorder = border.setContext(context);
  10674. drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
  10675. }
  10676. });
  10677. }
  10678. if (angleLines.display) {
  10679. ctx.save();
  10680. for(i = labelCount - 1; i >= 0; i--){
  10681. const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
  10682. const { color , lineWidth } = optsAtIndex;
  10683. if (!lineWidth || !color) {
  10684. continue;
  10685. }
  10686. ctx.lineWidth = lineWidth;
  10687. ctx.strokeStyle = color;
  10688. ctx.setLineDash(optsAtIndex.borderDash);
  10689. ctx.lineDashOffset = optsAtIndex.borderDashOffset;
  10690. offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
  10691. position = this.getPointPosition(i, offset);
  10692. ctx.beginPath();
  10693. ctx.moveTo(this.xCenter, this.yCenter);
  10694. ctx.lineTo(position.x, position.y);
  10695. ctx.stroke();
  10696. }
  10697. ctx.restore();
  10698. }
  10699. }
  10700. drawBorder() {}
  10701. drawLabels() {
  10702. const ctx = this.ctx;
  10703. const opts = this.options;
  10704. const tickOpts = opts.ticks;
  10705. if (!tickOpts.display) {
  10706. return;
  10707. }
  10708. const startAngle = this.getIndexAngle(0);
  10709. let offset, width;
  10710. ctx.save();
  10711. ctx.translate(this.xCenter, this.yCenter);
  10712. ctx.rotate(startAngle);
  10713. ctx.textAlign = 'center';
  10714. ctx.textBaseline = 'middle';
  10715. this.ticks.forEach((tick, index)=>{
  10716. if (index === 0 && !opts.reverse) {
  10717. return;
  10718. }
  10719. const optsAtIndex = tickOpts.setContext(this.getContext(index));
  10720. const tickFont = helpers_segment.toFont(optsAtIndex.font);
  10721. offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
  10722. if (optsAtIndex.showLabelBackdrop) {
  10723. ctx.font = tickFont.string;
  10724. width = ctx.measureText(tick.label).width;
  10725. ctx.fillStyle = optsAtIndex.backdropColor;
  10726. const padding = helpers_segment.toPadding(optsAtIndex.backdropPadding);
  10727. ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
  10728. }
  10729. helpers_segment.renderText(ctx, tick.label, 0, -offset, tickFont, {
  10730. color: optsAtIndex.color
  10731. });
  10732. });
  10733. ctx.restore();
  10734. }
  10735. drawTitle() {}
  10736. }
  10737. const INTERVALS = {
  10738. millisecond: {
  10739. common: true,
  10740. size: 1,
  10741. steps: 1000
  10742. },
  10743. second: {
  10744. common: true,
  10745. size: 1000,
  10746. steps: 60
  10747. },
  10748. minute: {
  10749. common: true,
  10750. size: 60000,
  10751. steps: 60
  10752. },
  10753. hour: {
  10754. common: true,
  10755. size: 3600000,
  10756. steps: 24
  10757. },
  10758. day: {
  10759. common: true,
  10760. size: 86400000,
  10761. steps: 30
  10762. },
  10763. week: {
  10764. common: false,
  10765. size: 604800000,
  10766. steps: 4
  10767. },
  10768. month: {
  10769. common: true,
  10770. size: 2.628e9,
  10771. steps: 12
  10772. },
  10773. quarter: {
  10774. common: false,
  10775. size: 7.884e9,
  10776. steps: 4
  10777. },
  10778. year: {
  10779. common: true,
  10780. size: 3.154e10
  10781. }
  10782. };
  10783. const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
  10784. function sorter(a, b) {
  10785. return a - b;
  10786. }
  10787. function parse(scale, input) {
  10788. if (helpers_segment.isNullOrUndef(input)) {
  10789. return null;
  10790. }
  10791. const adapter = scale._adapter;
  10792. const { parser , round , isoWeekday } = scale._parseOpts;
  10793. let value = input;
  10794. if (typeof parser === 'function') {
  10795. value = parser(value);
  10796. }
  10797. if (!helpers_segment.isNumberFinite(value)) {
  10798. value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
  10799. }
  10800. if (value === null) {
  10801. return null;
  10802. }
  10803. if (round) {
  10804. value = round === 'week' && (helpers_segment.isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
  10805. }
  10806. return +value;
  10807. }
  10808. function determineUnitForAutoTicks(minUnit, min, max, capacity) {
  10809. const ilen = UNITS.length;
  10810. for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
  10811. const interval = INTERVALS[UNITS[i]];
  10812. const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
  10813. if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
  10814. return UNITS[i];
  10815. }
  10816. }
  10817. return UNITS[ilen - 1];
  10818. }
  10819. function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
  10820. for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
  10821. const unit = UNITS[i];
  10822. if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
  10823. return unit;
  10824. }
  10825. }
  10826. return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  10827. }
  10828. function determineMajorUnit(unit) {
  10829. for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
  10830. if (INTERVALS[UNITS[i]].common) {
  10831. return UNITS[i];
  10832. }
  10833. }
  10834. }
  10835. function addTick(ticks, time, timestamps) {
  10836. if (!timestamps) {
  10837. ticks[time] = true;
  10838. } else if (timestamps.length) {
  10839. const { lo , hi } = helpers_segment._lookup(timestamps, time);
  10840. const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
  10841. ticks[timestamp] = true;
  10842. }
  10843. }
  10844. function setMajorTicks(scale, ticks, map, majorUnit) {
  10845. const adapter = scale._adapter;
  10846. const first = +adapter.startOf(ticks[0].value, majorUnit);
  10847. const last = ticks[ticks.length - 1].value;
  10848. let major, index;
  10849. for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
  10850. index = map[major];
  10851. if (index >= 0) {
  10852. ticks[index].major = true;
  10853. }
  10854. }
  10855. return ticks;
  10856. }
  10857. function ticksFromTimestamps(scale, values, majorUnit) {
  10858. const ticks = [];
  10859. const map = {};
  10860. const ilen = values.length;
  10861. let i, value;
  10862. for(i = 0; i < ilen; ++i){
  10863. value = values[i];
  10864. map[value] = i;
  10865. ticks.push({
  10866. value,
  10867. major: false
  10868. });
  10869. }
  10870. return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  10871. }
  10872. class TimeScale extends Scale {
  10873. static id = 'time';
  10874. static defaults = {
  10875. bounds: 'data',
  10876. adapters: {},
  10877. time: {
  10878. parser: false,
  10879. unit: false,
  10880. round: false,
  10881. isoWeekday: false,
  10882. minUnit: 'millisecond',
  10883. displayFormats: {}
  10884. },
  10885. ticks: {
  10886. source: 'auto',
  10887. callback: false,
  10888. major: {
  10889. enabled: false
  10890. }
  10891. }
  10892. };
  10893. constructor(props){
  10894. super(props);
  10895. this._cache = {
  10896. data: [],
  10897. labels: [],
  10898. all: []
  10899. };
  10900. this._unit = 'day';
  10901. this._majorUnit = undefined;
  10902. this._offsets = {};
  10903. this._normalized = false;
  10904. this._parseOpts = undefined;
  10905. }
  10906. init(scaleOpts, opts = {}) {
  10907. const time = scaleOpts.time || (scaleOpts.time = {});
  10908. const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
  10909. adapter.init(opts);
  10910. helpers_segment.mergeIf(time.displayFormats, adapter.formats());
  10911. this._parseOpts = {
  10912. parser: time.parser,
  10913. round: time.round,
  10914. isoWeekday: time.isoWeekday
  10915. };
  10916. super.init(scaleOpts);
  10917. this._normalized = opts.normalized;
  10918. }
  10919. parse(raw, index) {
  10920. if (raw === undefined) {
  10921. return null;
  10922. }
  10923. return parse(this, raw);
  10924. }
  10925. beforeLayout() {
  10926. super.beforeLayout();
  10927. this._cache = {
  10928. data: [],
  10929. labels: [],
  10930. all: []
  10931. };
  10932. }
  10933. determineDataLimits() {
  10934. const options = this.options;
  10935. const adapter = this._adapter;
  10936. const unit = options.time.unit || 'day';
  10937. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  10938. function _applyBounds(bounds) {
  10939. if (!minDefined && !isNaN(bounds.min)) {
  10940. min = Math.min(min, bounds.min);
  10941. }
  10942. if (!maxDefined && !isNaN(bounds.max)) {
  10943. max = Math.max(max, bounds.max);
  10944. }
  10945. }
  10946. if (!minDefined || !maxDefined) {
  10947. _applyBounds(this._getLabelBounds());
  10948. if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
  10949. _applyBounds(this.getMinMax(false));
  10950. }
  10951. }
  10952. min = helpers_segment.isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
  10953. max = helpers_segment.isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
  10954. this.min = Math.min(min, max - 1);
  10955. this.max = Math.max(min + 1, max);
  10956. }
  10957. _getLabelBounds() {
  10958. const arr = this.getLabelTimestamps();
  10959. let min = Number.POSITIVE_INFINITY;
  10960. let max = Number.NEGATIVE_INFINITY;
  10961. if (arr.length) {
  10962. min = arr[0];
  10963. max = arr[arr.length - 1];
  10964. }
  10965. return {
  10966. min,
  10967. max
  10968. };
  10969. }
  10970. buildTicks() {
  10971. const options = this.options;
  10972. const timeOpts = options.time;
  10973. const tickOpts = options.ticks;
  10974. const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
  10975. if (options.bounds === 'ticks' && timestamps.length) {
  10976. this.min = this._userMin || timestamps[0];
  10977. this.max = this._userMax || timestamps[timestamps.length - 1];
  10978. }
  10979. const min = this.min;
  10980. const max = this.max;
  10981. const ticks = helpers_segment._filterBetween(timestamps, min, max);
  10982. this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
  10983. this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
  10984. this.initOffsets(timestamps);
  10985. if (options.reverse) {
  10986. ticks.reverse();
  10987. }
  10988. return ticksFromTimestamps(this, ticks, this._majorUnit);
  10989. }
  10990. afterAutoSkip() {
  10991. if (this.options.offsetAfterAutoskip) {
  10992. this.initOffsets(this.ticks.map((tick)=>+tick.value));
  10993. }
  10994. }
  10995. initOffsets(timestamps = []) {
  10996. let start = 0;
  10997. let end = 0;
  10998. let first, last;
  10999. if (this.options.offset && timestamps.length) {
  11000. first = this.getDecimalForValue(timestamps[0]);
  11001. if (timestamps.length === 1) {
  11002. start = 1 - first;
  11003. } else {
  11004. start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
  11005. }
  11006. last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
  11007. if (timestamps.length === 1) {
  11008. end = last;
  11009. } else {
  11010. end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
  11011. }
  11012. }
  11013. const limit = timestamps.length < 3 ? 0.5 : 0.25;
  11014. start = helpers_segment._limitValue(start, 0, limit);
  11015. end = helpers_segment._limitValue(end, 0, limit);
  11016. this._offsets = {
  11017. start,
  11018. end,
  11019. factor: 1 / (start + 1 + end)
  11020. };
  11021. }
  11022. _generate() {
  11023. const adapter = this._adapter;
  11024. const min = this.min;
  11025. const max = this.max;
  11026. const options = this.options;
  11027. const timeOpts = options.time;
  11028. const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
  11029. const stepSize = helpers_segment.valueOrDefault(options.ticks.stepSize, 1);
  11030. const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
  11031. const hasWeekday = helpers_segment.isNumber(weekday) || weekday === true;
  11032. const ticks = {};
  11033. let first = min;
  11034. let time, count;
  11035. if (hasWeekday) {
  11036. first = +adapter.startOf(first, 'isoWeek', weekday);
  11037. }
  11038. first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
  11039. if (adapter.diff(max, min, minor) > 100000 * stepSize) {
  11040. throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
  11041. }
  11042. const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
  11043. for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
  11044. addTick(ticks, time, timestamps);
  11045. }
  11046. if (time === max || options.bounds === 'ticks' || count === 1) {
  11047. addTick(ticks, time, timestamps);
  11048. }
  11049. return Object.keys(ticks).sort((a, b)=>a - b).map((x)=>+x);
  11050. }
  11051. getLabelForValue(value) {
  11052. const adapter = this._adapter;
  11053. const timeOpts = this.options.time;
  11054. if (timeOpts.tooltipFormat) {
  11055. return adapter.format(value, timeOpts.tooltipFormat);
  11056. }
  11057. return adapter.format(value, timeOpts.displayFormats.datetime);
  11058. }
  11059. format(value, format) {
  11060. const options = this.options;
  11061. const formats = options.time.displayFormats;
  11062. const unit = this._unit;
  11063. const fmt = format || formats[unit];
  11064. return this._adapter.format(value, fmt);
  11065. }
  11066. _tickFormatFunction(time, index, ticks, format) {
  11067. const options = this.options;
  11068. const formatter = options.ticks.callback;
  11069. if (formatter) {
  11070. return helpers_segment.callback(formatter, [
  11071. time,
  11072. index,
  11073. ticks
  11074. ], this);
  11075. }
  11076. const formats = options.time.displayFormats;
  11077. const unit = this._unit;
  11078. const majorUnit = this._majorUnit;
  11079. const minorFormat = unit && formats[unit];
  11080. const majorFormat = majorUnit && formats[majorUnit];
  11081. const tick = ticks[index];
  11082. const major = majorUnit && majorFormat && tick && tick.major;
  11083. return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
  11084. }
  11085. generateTickLabels(ticks) {
  11086. let i, ilen, tick;
  11087. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  11088. tick = ticks[i];
  11089. tick.label = this._tickFormatFunction(tick.value, i, ticks);
  11090. }
  11091. }
  11092. getDecimalForValue(value) {
  11093. return value === null ? NaN : (value - this.min) / (this.max - this.min);
  11094. }
  11095. getPixelForValue(value) {
  11096. const offsets = this._offsets;
  11097. const pos = this.getDecimalForValue(value);
  11098. return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
  11099. }
  11100. getValueForPixel(pixel) {
  11101. const offsets = this._offsets;
  11102. const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11103. return this.min + pos * (this.max - this.min);
  11104. }
  11105. _getLabelSize(label) {
  11106. const ticksOpts = this.options.ticks;
  11107. const tickLabelWidth = this.ctx.measureText(label).width;
  11108. const angle = helpers_segment.toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
  11109. const cosRotation = Math.cos(angle);
  11110. const sinRotation = Math.sin(angle);
  11111. const tickFontSize = this._resolveTickFontOptions(0).size;
  11112. return {
  11113. w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
  11114. h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
  11115. };
  11116. }
  11117. _getLabelCapacity(exampleTime) {
  11118. const timeOpts = this.options.time;
  11119. const displayFormats = timeOpts.displayFormats;
  11120. const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
  11121. const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
  11122. exampleTime
  11123. ], this._majorUnit), format);
  11124. const size = this._getLabelSize(exampleLabel);
  11125. const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
  11126. return capacity > 0 ? capacity : 1;
  11127. }
  11128. getDataTimestamps() {
  11129. let timestamps = this._cache.data || [];
  11130. let i, ilen;
  11131. if (timestamps.length) {
  11132. return timestamps;
  11133. }
  11134. const metas = this.getMatchingVisibleMetas();
  11135. if (this._normalized && metas.length) {
  11136. return this._cache.data = metas[0].controller.getAllParsedValues(this);
  11137. }
  11138. for(i = 0, ilen = metas.length; i < ilen; ++i){
  11139. timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
  11140. }
  11141. return this._cache.data = this.normalize(timestamps);
  11142. }
  11143. getLabelTimestamps() {
  11144. const timestamps = this._cache.labels || [];
  11145. let i, ilen;
  11146. if (timestamps.length) {
  11147. return timestamps;
  11148. }
  11149. const labels = this.getLabels();
  11150. for(i = 0, ilen = labels.length; i < ilen; ++i){
  11151. timestamps.push(parse(this, labels[i]));
  11152. }
  11153. return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
  11154. }
  11155. normalize(values) {
  11156. return helpers_segment._arrayUnique(values.sort(sorter));
  11157. }
  11158. }
  11159. function interpolate(table, val, reverse) {
  11160. let lo = 0;
  11161. let hi = table.length - 1;
  11162. let prevSource, nextSource, prevTarget, nextTarget;
  11163. if (reverse) {
  11164. if (val >= table[lo].pos && val <= table[hi].pos) {
  11165. ({ lo , hi } = helpers_segment._lookupByKey(table, 'pos', val));
  11166. }
  11167. ({ pos: prevSource , time: prevTarget } = table[lo]);
  11168. ({ pos: nextSource , time: nextTarget } = table[hi]);
  11169. } else {
  11170. if (val >= table[lo].time && val <= table[hi].time) {
  11171. ({ lo , hi } = helpers_segment._lookupByKey(table, 'time', val));
  11172. }
  11173. ({ time: prevSource , pos: prevTarget } = table[lo]);
  11174. ({ time: nextSource , pos: nextTarget } = table[hi]);
  11175. }
  11176. const span = nextSource - prevSource;
  11177. return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  11178. }
  11179. class TimeSeriesScale extends TimeScale {
  11180. static id = 'timeseries';
  11181. static defaults = TimeScale.defaults;
  11182. constructor(props){
  11183. super(props);
  11184. this._table = [];
  11185. this._minPos = undefined;
  11186. this._tableRange = undefined;
  11187. }
  11188. initOffsets() {
  11189. const timestamps = this._getTimestampsForTable();
  11190. const table = this._table = this.buildLookupTable(timestamps);
  11191. this._minPos = interpolate(table, this.min);
  11192. this._tableRange = interpolate(table, this.max) - this._minPos;
  11193. super.initOffsets(timestamps);
  11194. }
  11195. buildLookupTable(timestamps) {
  11196. const { min , max } = this;
  11197. const items = [];
  11198. const table = [];
  11199. let i, ilen, prev, curr, next;
  11200. for(i = 0, ilen = timestamps.length; i < ilen; ++i){
  11201. curr = timestamps[i];
  11202. if (curr >= min && curr <= max) {
  11203. items.push(curr);
  11204. }
  11205. }
  11206. if (items.length < 2) {
  11207. return [
  11208. {
  11209. time: min,
  11210. pos: 0
  11211. },
  11212. {
  11213. time: max,
  11214. pos: 1
  11215. }
  11216. ];
  11217. }
  11218. for(i = 0, ilen = items.length; i < ilen; ++i){
  11219. next = items[i + 1];
  11220. prev = items[i - 1];
  11221. curr = items[i];
  11222. if (Math.round((next + prev) / 2) !== curr) {
  11223. table.push({
  11224. time: curr,
  11225. pos: i / (ilen - 1)
  11226. });
  11227. }
  11228. }
  11229. return table;
  11230. }
  11231. _getTimestampsForTable() {
  11232. let timestamps = this._cache.all || [];
  11233. if (timestamps.length) {
  11234. return timestamps;
  11235. }
  11236. const data = this.getDataTimestamps();
  11237. const label = this.getLabelTimestamps();
  11238. if (data.length && label.length) {
  11239. timestamps = this.normalize(data.concat(label));
  11240. } else {
  11241. timestamps = data.length ? data : label;
  11242. }
  11243. timestamps = this._cache.all = timestamps;
  11244. return timestamps;
  11245. }
  11246. getDecimalForValue(value) {
  11247. return (interpolate(this._table, value) - this._minPos) / this._tableRange;
  11248. }
  11249. getValueForPixel(pixel) {
  11250. const offsets = this._offsets;
  11251. const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11252. return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
  11253. }
  11254. }
  11255. var scales = /*#__PURE__*/Object.freeze({
  11256. __proto__: null,
  11257. CategoryScale: CategoryScale,
  11258. LinearScale: LinearScale,
  11259. LogarithmicScale: LogarithmicScale,
  11260. RadialLinearScale: RadialLinearScale,
  11261. TimeScale: TimeScale,
  11262. TimeSeriesScale: TimeSeriesScale
  11263. });
  11264. const registerables = [
  11265. controllers,
  11266. elements,
  11267. plugins,
  11268. scales
  11269. ];
  11270. exports.Ticks = helpers_segment.Ticks;
  11271. exports.defaults = helpers_segment.defaults;
  11272. exports.Animation = Animation;
  11273. exports.Animations = Animations;
  11274. exports.ArcElement = ArcElement;
  11275. exports.BarController = BarController;
  11276. exports.BarElement = BarElement;
  11277. exports.BasePlatform = BasePlatform;
  11278. exports.BasicPlatform = BasicPlatform;
  11279. exports.BubbleController = BubbleController;
  11280. exports.CategoryScale = CategoryScale;
  11281. exports.Chart = Chart;
  11282. exports.Colors = plugin_colors;
  11283. exports.DatasetController = DatasetController;
  11284. exports.Decimation = plugin_decimation;
  11285. exports.DomPlatform = DomPlatform;
  11286. exports.DoughnutController = DoughnutController;
  11287. exports.Element = Element;
  11288. exports.Filler = index;
  11289. exports.Interaction = Interaction;
  11290. exports.Legend = plugin_legend;
  11291. exports.LineController = LineController;
  11292. exports.LineElement = LineElement;
  11293. exports.LinearScale = LinearScale;
  11294. exports.LogarithmicScale = LogarithmicScale;
  11295. exports.PieController = PieController;
  11296. exports.PointElement = PointElement;
  11297. exports.PolarAreaController = PolarAreaController;
  11298. exports.RadarController = RadarController;
  11299. exports.RadialLinearScale = RadialLinearScale;
  11300. exports.Scale = Scale;
  11301. exports.ScatterController = ScatterController;
  11302. exports.SubTitle = plugin_subtitle;
  11303. exports.TimeScale = TimeScale;
  11304. exports.TimeSeriesScale = TimeSeriesScale;
  11305. exports.Title = plugin_title;
  11306. exports.Tooltip = plugin_tooltip;
  11307. exports._adapters = adapters;
  11308. exports._detectPlatform = _detectPlatform;
  11309. exports.animator = animator;
  11310. exports.controllers = controllers;
  11311. exports.elements = elements;
  11312. exports.layouts = layouts;
  11313. exports.plugins = plugins;
  11314. exports.registerables = registerables;
  11315. exports.registry = registry;
  11316. exports.scales = scales;
  11317. //# sourceMappingURL=chart.cjs.map