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.

4286 lines
104 KiB

1 year ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. (function (root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. define(['d3'], function (d3) {
  4. return (root.Rickshaw = factory(d3));
  5. });
  6. } else if (typeof exports === 'object') {
  7. module.exports = factory(require('d3'));
  8. } else {
  9. root.Rickshaw = factory(d3);
  10. }
  11. }(this, function (d3) {
  12. /* jshint -W079 */
  13. var Rickshaw = {
  14. version: '1.6.1',
  15. namespace: function(namespace, obj) {
  16. var parts = namespace.split('.');
  17. var parent = Rickshaw;
  18. for(var i = 1, length = parts.length; i < length; i++) {
  19. var currentPart = parts[i];
  20. parent[currentPart] = parent[currentPart] || {};
  21. parent = parent[currentPart];
  22. }
  23. return parent;
  24. },
  25. keys: function(obj) {
  26. var keys = [];
  27. for (var key in obj) keys.push(key);
  28. return keys;
  29. },
  30. extend: function(destination, source) {
  31. for (var property in source) {
  32. destination[property] = source[property];
  33. }
  34. return destination;
  35. },
  36. clone: function(obj) {
  37. return JSON.parse(JSON.stringify(obj));
  38. }
  39. };
  40. /* Adapted from https://github.com/Jakobo/PTClass */
  41. /*
  42. Copyright (c) 2005-2010 Sam Stephenson
  43. Permission is hereby granted, free of charge, to any person obtaining a copy
  44. of this software and associated documentation files (the "Software"), to deal
  45. in the Software without restriction, including without limitation the rights
  46. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  47. copies of the Software, and to permit persons to whom the Software is
  48. furnished to do so, subject to the following conditions:
  49. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  50. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  51. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  52. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  53. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  54. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  55. SOFTWARE.
  56. */
  57. /* Based on Alex Arnell's inheritance implementation. */
  58. /** section: Language
  59. * class Class
  60. *
  61. * Manages Prototype's class-based OOP system.
  62. *
  63. * Refer to Prototype's web site for a [tutorial on classes and
  64. * inheritance](http://prototypejs.org/learn/class-inheritance).
  65. **/
  66. (function(globalContext) {
  67. /* ------------------------------------ */
  68. /* Import from object.js */
  69. /* ------------------------------------ */
  70. var _toString = Object.prototype.toString,
  71. NULL_TYPE = 'Null',
  72. UNDEFINED_TYPE = 'Undefined',
  73. BOOLEAN_TYPE = 'Boolean',
  74. NUMBER_TYPE = 'Number',
  75. STRING_TYPE = 'String',
  76. OBJECT_TYPE = 'Object',
  77. FUNCTION_CLASS = '[object Function]';
  78. function isFunction(object) {
  79. return _toString.call(object) === FUNCTION_CLASS;
  80. }
  81. function extend(destination, source) {
  82. for (var property in source) if (source.hasOwnProperty(property)) // modify protect primitive slaughter
  83. destination[property] = source[property];
  84. return destination;
  85. }
  86. function keys(object) {
  87. if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
  88. var results = [];
  89. for (var property in object) {
  90. if (object.hasOwnProperty(property)) {
  91. results.push(property);
  92. }
  93. }
  94. return results;
  95. }
  96. function Type(o) {
  97. switch(o) {
  98. case null: return NULL_TYPE;
  99. case (void 0): return UNDEFINED_TYPE;
  100. }
  101. var type = typeof o;
  102. switch(type) {
  103. case 'boolean': return BOOLEAN_TYPE;
  104. case 'number': return NUMBER_TYPE;
  105. case 'string': return STRING_TYPE;
  106. }
  107. return OBJECT_TYPE;
  108. }
  109. function isUndefined(object) {
  110. return typeof object === "undefined";
  111. }
  112. /* ------------------------------------ */
  113. /* Import from Function.js */
  114. /* ------------------------------------ */
  115. var slice = Array.prototype.slice;
  116. function argumentNames(fn) {
  117. var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
  118. .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
  119. .replace(/\s+/g, '').split(',');
  120. return names.length == 1 && !names[0] ? [] : names;
  121. }
  122. function wrap(fn, wrapper) {
  123. var __method = fn;
  124. return function() {
  125. var a = update([bind(__method, this)], arguments);
  126. return wrapper.apply(this, a);
  127. }
  128. }
  129. function update(array, args) {
  130. var arrayLength = array.length, length = args.length;
  131. while (length--) array[arrayLength + length] = args[length];
  132. return array;
  133. }
  134. function merge(array, args) {
  135. array = slice.call(array, 0);
  136. return update(array, args);
  137. }
  138. function bind(fn, context) {
  139. if (arguments.length < 2 && isUndefined(arguments[0])) return this;
  140. var __method = fn, args = slice.call(arguments, 2);
  141. return function() {
  142. var a = merge(args, arguments);
  143. return __method.apply(context, a);
  144. }
  145. }
  146. /* ------------------------------------ */
  147. /* Import from Prototype.js */
  148. /* ------------------------------------ */
  149. var emptyFunction = function(){};
  150. var Class = (function() {
  151. // Some versions of JScript fail to enumerate over properties, names of which
  152. // correspond to non-enumerable properties in the prototype chain
  153. var IS_DONTENUM_BUGGY = (function(){
  154. for (var p in { toString: 1 }) {
  155. // check actual property name, so that it works with augmented Object.prototype
  156. if (p === 'toString') return false;
  157. }
  158. return true;
  159. })();
  160. function subclass() {};
  161. function create() {
  162. var parent = null, properties = [].slice.apply(arguments);
  163. if (isFunction(properties[0]))
  164. parent = properties.shift();
  165. function klass() {
  166. this.initialize.apply(this, arguments);
  167. }
  168. extend(klass, Class.Methods);
  169. klass.superclass = parent;
  170. klass.subclasses = [];
  171. if (parent) {
  172. subclass.prototype = parent.prototype;
  173. klass.prototype = new subclass;
  174. try { parent.subclasses.push(klass) } catch(e) {}
  175. }
  176. for (var i = 0, length = properties.length; i < length; i++)
  177. klass.addMethods(properties[i]);
  178. if (!klass.prototype.initialize)
  179. klass.prototype.initialize = emptyFunction;
  180. klass.prototype.constructor = klass;
  181. return klass;
  182. }
  183. function addMethods(source) {
  184. var ancestor = this.superclass && this.superclass.prototype,
  185. properties = keys(source);
  186. // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties,
  187. // Force copy if they're not Object.prototype ones.
  188. // Do not copy other Object.prototype.* for performance reasons
  189. if (IS_DONTENUM_BUGGY) {
  190. if (source.toString != Object.prototype.toString)
  191. properties.push("toString");
  192. if (source.valueOf != Object.prototype.valueOf)
  193. properties.push("valueOf");
  194. }
  195. for (var i = 0, length = properties.length; i < length; i++) {
  196. var property = properties[i], value = source[property];
  197. if (ancestor && isFunction(value) &&
  198. argumentNames(value)[0] == "$super") {
  199. var method = value;
  200. value = wrap((function(m) {
  201. return function() { return ancestor[m].apply(this, arguments); };
  202. })(property), method);
  203. value.valueOf = bind(method.valueOf, method);
  204. value.toString = bind(method.toString, method);
  205. }
  206. this.prototype[property] = value;
  207. }
  208. return this;
  209. }
  210. return {
  211. create: create,
  212. Methods: {
  213. addMethods: addMethods
  214. }
  215. };
  216. })();
  217. if (globalContext.exports) {
  218. globalContext.exports.Class = Class;
  219. }
  220. else {
  221. globalContext.Class = Class;
  222. }
  223. })(Rickshaw);
  224. Rickshaw.namespace('Rickshaw.Compat.ClassList');
  225. Rickshaw.Compat.ClassList = function() {
  226. /* adapted from http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
  227. if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
  228. (function (view) {
  229. "use strict";
  230. var
  231. classListProp = "classList"
  232. , protoProp = "prototype"
  233. , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
  234. , objCtr = Object
  235. , strTrim = String[protoProp].trim || function () {
  236. return this.replace(/^\s+|\s+$/g, "");
  237. }
  238. , arrIndexOf = Array[protoProp].indexOf || function (item) {
  239. var
  240. i = 0
  241. , len = this.length
  242. ;
  243. for (; i < len; i++) {
  244. if (i in this && this[i] === item) {
  245. return i;
  246. }
  247. }
  248. return -1;
  249. }
  250. // Vendors: please allow content code to instantiate DOMExceptions
  251. , DOMEx = function (type, message) {
  252. this.name = type;
  253. this.code = DOMException[type];
  254. this.message = message;
  255. }
  256. , checkTokenAndGetIndex = function (classList, token) {
  257. if (token === "") {
  258. throw new DOMEx(
  259. "SYNTAX_ERR"
  260. , "An invalid or illegal string was specified"
  261. );
  262. }
  263. if (/\s/.test(token)) {
  264. throw new DOMEx(
  265. "INVALID_CHARACTER_ERR"
  266. , "String contains an invalid character"
  267. );
  268. }
  269. return arrIndexOf.call(classList, token);
  270. }
  271. , ClassList = function (elem) {
  272. var
  273. trimmedClasses = strTrim.call(elem.className)
  274. , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
  275. , i = 0
  276. , len = classes.length
  277. ;
  278. for (; i < len; i++) {
  279. this.push(classes[i]);
  280. }
  281. this._updateClassName = function () {
  282. elem.className = this.toString();
  283. };
  284. }
  285. , classListProto = ClassList[protoProp] = []
  286. , classListGetter = function () {
  287. return new ClassList(this);
  288. }
  289. ;
  290. // Most DOMException implementations don't allow calling DOMException's toString()
  291. // on non-DOMExceptions. Error's toString() is sufficient here.
  292. DOMEx[protoProp] = Error[protoProp];
  293. classListProto.item = function (i) {
  294. return this[i] || null;
  295. };
  296. classListProto.contains = function (token) {
  297. token += "";
  298. return checkTokenAndGetIndex(this, token) !== -1;
  299. };
  300. classListProto.add = function (token) {
  301. token += "";
  302. if (checkTokenAndGetIndex(this, token) === -1) {
  303. this.push(token);
  304. this._updateClassName();
  305. }
  306. };
  307. classListProto.remove = function (token) {
  308. token += "";
  309. var index = checkTokenAndGetIndex(this, token);
  310. if (index !== -1) {
  311. this.splice(index, 1);
  312. this._updateClassName();
  313. }
  314. };
  315. classListProto.toggle = function (token) {
  316. token += "";
  317. if (checkTokenAndGetIndex(this, token) === -1) {
  318. this.add(token);
  319. } else {
  320. this.remove(token);
  321. }
  322. };
  323. classListProto.toString = function () {
  324. return this.join(" ");
  325. };
  326. if (objCtr.defineProperty) {
  327. var classListPropDesc = {
  328. get: classListGetter
  329. , enumerable: true
  330. , configurable: true
  331. };
  332. try {
  333. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  334. } catch (ex) { // IE 8 doesn't support enumerable:true
  335. if (ex.number === -0x7FF5EC54) {
  336. classListPropDesc.enumerable = false;
  337. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  338. }
  339. }
  340. } else if (objCtr[protoProp].__defineGetter__) {
  341. elemCtrProto.__defineGetter__(classListProp, classListGetter);
  342. }
  343. }(window));
  344. }
  345. };
  346. if ( (typeof RICKSHAW_NO_COMPAT !== "undefined" && !RICKSHAW_NO_COMPAT) || typeof RICKSHAW_NO_COMPAT === "undefined") {
  347. new Rickshaw.Compat.ClassList();
  348. }
  349. Rickshaw.namespace('Rickshaw.Graph');
  350. Rickshaw.Graph = function(args) {
  351. var self = this;
  352. this.initialize = function(args) {
  353. if (!args.element) throw "Rickshaw.Graph needs a reference to an element";
  354. if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element";
  355. this.element = args.element;
  356. this.series = args.series;
  357. this.window = {};
  358. this.updateCallbacks = [];
  359. this.configureCallbacks = [];
  360. this.defaults = {
  361. interpolation: 'cardinal',
  362. offset: 'zero',
  363. min: undefined,
  364. max: undefined,
  365. preserve: false,
  366. xScale: undefined,
  367. yScale: undefined,
  368. stack: true
  369. };
  370. this._loadRenderers();
  371. this.configure(args);
  372. this.validateSeries(args.series);
  373. this.series.active = function() { return self.series.filter( function(s) { return !s.disabled } ) };
  374. this.setSize({ width: args.width, height: args.height });
  375. this.element.classList.add('rickshaw_graph');
  376. this.vis = d3.select(this.element)
  377. .append("svg:svg")
  378. .attr('width', this.width)
  379. .attr('height', this.height);
  380. this.discoverRange();
  381. };
  382. this._loadRenderers = function() {
  383. for (var name in Rickshaw.Graph.Renderer) {
  384. if (!name || !Rickshaw.Graph.Renderer.hasOwnProperty(name)) continue;
  385. var r = Rickshaw.Graph.Renderer[name];
  386. if (!r || !r.prototype || !r.prototype.render) continue;
  387. self.registerRenderer(new r( { graph: self } ));
  388. }
  389. };
  390. this.validateSeries = function(series) {
  391. if (!Array.isArray(series) && !(series instanceof Rickshaw.Series)) {
  392. var seriesSignature = Object.prototype.toString.apply(series);
  393. throw "series is not an array: " + seriesSignature;
  394. }
  395. var pointsCount;
  396. series.forEach( function(s) {
  397. if (!(s instanceof Object)) {
  398. throw "series element is not an object: " + s;
  399. }
  400. if (!(s.data)) {
  401. throw "series has no data: " + JSON.stringify(s);
  402. }
  403. if (!Array.isArray(s.data)) {
  404. throw "series data is not an array: " + JSON.stringify(s.data);
  405. }
  406. if (s.data.length > 0) {
  407. var x = s.data[0].x;
  408. var y = s.data[0].y;
  409. if (typeof x != 'number' || ( typeof y != 'number' && y !== null ) ) {
  410. throw "x and y properties of points should be numbers instead of " +
  411. (typeof x) + " and " + (typeof y);
  412. }
  413. }
  414. if (s.data.length >= 3) {
  415. // probe to sanity check sort order
  416. if (s.data[2].x < s.data[1].x || s.data[1].x < s.data[0].x || s.data[s.data.length - 1].x < s.data[0].x) {
  417. throw "series data needs to be sorted on x values for series name: " + s.name;
  418. }
  419. }
  420. }, this );
  421. };
  422. this.dataDomain = function() {
  423. var data = this.series.map( function(s) { return s.data } );
  424. var min = d3.min( data.map( function(d) { return d[0].x } ) );
  425. var max = d3.max( data.map( function(d) { return d[d.length - 1].x } ) );
  426. return [min, max];
  427. };
  428. this.discoverRange = function() {
  429. var domain = this.renderer.domain();
  430. // this.*Scale is coming from the configuration dictionary
  431. // which may be referenced by the Graph creator, or shared
  432. // with other Graphs. We need to ensure we copy the scale
  433. // so that our mutations do not change the object given to us.
  434. // Hence the .copy()
  435. this.x = (this.xScale || d3.scale.linear()).copy().domain(domain.x).range([0, this.width]);
  436. this.y = (this.yScale || d3.scale.linear()).copy().domain(domain.y).range([this.height, 0]);
  437. this.x.magnitude = d3.scale.linear()
  438. .domain([domain.x[0] - domain.x[0], domain.x[1] - domain.x[0]])
  439. .range([0, this.width]);
  440. this.y.magnitude = d3.scale.linear()
  441. .domain([domain.y[0] - domain.y[0], domain.y[1] - domain.y[0]])
  442. .range([0, this.height]);
  443. };
  444. this.render = function() {
  445. var stackedData = this.stackData();
  446. this.discoverRange();
  447. this.renderer.render();
  448. this.updateCallbacks.forEach( function(callback) {
  449. callback();
  450. } );
  451. };
  452. this.update = this.render;
  453. this.stackData = function() {
  454. var data = this.series.active()
  455. .map( function(d) { return d.data } )
  456. .map( function(d) { return d.filter( function(d) { return this._slice(d) }, this ) }, this);
  457. var preserve = this.preserve;
  458. if (!preserve) {
  459. this.series.forEach( function(series) {
  460. if (series.scale) {
  461. // data must be preserved when a scale is used
  462. preserve = true;
  463. }
  464. } );
  465. }
  466. data = preserve ? Rickshaw.clone(data) : data;
  467. this.series.active().forEach( function(series, index) {
  468. if (series.scale) {
  469. // apply scale to each series
  470. var seriesData = data[index];
  471. if(seriesData) {
  472. seriesData.forEach( function(d) {
  473. d.y = series.scale(d.y);
  474. } );
  475. }
  476. }
  477. } );
  478. this.stackData.hooks.data.forEach( function(entry) {
  479. data = entry.f.apply(self, [data]);
  480. } );
  481. var stackedData;
  482. if (!this.renderer.unstack) {
  483. this._validateStackable();
  484. var layout = d3.layout.stack();
  485. layout.offset( self.offset );
  486. stackedData = layout(data);
  487. }
  488. stackedData = stackedData || data;
  489. if (this.renderer.unstack) {
  490. stackedData.forEach( function(seriesData) {
  491. seriesData.forEach( function(d) {
  492. d.y0 = d.y0 === undefined ? 0 : d.y0;
  493. } );
  494. } );
  495. }
  496. this.stackData.hooks.after.forEach( function(entry) {
  497. stackedData = entry.f.apply(self, [data]);
  498. } );
  499. var i = 0;
  500. this.series.forEach( function(series) {
  501. if (series.disabled) return;
  502. series.stack = stackedData[i++];
  503. } );
  504. this.stackedData = stackedData;
  505. return stackedData;
  506. };
  507. this._validateStackable = function() {
  508. var series = this.series;
  509. var pointsCount;
  510. series.forEach( function(s) {
  511. pointsCount = pointsCount || s.data.length;
  512. if (pointsCount && s.data.length != pointsCount) {
  513. throw "stacked series cannot have differing numbers of points: " +
  514. pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.fill()";
  515. }
  516. }, this );
  517. };
  518. this.stackData.hooks = { data: [], after: [] };
  519. this._slice = function(d) {
  520. if (this.window.xMin || this.window.xMax) {
  521. var isInRange = true;
  522. if (this.window.xMin && d.x < this.window.xMin) isInRange = false;
  523. if (this.window.xMax && d.x > this.window.xMax) isInRange = false;
  524. return isInRange;
  525. }
  526. return true;
  527. };
  528. this.onUpdate = function(callback) {
  529. this.updateCallbacks.push(callback);
  530. };
  531. this.onConfigure = function(callback) {
  532. this.configureCallbacks.push(callback);
  533. };
  534. this.registerRenderer = function(renderer) {
  535. this._renderers = this._renderers || {};
  536. this._renderers[renderer.name] = renderer;
  537. };
  538. this.configure = function(args) {
  539. this.config = this.config || {};
  540. if (args.width || args.height) {
  541. this.setSize(args);
  542. }
  543. Rickshaw.keys(this.defaults).forEach( function(k) {
  544. this.config[k] = k in args ? args[k]
  545. : k in this ? this[k]
  546. : this.defaults[k];
  547. }, this );
  548. Rickshaw.keys(this.config).forEach( function(k) {
  549. this[k] = this.config[k];
  550. }, this );
  551. if ('stack' in args) args.unstack = !args.stack;
  552. var renderer = args.renderer || (this.renderer && this.renderer.name) || 'stack';
  553. this.setRenderer(renderer, args);
  554. this.configureCallbacks.forEach( function(callback) {
  555. callback(args);
  556. } );
  557. };
  558. this.setRenderer = function(r, args) {
  559. if (typeof r == 'function') {
  560. this.renderer = new r( { graph: self } );
  561. this.registerRenderer(this.renderer);
  562. } else {
  563. if (!this._renderers[r]) {
  564. throw "couldn't find renderer " + r;
  565. }
  566. this.renderer = this._renderers[r];
  567. }
  568. if (typeof args == 'object') {
  569. this.renderer.configure(args);
  570. }
  571. };
  572. this.setSize = function(args) {
  573. args = args || {};
  574. if (typeof window !== 'undefined') {
  575. var style = window.getComputedStyle(this.element, null);
  576. var elementWidth = parseInt(style.getPropertyValue('width'), 10);
  577. var elementHeight = parseInt(style.getPropertyValue('height'), 10);
  578. }
  579. this.width = args.width || elementWidth || 400;
  580. this.height = args.height || elementHeight || 250;
  581. this.vis && this.vis
  582. .attr('width', this.width)
  583. .attr('height', this.height);
  584. };
  585. this.initialize(args);
  586. };
  587. Rickshaw.namespace('Rickshaw.Fixtures.Color');
  588. Rickshaw.Fixtures.Color = function() {
  589. this.schemes = {};
  590. this.schemes.spectrum14 = [
  591. '#ecb796',
  592. '#dc8f70',
  593. '#b2a470',
  594. '#92875a',
  595. '#716c49',
  596. '#d2ed82',
  597. '#bbe468',
  598. '#a1d05d',
  599. '#e7cbe6',
  600. '#d8aad6',
  601. '#a888c2',
  602. '#9dc2d3',
  603. '#649eb9',
  604. '#387aa3'
  605. ].reverse();
  606. this.schemes.spectrum2000 = [
  607. '#57306f',
  608. '#514c76',
  609. '#646583',
  610. '#738394',
  611. '#6b9c7d',
  612. '#84b665',
  613. '#a7ca50',
  614. '#bfe746',
  615. '#e2f528',
  616. '#fff726',
  617. '#ecdd00',
  618. '#d4b11d',
  619. '#de8800',
  620. '#de4800',
  621. '#c91515',
  622. '#9a0000',
  623. '#7b0429',
  624. '#580839',
  625. '#31082b'
  626. ];
  627. this.schemes.spectrum2001 = [
  628. '#2f243f',
  629. '#3c2c55',
  630. '#4a3768',
  631. '#565270',
  632. '#6b6b7c',
  633. '#72957f',
  634. '#86ad6e',
  635. '#a1bc5e',
  636. '#b8d954',
  637. '#d3e04e',
  638. '#ccad2a',
  639. '#cc8412',
  640. '#c1521d',
  641. '#ad3821',
  642. '#8a1010',
  643. '#681717',
  644. '#531e1e',
  645. '#3d1818',
  646. '#320a1b'
  647. ];
  648. this.schemes.classic9 = [
  649. '#423d4f',
  650. '#4a6860',
  651. '#848f39',
  652. '#a2b73c',
  653. '#ddcb53',
  654. '#c5a32f',
  655. '#7d5836',
  656. '#963b20',
  657. '#7c2626',
  658. '#491d37',
  659. '#2f254a'
  660. ].reverse();
  661. this.schemes.httpStatus = {
  662. 503: '#ea5029',
  663. 502: '#d23f14',
  664. 500: '#bf3613',
  665. 410: '#efacea',
  666. 409: '#e291dc',
  667. 403: '#f457e8',
  668. 408: '#e121d2',
  669. 401: '#b92dae',
  670. 405: '#f47ceb',
  671. 404: '#a82a9f',
  672. 400: '#b263c6',
  673. 301: '#6fa024',
  674. 302: '#87c32b',
  675. 307: '#a0d84c',
  676. 304: '#28b55c',
  677. 200: '#1a4f74',
  678. 206: '#27839f',
  679. 201: '#52adc9',
  680. 202: '#7c979f',
  681. 203: '#a5b8bd',
  682. 204: '#c1cdd1'
  683. };
  684. this.schemes.colorwheel = [
  685. '#b5b6a9',
  686. '#858772',
  687. '#785f43',
  688. '#96557e',
  689. '#4682b4',
  690. '#65b9ac',
  691. '#73c03a',
  692. '#cb513a'
  693. ].reverse();
  694. this.schemes.cool = [
  695. '#5e9d2f',
  696. '#73c03a',
  697. '#4682b4',
  698. '#7bc3b8',
  699. '#a9884e',
  700. '#c1b266',
  701. '#a47493',
  702. '#c09fb5'
  703. ];
  704. this.schemes.munin = [
  705. '#00cc00',
  706. '#0066b3',
  707. '#ff8000',
  708. '#ffcc00',
  709. '#330099',
  710. '#990099',
  711. '#ccff00',
  712. '#ff0000',
  713. '#808080',
  714. '#008f00',
  715. '#00487d',
  716. '#b35a00',
  717. '#b38f00',
  718. '#6b006b',
  719. '#8fb300',
  720. '#b30000',
  721. '#bebebe',
  722. '#80ff80',
  723. '#80c9ff',
  724. '#ffc080',
  725. '#ffe680',
  726. '#aa80ff',
  727. '#ee00cc',
  728. '#ff8080',
  729. '#666600',
  730. '#ffbfff',
  731. '#00ffcc',
  732. '#cc6699',
  733. '#999900'
  734. ];
  735. };
  736. Rickshaw.namespace('Rickshaw.Fixtures.RandomData');
  737. Rickshaw.Fixtures.RandomData = function(timeInterval) {
  738. var addData;
  739. timeInterval = timeInterval || 1;
  740. var lastRandomValue = 200;
  741. var timeBase = Math.floor(new Date().getTime() / 1000);
  742. this.addData = function(data) {
  743. var randomValue = Math.random() * 100 + 15 + lastRandomValue;
  744. var index = data[0].length;
  745. var counter = 1;
  746. data.forEach( function(series) {
  747. var randomVariance = Math.random() * 20;
  748. var v = randomValue / 25 + counter++ +
  749. (Math.cos((index * counter * 11) / 960) + 2) * 15 +
  750. (Math.cos(index / 7) + 2) * 7 +
  751. (Math.cos(index / 17) + 2) * 1;
  752. series.push( { x: (index * timeInterval) + timeBase, y: v + randomVariance } );
  753. } );
  754. lastRandomValue = randomValue * 0.85;
  755. };
  756. this.removeData = function(data) {
  757. data.forEach( function(series) {
  758. series.shift();
  759. } );
  760. timeBase += timeInterval;
  761. };
  762. };
  763. Rickshaw.namespace('Rickshaw.Fixtures.Time');
  764. Rickshaw.Fixtures.Time = function() {
  765. var self = this;
  766. this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  767. this.units = [
  768. {
  769. name: 'decade',
  770. seconds: 86400 * 365.25 * 10,
  771. formatter: function(d) { return (parseInt(d.getUTCFullYear() / 10, 10) * 10) }
  772. }, {
  773. name: 'year',
  774. seconds: 86400 * 365.25,
  775. formatter: function(d) { return d.getUTCFullYear() }
  776. }, {
  777. name: 'month',
  778. seconds: 86400 * 30.5,
  779. formatter: function(d) { return self.months[d.getUTCMonth()] }
  780. }, {
  781. name: 'week',
  782. seconds: 86400 * 7,
  783. formatter: function(d) { return self.formatDate(d) }
  784. }, {
  785. name: 'day',
  786. seconds: 86400,
  787. formatter: function(d) { return d.getUTCDate() }
  788. }, {
  789. name: '6 hour',
  790. seconds: 3600 * 6,
  791. formatter: function(d) { return self.formatTime(d) }
  792. }, {
  793. name: 'hour',
  794. seconds: 3600,
  795. formatter: function(d) { return self.formatTime(d) }
  796. }, {
  797. name: '15 minute',
  798. seconds: 60 * 15,
  799. formatter: function(d) { return self.formatTime(d) }
  800. }, {
  801. name: 'minute',
  802. seconds: 60,
  803. formatter: function(d) { return d.getUTCMinutes() }
  804. }, {
  805. name: '15 second',
  806. seconds: 15,
  807. formatter: function(d) { return d.getUTCSeconds() + 's' }
  808. }, {
  809. name: 'second',
  810. seconds: 1,
  811. formatter: function(d) { return d.getUTCSeconds() + 's' }
  812. }, {
  813. name: 'decisecond',
  814. seconds: 1/10,
  815. formatter: function(d) { return d.getUTCMilliseconds() + 'ms' }
  816. }, {
  817. name: 'centisecond',
  818. seconds: 1/100,
  819. formatter: function(d) { return d.getUTCMilliseconds() + 'ms' }
  820. }
  821. ];
  822. this.unit = function(unitName) {
  823. return this.units.filter( function(unit) { return unitName == unit.name } ).shift();
  824. };
  825. this.formatDate = function(d) {
  826. return d3.time.format('%b %e')(d);
  827. };
  828. this.formatTime = function(d) {
  829. return d.toUTCString().match(/(\d+:\d+):/)[1];
  830. };
  831. this.ceil = function(time, unit) {
  832. var date, floor, year;
  833. if (unit.name == 'month') {
  834. date = new Date(time * 1000);
  835. floor = Date.UTC(date.getUTCFullYear(), date.getUTCMonth()) / 1000;
  836. if (floor == time) return time;
  837. year = date.getUTCFullYear();
  838. var month = date.getUTCMonth();
  839. if (month == 11) {
  840. month = 0;
  841. year = year + 1;
  842. } else {
  843. month += 1;
  844. }
  845. return Date.UTC(year, month) / 1000;
  846. }
  847. if (unit.name == 'year') {
  848. date = new Date(time * 1000);
  849. floor = Date.UTC(date.getUTCFullYear(), 0) / 1000;
  850. if (floor == time) return time;
  851. year = date.getUTCFullYear() + 1;
  852. return Date.UTC(year, 0) / 1000;
  853. }
  854. return Math.ceil(time / unit.seconds) * unit.seconds;
  855. };
  856. };
  857. Rickshaw.namespace('Rickshaw.Fixtures.Time.Local');
  858. Rickshaw.Fixtures.Time.Local = function() {
  859. var self = this;
  860. this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  861. this.units = [
  862. {
  863. name: 'decade',
  864. seconds: 86400 * 365.25 * 10,
  865. formatter: function(d) { return (parseInt(d.getFullYear() / 10, 10) * 10) }
  866. }, {
  867. name: 'year',
  868. seconds: 86400 * 365.25,
  869. formatter: function(d) { return d.getFullYear() }
  870. }, {
  871. name: 'month',
  872. seconds: 86400 * 30.5,
  873. formatter: function(d) { return self.months[d.getMonth()] }
  874. }, {
  875. name: 'week',
  876. seconds: 86400 * 7,
  877. formatter: function(d) { return self.formatDate(d) }
  878. }, {
  879. name: 'day',
  880. seconds: 86400,
  881. formatter: function(d) { return d.getDate() }
  882. }, {
  883. name: '6 hour',
  884. seconds: 3600 * 6,
  885. formatter: function(d) { return self.formatTime(d) }
  886. }, {
  887. name: 'hour',
  888. seconds: 3600,
  889. formatter: function(d) { return self.formatTime(d) }
  890. }, {
  891. name: '15 minute',
  892. seconds: 60 * 15,
  893. formatter: function(d) { return self.formatTime(d) }
  894. }, {
  895. name: 'minute',
  896. seconds: 60,
  897. formatter: function(d) { return d.getMinutes() }
  898. }, {
  899. name: '15 second',
  900. seconds: 15,
  901. formatter: function(d) { return d.getSeconds() + 's' }
  902. }, {
  903. name: 'second',
  904. seconds: 1,
  905. formatter: function(d) { return d.getSeconds() + 's' }
  906. }, {
  907. name: 'decisecond',
  908. seconds: 1/10,
  909. formatter: function(d) { return d.getMilliseconds() + 'ms' }
  910. }, {
  911. name: 'centisecond',
  912. seconds: 1/100,
  913. formatter: function(d) { return d.getMilliseconds() + 'ms' }
  914. }
  915. ];
  916. this.unit = function(unitName) {
  917. return this.units.filter( function(unit) { return unitName == unit.name } ).shift();
  918. };
  919. this.formatDate = function(d) {
  920. return d3.time.format('%b %e')(d);
  921. };
  922. this.formatTime = function(d) {
  923. return d.toString().match(/(\d+:\d+):/)[1];
  924. };
  925. this.ceil = function(time, unit) {
  926. var date, floor, year, offset;
  927. if (unit.name == 'day') {
  928. var nearFuture = new Date((time + unit.seconds - 1) * 1000);
  929. var rounded = new Date(0);
  930. rounded.setFullYear(nearFuture.getFullYear());
  931. rounded.setMonth(nearFuture.getMonth());
  932. rounded.setDate(nearFuture.getDate());
  933. rounded.setMilliseconds(0);
  934. rounded.setSeconds(0);
  935. rounded.setMinutes(0);
  936. rounded.setHours(0);
  937. return rounded.getTime() / 1000;
  938. }
  939. if (unit.name == 'month') {
  940. date = new Date(time * 1000);
  941. floor = new Date(date.getFullYear(), date.getMonth()).getTime() / 1000;
  942. if (floor == time) return time;
  943. year = date.getFullYear();
  944. var month = date.getMonth();
  945. if (month == 11) {
  946. month = 0;
  947. year = year + 1;
  948. } else {
  949. month += 1;
  950. }
  951. return new Date(year, month).getTime() / 1000;
  952. }
  953. if (unit.name == 'year') {
  954. date = new Date(time * 1000);
  955. floor = new Date(date.getUTCFullYear(), 0).getTime() / 1000;
  956. if (floor == time) return time;
  957. year = date.getFullYear() + 1;
  958. return new Date(year, 0).getTime() / 1000;
  959. }
  960. offset = new Date(time * 1000).getTimezoneOffset() * 60;
  961. return Math.ceil((time - offset) / unit.seconds) * unit.seconds + offset;
  962. };
  963. };
  964. Rickshaw.namespace('Rickshaw.Fixtures.Number');
  965. Rickshaw.Fixtures.Number.formatKMBT = function(y) {
  966. var abs_y = Math.abs(y);
  967. if (abs_y >= 1000000000000) { return y / 1000000000000 + "T" }
  968. else if (abs_y >= 1000000000) { return y / 1000000000 + "B" }
  969. else if (abs_y >= 1000000) { return y / 1000000 + "M" }
  970. else if (abs_y >= 1000) { return y / 1000 + "K" }
  971. else if (abs_y < 1 && abs_y > 0) { return y.toFixed(2) }
  972. else if (abs_y === 0) { return '' }
  973. else { return y }
  974. };
  975. Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) {
  976. var abs_y = Math.abs(y);
  977. if (abs_y >= 1125899906842624) { return y / 1125899906842624 + "P" }
  978. else if (abs_y >= 1099511627776){ return y / 1099511627776 + "T" }
  979. else if (abs_y >= 1073741824) { return y / 1073741824 + "G" }
  980. else if (abs_y >= 1048576) { return y / 1048576 + "M" }
  981. else if (abs_y >= 1024) { return y / 1024 + "K" }
  982. else if (abs_y < 1 && abs_y > 0) { return y.toFixed(2) }
  983. else if (abs_y === 0) { return '' }
  984. else { return y }
  985. };
  986. Rickshaw.namespace("Rickshaw.Color.Palette");
  987. Rickshaw.Color.Palette = function(args) {
  988. var color = new Rickshaw.Fixtures.Color();
  989. args = args || {};
  990. this.schemes = {};
  991. this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel;
  992. this.runningIndex = 0;
  993. this.generatorIndex = 0;
  994. if (args.interpolatedStopCount) {
  995. var schemeCount = this.scheme.length - 1;
  996. var i, j, scheme = [];
  997. for (i = 0; i < schemeCount; i++) {
  998. scheme.push(this.scheme[i]);
  999. var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]);
  1000. for (j = 1; j < args.interpolatedStopCount; j++) {
  1001. scheme.push(generator((1 / args.interpolatedStopCount) * j));
  1002. }
  1003. }
  1004. scheme.push(this.scheme[this.scheme.length - 1]);
  1005. this.scheme = scheme;
  1006. }
  1007. this.rotateCount = this.scheme.length;
  1008. this.color = function(key) {
  1009. return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || '#808080';
  1010. };
  1011. this.interpolateColor = function() {
  1012. if (!Array.isArray(this.scheme)) return;
  1013. var color;
  1014. if (this.generatorIndex == this.rotateCount * 2 - 1) {
  1015. color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(0.5);
  1016. this.generatorIndex = 0;
  1017. this.rotateCount *= 2;
  1018. } else {
  1019. color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(0.5);
  1020. this.generatorIndex++;
  1021. }
  1022. this.scheme.push(color);
  1023. return color;
  1024. };
  1025. };
  1026. Rickshaw.namespace('Rickshaw.Graph.Ajax');
  1027. Rickshaw.Graph.Ajax = Rickshaw.Class.create( {
  1028. initialize: function(args) {
  1029. this.dataURL = args.dataURL;
  1030. this.onData = args.onData || function(d) { return d };
  1031. this.onComplete = args.onComplete || function() {};
  1032. this.onError = args.onError || function() {};
  1033. this.args = args; // pass through to Rickshaw.Graph
  1034. this.request();
  1035. },
  1036. request: function() {
  1037. jQuery.ajax( {
  1038. url: this.dataURL,
  1039. dataType: 'json',
  1040. success: this.success.bind(this),
  1041. error: this.error.bind(this)
  1042. } );
  1043. },
  1044. error: function() {
  1045. console.log("error loading dataURL: " + this.dataURL);
  1046. this.onError(this);
  1047. },
  1048. success: function(data, status) {
  1049. data = this.onData(data);
  1050. this.args.series = this._splice({ data: data, series: this.args.series });
  1051. this.graph = this.graph || new Rickshaw.Graph(this.args);
  1052. this.graph.render();
  1053. this.onComplete(this);
  1054. },
  1055. _splice: function(args) {
  1056. var data = args.data;
  1057. var series = args.series;
  1058. if (!args.series) return data;
  1059. series.forEach( function(s) {
  1060. var seriesKey = s.key || s.name;
  1061. if (!seriesKey) throw "series needs a key or a name";
  1062. data.forEach( function(d) {
  1063. var dataKey = d.key || d.name;
  1064. if (!dataKey) throw "data needs a key or a name";
  1065. if (seriesKey == dataKey) {
  1066. var properties = ['color', 'name', 'data'];
  1067. properties.forEach( function(p) {
  1068. if (d[p]) s[p] = d[p];
  1069. } );
  1070. }
  1071. } );
  1072. } );
  1073. return series;
  1074. }
  1075. } );
  1076. Rickshaw.namespace('Rickshaw.Graph.Annotate');
  1077. Rickshaw.Graph.Annotate = function(args) {
  1078. var graph = this.graph = args.graph;
  1079. this.elements = { timeline: args.element };
  1080. var self = this;
  1081. this.data = {};
  1082. this.elements.timeline.classList.add('rickshaw_annotation_timeline');
  1083. this.add = function(time, content, end_time) {
  1084. self.data[time] = self.data[time] || {'boxes': []};
  1085. self.data[time].boxes.push({content: content, end: end_time});
  1086. };
  1087. this.update = function() {
  1088. Rickshaw.keys(self.data).forEach( function(time) {
  1089. var annotation = self.data[time];
  1090. var left = self.graph.x(time);
  1091. if (left < 0 || left > self.graph.x.range()[1]) {
  1092. if (annotation.element) {
  1093. annotation.line.classList.add('offscreen');
  1094. annotation.element.style.display = 'none';
  1095. }
  1096. annotation.boxes.forEach( function(box) {
  1097. if ( box.rangeElement ) box.rangeElement.classList.add('offscreen');
  1098. });
  1099. return;
  1100. }
  1101. if (!annotation.element) {
  1102. var element = annotation.element = document.createElement('div');
  1103. element.classList.add('annotation');
  1104. this.elements.timeline.appendChild(element);
  1105. element.addEventListener('click', function(e) {
  1106. element.classList.toggle('active');
  1107. annotation.line.classList.toggle('active');
  1108. annotation.boxes.forEach( function(box) {
  1109. if ( box.rangeElement ) box.rangeElement.classList.toggle('active');
  1110. });
  1111. }, false);
  1112. }
  1113. annotation.element.style.left = left + 'px';
  1114. annotation.element.style.display = 'block';
  1115. annotation.boxes.forEach( function(box) {
  1116. var element = box.element;
  1117. if (!element) {
  1118. element = box.element = document.createElement('div');
  1119. element.classList.add('content');
  1120. element.innerHTML = box.content;
  1121. annotation.element.appendChild(element);
  1122. annotation.line = document.createElement('div');
  1123. annotation.line.classList.add('annotation_line');
  1124. self.graph.element.appendChild(annotation.line);
  1125. if ( box.end ) {
  1126. box.rangeElement = document.createElement('div');
  1127. box.rangeElement.classList.add('annotation_range');
  1128. self.graph.element.appendChild(box.rangeElement);
  1129. }
  1130. }
  1131. if ( box.end ) {
  1132. var annotationRangeStart = left;
  1133. var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] );
  1134. // annotation makes more sense at end
  1135. if ( annotationRangeStart > annotationRangeEnd ) {
  1136. annotationRangeEnd = left;
  1137. annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] );
  1138. }
  1139. var annotationRangeWidth = annotationRangeEnd - annotationRangeStart;
  1140. box.rangeElement.style.left = annotationRangeStart + 'px';
  1141. box.rangeElement.style.width = annotationRangeWidth + 'px';
  1142. box.rangeElement.classList.remove('offscreen');
  1143. }
  1144. annotation.line.classList.remove('offscreen');
  1145. annotation.line.style.left = left + 'px';
  1146. } );
  1147. }, this );
  1148. };
  1149. this.graph.onUpdate( function() { self.update() } );
  1150. };
  1151. Rickshaw.namespace('Rickshaw.Graph.Axis.Time');
  1152. Rickshaw.Graph.Axis.Time = function(args) {
  1153. var self = this;
  1154. this.graph = args.graph;
  1155. this.elements = [];
  1156. this.ticksTreatment = args.ticksTreatment || 'plain';
  1157. this.fixedTimeUnit = args.timeUnit;
  1158. var time = args.timeFixture || new Rickshaw.Fixtures.Time();
  1159. this.appropriateTimeUnit = function() {
  1160. var unit;
  1161. var units = time.units;
  1162. var domain = this.graph.x.domain();
  1163. var rangeSeconds = domain[1] - domain[0];
  1164. units.forEach( function(u) {
  1165. if (Math.floor(rangeSeconds / u.seconds) >= 2) {
  1166. unit = unit || u;
  1167. }
  1168. } );
  1169. return (unit || time.units[time.units.length - 1]);
  1170. };
  1171. this.tickOffsets = function() {
  1172. var domain = this.graph.x.domain();
  1173. var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
  1174. var count = Math.ceil((domain[1] - domain[0]) / unit.seconds);
  1175. var runningTick = domain[0];
  1176. var offsets = [];
  1177. for (var i = 0; i < count; i++) {
  1178. var tickValue = time.ceil(runningTick, unit);
  1179. runningTick = tickValue + unit.seconds / 2;
  1180. offsets.push( { value: tickValue, unit: unit } );
  1181. }
  1182. return offsets;
  1183. };
  1184. this.render = function() {
  1185. this.elements.forEach( function(e) {
  1186. e.parentNode.removeChild(e);
  1187. } );
  1188. this.elements = [];
  1189. var offsets = this.tickOffsets();
  1190. offsets.forEach( function(o) {
  1191. if (self.graph.x(o.value) > self.graph.x.range()[1]) return;
  1192. var element = document.createElement('div');
  1193. element.style.left = self.graph.x(o.value) + 'px';
  1194. element.classList.add('x_tick');
  1195. element.classList.add(self.ticksTreatment);
  1196. var title = document.createElement('div');
  1197. title.classList.add('title');
  1198. title.innerHTML = o.unit.formatter(new Date(o.value * 1000));
  1199. element.appendChild(title);
  1200. self.graph.element.appendChild(element);
  1201. self.elements.push(element);
  1202. } );
  1203. };
  1204. this.graph.onUpdate( function() { self.render() } );
  1205. };
  1206. Rickshaw.namespace('Rickshaw.Graph.Axis.X');
  1207. Rickshaw.Graph.Axis.X = function(args) {
  1208. var self = this;
  1209. var berthRate = 0.10;
  1210. this.initialize = function(args) {
  1211. this.graph = args.graph;
  1212. this.orientation = args.orientation || 'top';
  1213. this.pixelsPerTick = args.pixelsPerTick || 75;
  1214. if (args.ticks) this.staticTicks = args.ticks;
  1215. if (args.tickValues) this.tickValues = args.tickValues;
  1216. this.tickSize = args.tickSize || 4;
  1217. this.ticksTreatment = args.ticksTreatment || 'plain';
  1218. if (args.element) {
  1219. this.element = args.element;
  1220. this._discoverSize(args.element, args);
  1221. this.vis = d3.select(args.element)
  1222. .append("svg:svg")
  1223. .attr('height', this.height)
  1224. .attr('width', this.width)
  1225. .attr('class', 'rickshaw_graph x_axis_d3');
  1226. this.element = this.vis[0][0];
  1227. this.element.style.position = 'relative';
  1228. this.setSize({ width: args.width, height: args.height });
  1229. } else {
  1230. this.vis = this.graph.vis;
  1231. }
  1232. this.graph.onUpdate( function() { self.render() } );
  1233. };
  1234. this.setSize = function(args) {
  1235. args = args || {};
  1236. if (!this.element) return;
  1237. this._discoverSize(this.element.parentNode, args);
  1238. this.vis
  1239. .attr('height', this.height)
  1240. .attr('width', this.width * (1 + berthRate));
  1241. var berth = Math.floor(this.width * berthRate / 2);
  1242. this.element.style.left = -1 * berth + 'px';
  1243. };
  1244. this.render = function() {
  1245. if (this._renderWidth !== undefined && this.graph.width !== this._renderWidth) this.setSize({ auto: true });
  1246. var axis = d3.svg.axis().scale(this.graph.x).orient(this.orientation);
  1247. axis.tickFormat( args.tickFormat || function(x) { return x } );
  1248. if (this.tickValues) axis.tickValues(this.tickValues);
  1249. this.ticks = this.staticTicks || Math.floor(this.graph.width / this.pixelsPerTick);
  1250. var berth = Math.floor(this.width * berthRate / 2) || 0;
  1251. var bar_offset = this.graph.renderer.name == "bar" && Math.ceil(this.graph.width * 0.95 / this.graph.series[0].data.length / 2) || 0;
  1252. var transform;
  1253. if (this.orientation == 'top') {
  1254. var yOffset = this.height || this.graph.height;
  1255. transform = 'translate(' + (berth + bar_offset) + ',' + yOffset + ')';
  1256. } else {
  1257. transform = 'translate(' + (berth + bar_offset) + ', 0)';
  1258. }
  1259. if (this.element) {
  1260. this.vis.selectAll('*').remove();
  1261. }
  1262. this.vis
  1263. .append("svg:g")
  1264. .attr("class", ["x_ticks_d3", this.ticksTreatment].join(" "))
  1265. .attr("transform", transform)
  1266. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));
  1267. var gridSize = (this.orientation == 'bottom' ? 1 : -1) * this.graph.height;
  1268. this.graph.vis
  1269. .append("svg:g")
  1270. .attr("class", "x_grid_d3")
  1271. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize))
  1272. .selectAll('text')
  1273. .each(function() { this.parentNode.setAttribute('data-x-value', this.textContent) });
  1274. this._renderHeight = this.graph.height;
  1275. };
  1276. this._discoverSize = function(element, args) {
  1277. if (typeof window !== 'undefined') {
  1278. var style = window.getComputedStyle(element, null);
  1279. var elementHeight = parseInt(style.getPropertyValue('height'), 10);
  1280. if (!args.auto) {
  1281. var elementWidth = parseInt(style.getPropertyValue('width'), 10);
  1282. }
  1283. }
  1284. this.width = (args.width || elementWidth || this.graph.width) * (1 + berthRate);
  1285. this.height = args.height || elementHeight || 40;
  1286. };
  1287. this.initialize(args);
  1288. };
  1289. Rickshaw.namespace('Rickshaw.Graph.Axis.Y');
  1290. Rickshaw.Graph.Axis.Y = Rickshaw.Class.create( {
  1291. initialize: function(args) {
  1292. this.graph = args.graph;
  1293. this.orientation = args.orientation || 'right';
  1294. this.pixelsPerTick = args.pixelsPerTick || 75;
  1295. if (args.ticks) this.staticTicks = args.ticks;
  1296. if (args.tickValues) this.tickValues = args.tickValues;
  1297. this.tickSize = args.tickSize || 4;
  1298. this.ticksTreatment = args.ticksTreatment || 'plain';
  1299. this.tickFormat = args.tickFormat || function(y) { return y };
  1300. this.berthRate = 0.10;
  1301. if (args.element) {
  1302. this.element = args.element;
  1303. this.vis = d3.select(args.element)
  1304. .append("svg:svg")
  1305. .attr('class', 'rickshaw_graph y_axis');
  1306. this.element = this.vis[0][0];
  1307. this.element.style.position = 'relative';
  1308. this.setSize({ width: args.width, height: args.height });
  1309. } else {
  1310. this.vis = this.graph.vis;
  1311. }
  1312. var self = this;
  1313. this.graph.onUpdate( function() { self.render() } );
  1314. },
  1315. setSize: function(args) {
  1316. args = args || {};
  1317. if (!this.element) return;
  1318. if (typeof window !== 'undefined') {
  1319. var style = window.getComputedStyle(this.element.parentNode, null);
  1320. var elementWidth = parseInt(style.getPropertyValue('width'), 10);
  1321. if (!args.auto) {
  1322. var elementHeight = parseInt(style.getPropertyValue('height'), 10);
  1323. }
  1324. }
  1325. this.width = args.width || elementWidth || this.graph.width * this.berthRate;
  1326. this.height = args.height || elementHeight || this.graph.height;
  1327. this.vis
  1328. .attr('width', this.width)
  1329. .attr('height', this.height * (1 + this.berthRate));
  1330. var berth = this.height * this.berthRate;
  1331. if (this.orientation == 'left') {
  1332. this.element.style.top = -1 * berth + 'px';
  1333. }
  1334. },
  1335. render: function() {
  1336. if (this._renderHeight !== undefined && this.graph.height !== this._renderHeight) this.setSize({ auto: true });
  1337. this.ticks = this.staticTicks || Math.floor(this.graph.height / this.pixelsPerTick);
  1338. var axis = this._drawAxis(this.graph.y);
  1339. this._drawGrid(axis);
  1340. this._renderHeight = this.graph.height;
  1341. },
  1342. _drawAxis: function(scale) {
  1343. var axis = d3.svg.axis().scale(scale).orient(this.orientation);
  1344. axis.tickFormat(this.tickFormat);
  1345. if (this.tickValues) axis.tickValues(this.tickValues);
  1346. if (this.orientation == 'left') {
  1347. var berth = this.height * this.berthRate;
  1348. var transform = 'translate(' + this.width + ', ' + berth + ')';
  1349. }
  1350. if (this.element) {
  1351. this.vis.selectAll('*').remove();
  1352. }
  1353. this.vis
  1354. .append("svg:g")
  1355. .attr("class", ["y_ticks", this.ticksTreatment].join(" "))
  1356. .attr("transform", transform)
  1357. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));
  1358. return axis;
  1359. },
  1360. _drawGrid: function(axis) {
  1361. var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width;
  1362. this.graph.vis
  1363. .append("svg:g")
  1364. .attr("class", "y_grid")
  1365. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize))
  1366. .selectAll('text')
  1367. .each(function() { this.parentNode.setAttribute('data-y-value', this.textContent) });
  1368. }
  1369. } );
  1370. Rickshaw.namespace('Rickshaw.Graph.Axis.Y.Scaled');
  1371. Rickshaw.Graph.Axis.Y.Scaled = Rickshaw.Class.create( Rickshaw.Graph.Axis.Y, {
  1372. initialize: function($super, args) {
  1373. if (typeof(args.scale) === 'undefined') {
  1374. throw new Error('Scaled requires scale');
  1375. }
  1376. this.scale = args.scale;
  1377. if (typeof(args.grid) === 'undefined') {
  1378. this.grid = true;
  1379. } else {
  1380. this.grid = args.grid;
  1381. }
  1382. $super(args);
  1383. },
  1384. _drawAxis: function($super, scale) {
  1385. // Adjust scale's domain to compensate for adjustments to the
  1386. // renderer's domain (e.g. padding).
  1387. var domain = this.scale.domain();
  1388. var renderDomain = this.graph.renderer.domain().y;
  1389. var extents = [
  1390. Math.min.apply(Math, domain),
  1391. Math.max.apply(Math, domain)];
  1392. // A mapping from the ideal render domain [0, 1] to the extent
  1393. // of the original scale's domain. This is used to calculate
  1394. // the extents of the adjusted domain.
  1395. var extentMap = d3.scale.linear().domain([0, 1]).range(extents);
  1396. var adjExtents = [
  1397. extentMap(renderDomain[0]),
  1398. extentMap(renderDomain[1])];
  1399. // A mapping from the original domain to the adjusted domain.
  1400. var adjustment = d3.scale.linear().domain(extents).range(adjExtents);
  1401. // Make a copy of the custom scale, apply the adjusted domain, and
  1402. // copy the range to match the graph's scale.
  1403. var adjustedScale = this.scale.copy()
  1404. .domain(domain.map(adjustment))
  1405. .range(scale.range());
  1406. return $super(adjustedScale);
  1407. },
  1408. _drawGrid: function($super, axis) {
  1409. if (this.grid) {
  1410. // only draw the axis if the grid option is true
  1411. $super(axis);
  1412. }
  1413. }
  1414. } );
  1415. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Highlight');
  1416. Rickshaw.Graph.Behavior.Series.Highlight = function(args) {
  1417. this.graph = args.graph;
  1418. this.legend = args.legend;
  1419. var self = this;
  1420. var colorSafe = {};
  1421. var activeLine = null;
  1422. var disabledColor = args.disabledColor || function(seriesColor) {
  1423. return d3.interpolateRgb(seriesColor, d3.rgb('#d8d8d8'))(0.8).toString();
  1424. };
  1425. this.addHighlightEvents = function (l) {
  1426. l.element.addEventListener( 'mouseover', function(e) {
  1427. if (activeLine) return;
  1428. else activeLine = l;
  1429. self.legend.lines.forEach( function(line) {
  1430. if (l === line) {
  1431. // if we're not in a stacked renderer bring active line to the top
  1432. if (self.graph.renderer.unstack && (line.series.renderer ? line.series.renderer.unstack : true)) {
  1433. var seriesIndex = self.graph.series.indexOf(line.series);
  1434. line.originalIndex = seriesIndex;
  1435. var series = self.graph.series.splice(seriesIndex, 1)[0];
  1436. self.graph.series.push(series);
  1437. }
  1438. return;
  1439. }
  1440. colorSafe[line.series.name] = colorSafe[line.series.name] || line.series.color;
  1441. line.series.color = disabledColor(line.series.color);
  1442. } );
  1443. self.graph.update();
  1444. }, false );
  1445. l.element.addEventListener( 'mouseout', function(e) {
  1446. if (!activeLine) return;
  1447. else activeLine = null;
  1448. self.legend.lines.forEach( function(line) {
  1449. // return reordered series to its original place
  1450. if (l === line && line.hasOwnProperty('originalIndex')) {
  1451. var series = self.graph.series.pop();
  1452. self.graph.series.splice(line.originalIndex, 0, series);
  1453. delete line.originalIndex;
  1454. }
  1455. if (colorSafe[line.series.name]) {
  1456. line.series.color = colorSafe[line.series.name];
  1457. }
  1458. } );
  1459. self.graph.update();
  1460. }, false );
  1461. };
  1462. if (this.legend) {
  1463. this.legend.lines.forEach( function(l) {
  1464. self.addHighlightEvents(l);
  1465. } );
  1466. }
  1467. };
  1468. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Order');
  1469. Rickshaw.Graph.Behavior.Series.Order = function(args) {
  1470. this.graph = args.graph;
  1471. this.legend = args.legend;
  1472. var self = this;
  1473. if (typeof window.jQuery == 'undefined') {
  1474. throw "couldn't find jQuery at window.jQuery";
  1475. }
  1476. if (typeof window.jQuery.ui == 'undefined') {
  1477. throw "couldn't find jQuery UI at window.jQuery.ui";
  1478. }
  1479. jQuery(function() {
  1480. jQuery(self.legend.list).sortable( {
  1481. containment: 'parent',
  1482. tolerance: 'pointer',
  1483. update: function( event, ui ) {
  1484. var series = [];
  1485. jQuery(self.legend.list).find('li').each( function(index, item) {
  1486. if (!item.series) return;
  1487. series.push(item.series);
  1488. } );
  1489. for (var i = self.graph.series.length - 1; i >= 0; i--) {
  1490. self.graph.series[i] = series.shift();
  1491. }
  1492. self.graph.update();
  1493. }
  1494. } );
  1495. jQuery(self.legend.list).disableSelection();
  1496. });
  1497. //hack to make jquery-ui sortable behave
  1498. this.graph.onUpdate( function() {
  1499. var h = window.getComputedStyle(self.legend.element).height;
  1500. self.legend.element.style.height = h;
  1501. } );
  1502. };
  1503. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle');
  1504. Rickshaw.Graph.Behavior.Series.Toggle = function(args) {
  1505. this.graph = args.graph;
  1506. this.legend = args.legend;
  1507. var self = this;
  1508. this.addAnchor = function(line) {
  1509. var anchor = document.createElement('a');
  1510. anchor.innerHTML = '&#10004;';
  1511. anchor.classList.add('action');
  1512. line.element.insertBefore(anchor, line.element.firstChild);
  1513. anchor.onclick = function(e) {
  1514. if (line.series.disabled) {
  1515. line.series.enable();
  1516. line.element.classList.remove('disabled');
  1517. } else {
  1518. if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
  1519. line.series.disable();
  1520. line.element.classList.add('disabled');
  1521. }
  1522. self.graph.update();
  1523. }.bind(this);
  1524. var label = line.element.getElementsByTagName('span')[0];
  1525. label.onclick = function(e){
  1526. var disableAllOtherLines = line.series.disabled;
  1527. if ( ! disableAllOtherLines ) {
  1528. for ( var i = 0; i < self.legend.lines.length; i++ ) {
  1529. var l = self.legend.lines[i];
  1530. if ( line.series === l.series ) {
  1531. // noop
  1532. } else if ( l.series.disabled ) {
  1533. // noop
  1534. } else {
  1535. disableAllOtherLines = true;
  1536. break;
  1537. }
  1538. }
  1539. }
  1540. // show all or none
  1541. if ( disableAllOtherLines ) {
  1542. // these must happen first or else we try ( and probably fail ) to make a no line graph
  1543. line.series.enable();
  1544. line.element.classList.remove('disabled');
  1545. self.legend.lines.forEach(function(l){
  1546. if ( line.series === l.series ) {
  1547. // noop
  1548. } else {
  1549. l.series.disable();
  1550. l.element.classList.add('disabled');
  1551. }
  1552. });
  1553. } else {
  1554. self.legend.lines.forEach(function(l){
  1555. l.series.enable();
  1556. l.element.classList.remove('disabled');
  1557. });
  1558. }
  1559. self.graph.update();
  1560. };
  1561. };
  1562. if (this.legend) {
  1563. if (typeof jQuery != 'undefined' && jQuery(this.legend.list).sortable) {
  1564. jQuery(this.legend.list).sortable( {
  1565. start: function(event, ui) {
  1566. ui.item.bind('no.onclick',
  1567. function(event) {
  1568. event.preventDefault();
  1569. }
  1570. );
  1571. },
  1572. stop: function(event, ui) {
  1573. setTimeout(function(){
  1574. ui.item.unbind('no.onclick');
  1575. }, 250);
  1576. }
  1577. });
  1578. }
  1579. this.legend.lines.forEach( function(l) {
  1580. self.addAnchor(l);
  1581. } );
  1582. }
  1583. this._addBehavior = function() {
  1584. this.graph.series.forEach( function(s) {
  1585. s.disable = function() {
  1586. if (self.graph.series.length <= 1) {
  1587. throw('only one series left');
  1588. }
  1589. s.disabled = true;
  1590. };
  1591. s.enable = function() {
  1592. s.disabled = false;
  1593. };
  1594. } );
  1595. };
  1596. this._addBehavior();
  1597. this.updateBehaviour = function () { this._addBehavior() };
  1598. };
  1599. Rickshaw.namespace('Rickshaw.Graph.DragZoom');
  1600. Rickshaw.Graph.DragZoom = Rickshaw.Class.create({
  1601. initialize: function(args) {
  1602. if (!args || !args.graph) {
  1603. throw new Error("Rickshaw.Graph.DragZoom needs a reference to a graph");
  1604. }
  1605. var defaults = {
  1606. opacity: 0.5,
  1607. fill: 'steelblue',
  1608. minimumTimeSelection: 60,
  1609. callback: function() {}
  1610. };
  1611. this.graph = args.graph;
  1612. this.svg = d3.select(this.graph.element).select("svg");
  1613. this.svgWidth = parseInt(this.svg.attr("width"), 10);
  1614. this.opacity = args.opacity || defaults.opacity;
  1615. this.fill = args.fill || defaults.fill;
  1616. this.minimumTimeSelection = args.minimumTimeSelection || defaults.minimumTimeSelection;
  1617. this.callback = args.callback || defaults.callback;
  1618. this.registerMouseEvents();
  1619. },
  1620. registerMouseEvents: function() {
  1621. var self = this;
  1622. var ESCAPE_KEYCODE = 27;
  1623. var rectangle;
  1624. var drag = {
  1625. startDt: null,
  1626. stopDt: null,
  1627. startPX: null,
  1628. stopPX: null
  1629. };
  1630. this.svg.on("mousedown", onMousedown);
  1631. function onMouseup(datum, index) {
  1632. drag.stopDt = pointAsDate(d3.event);
  1633. var windowAfterDrag = [
  1634. drag.startDt,
  1635. drag.stopDt
  1636. ].sort(compareNumbers);
  1637. self.graph.window.xMin = windowAfterDrag[0];
  1638. self.graph.window.xMax = windowAfterDrag[1];
  1639. var endTime = self.graph.window.xMax;
  1640. var range = self.graph.window.xMax - self.graph.window.xMin;
  1641. reset(this);
  1642. if (range < self.minimumTimeSelection || isNaN(range)) {
  1643. return;
  1644. }
  1645. self.graph.update();
  1646. self.callback({range: range, endTime: endTime});
  1647. }
  1648. function onMousemove() {
  1649. var offset = drag.stopPX = (d3.event.offsetX || d3.event.layerX);
  1650. if (offset > (self.svgWidth - 1) || offset < 1) {
  1651. return;
  1652. }
  1653. var limits = [drag.startPX, offset].sort(compareNumbers);
  1654. var selectionWidth = limits[1]-limits[0];
  1655. if (isNaN(selectionWidth)) {
  1656. return reset(this);
  1657. }
  1658. rectangle.attr("fill", self.fill)
  1659. .attr("x", limits[0])
  1660. .attr("width", selectionWidth);
  1661. }
  1662. function onMousedown() {
  1663. var el = d3.select(this);
  1664. rectangle = el.append("rect")
  1665. .style("opacity", self.opacity)
  1666. .attr("y", 0)
  1667. .attr("height", "100%");
  1668. if(d3.event.preventDefault) {
  1669. d3.event.preventDefault();
  1670. } else {
  1671. d3.event.returnValue = false;
  1672. }
  1673. drag.target = d3.event.target;
  1674. drag.startDt = pointAsDate(d3.event);
  1675. drag.startPX = d3.event.offsetX || d3.event.layerX;
  1676. el.on("mousemove", onMousemove);
  1677. d3.select(document).on("mouseup", onMouseup);
  1678. d3.select(document).on("keyup", function() {
  1679. if (d3.event.keyCode === ESCAPE_KEYCODE) {
  1680. reset(this);
  1681. }
  1682. });
  1683. }
  1684. function reset(el) {
  1685. var s = d3.select(el);
  1686. s.on("mousemove", null);
  1687. d3.select(document).on("mouseup", null);
  1688. drag = {};
  1689. rectangle.remove();
  1690. }
  1691. function compareNumbers(a, b) {
  1692. return a - b;
  1693. }
  1694. function pointAsDate(e) {
  1695. return Math.floor(self.graph.x.invert(e.offsetX || e.layerX));
  1696. }
  1697. }
  1698. });
  1699. Rickshaw.namespace('Rickshaw.Graph.HoverDetail');
  1700. Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({
  1701. initialize: function(args) {
  1702. var graph = this.graph = args.graph;
  1703. this.xFormatter = args.xFormatter || function(x) {
  1704. return new Date( x * 1000 ).toUTCString();
  1705. };
  1706. this.yFormatter = args.yFormatter || function(y) {
  1707. return y === null ? y : y.toFixed(2);
  1708. };
  1709. var element = this.element = document.createElement('div');
  1710. element.className = 'detail inactive';
  1711. this.visible = true;
  1712. graph.element.appendChild(element);
  1713. this.lastEvent = null;
  1714. this._addListeners();
  1715. this.onShow = args.onShow;
  1716. this.onHide = args.onHide;
  1717. this.onRender = args.onRender;
  1718. this.formatter = args.formatter || this.formatter;
  1719. },
  1720. formatter: function(series, x, y, formattedX, formattedY, d) {
  1721. return series.name + ':&nbsp;' + formattedY;
  1722. },
  1723. update: function(e) {
  1724. e = e || this.lastEvent;
  1725. if (!e) return;
  1726. this.lastEvent = e;
  1727. if (!e.target.nodeName.match(/^(path|svg|rect|circle)$/)) return;
  1728. var graph = this.graph;
  1729. var eventX = e.layerX || e.offsetX;
  1730. var eventY = e.layerY || e.offsetY;
  1731. var j = 0;
  1732. var points = [];
  1733. var nearestPoint;
  1734. this.graph.series.active().forEach( function(series) {
  1735. var data = this.graph.stackedData[j++];
  1736. if (!data.length)
  1737. return;
  1738. var domainX = graph.x.invert(eventX);
  1739. var domainIndexScale = d3.scale.linear()
  1740. .domain([data[0].x, data.slice(-1)[0].x])
  1741. .range([0, data.length - 1]);
  1742. var approximateIndex = Math.round(domainIndexScale(domainX));
  1743. if (approximateIndex == data.length - 1) approximateIndex--;
  1744. var dataIndex = Math.min(approximateIndex || 0, data.length - 1);
  1745. for (var i = approximateIndex; i < data.length - 1;) {
  1746. if (!data[i] || !data[i + 1]) break;
  1747. if (data[i].x <= domainX && data[i + 1].x > domainX) {
  1748. dataIndex = Math.abs(domainX - data[i].x) < Math.abs(domainX - data[i + 1].x) ? i : i + 1;
  1749. break;
  1750. }
  1751. if (data[i + 1].x <= domainX) { i++ } else { i-- }
  1752. }
  1753. if (dataIndex < 0) dataIndex = 0;
  1754. var value = data[dataIndex];
  1755. var distance = Math.sqrt(
  1756. Math.pow(Math.abs(graph.x(value.x) - eventX), 2) +
  1757. Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2)
  1758. );
  1759. var xFormatter = series.xFormatter || this.xFormatter;
  1760. var yFormatter = series.yFormatter || this.yFormatter;
  1761. var point = {
  1762. formattedXValue: xFormatter(value.x),
  1763. formattedYValue: yFormatter(series.scale ? series.scale.invert(value.y) : value.y),
  1764. series: series,
  1765. value: value,
  1766. distance: distance,
  1767. order: j,
  1768. name: series.name
  1769. };
  1770. if (!nearestPoint || distance < nearestPoint.distance) {
  1771. nearestPoint = point;
  1772. }
  1773. points.push(point);
  1774. }, this );
  1775. if (!nearestPoint)
  1776. return;
  1777. nearestPoint.active = true;
  1778. var domainX = nearestPoint.value.x;
  1779. var formattedXValue = nearestPoint.formattedXValue;
  1780. this.element.innerHTML = '';
  1781. this.element.style.left = graph.x(domainX) + 'px';
  1782. this.visible && this.render( {
  1783. points: points,
  1784. detail: points, // for backwards compatibility
  1785. mouseX: eventX,
  1786. mouseY: eventY,
  1787. formattedXValue: formattedXValue,
  1788. domainX: domainX
  1789. } );
  1790. },
  1791. hide: function() {
  1792. this.visible = false;
  1793. this.element.classList.add('inactive');
  1794. if (typeof this.onHide == 'function') {
  1795. this.onHide();
  1796. }
  1797. },
  1798. show: function() {
  1799. this.visible = true;
  1800. this.element.classList.remove('inactive');
  1801. if (typeof this.onShow == 'function') {
  1802. this.onShow();
  1803. }
  1804. },
  1805. render: function(args) {
  1806. var graph = this.graph;
  1807. var points = args.points;
  1808. var point = points.filter( function(p) { return p.active } ).shift();
  1809. if (point.value.y === null) return;
  1810. var formattedXValue = point.formattedXValue;
  1811. var formattedYValue = point.formattedYValue;
  1812. this.element.innerHTML = '';
  1813. this.element.style.left = graph.x(point.value.x) + 'px';
  1814. var xLabel = document.createElement('div');
  1815. xLabel.className = 'x_label';
  1816. xLabel.innerHTML = formattedXValue;
  1817. this.element.appendChild(xLabel);
  1818. var item = document.createElement('div');
  1819. item.className = 'item';
  1820. // invert the scale if this series displays using a scale
  1821. var series = point.series;
  1822. var actualY = series.scale ? series.scale.invert(point.value.y) : point.value.y;
  1823. item.innerHTML = this.formatter(series, point.value.x, actualY, formattedXValue, formattedYValue, point);
  1824. item.style.top = this.graph.y(point.value.y0 + point.value.y) + 'px';
  1825. this.element.appendChild(item);
  1826. var dot = document.createElement('div');
  1827. dot.className = 'dot';
  1828. dot.style.top = item.style.top;
  1829. dot.style.borderColor = series.color;
  1830. this.element.appendChild(dot);
  1831. if (point.active) {
  1832. item.classList.add('active');
  1833. dot.classList.add('active');
  1834. }
  1835. // Assume left alignment until the element has been displayed and
  1836. // bounding box calculations are possible.
  1837. var alignables = [xLabel, item];
  1838. alignables.forEach(function(el) {
  1839. el.classList.add('left');
  1840. });
  1841. this.show();
  1842. // If left-alignment results in any error, try right-alignment.
  1843. var leftAlignError = this._calcLayoutError(alignables);
  1844. if (leftAlignError > 0) {
  1845. alignables.forEach(function(el) {
  1846. el.classList.remove('left');
  1847. el.classList.add('right');
  1848. });
  1849. // If right-alignment is worse than left alignment, switch back.
  1850. var rightAlignError = this._calcLayoutError(alignables);
  1851. if (rightAlignError > leftAlignError) {
  1852. alignables.forEach(function(el) {
  1853. el.classList.remove('right');
  1854. el.classList.add('left');
  1855. });
  1856. }
  1857. }
  1858. if (typeof this.onRender == 'function') {
  1859. this.onRender(args);
  1860. }
  1861. },
  1862. _calcLayoutError: function(alignables) {
  1863. // Layout error is calculated as the number of linear pixels by which
  1864. // an alignable extends past the left or right edge of the parent.
  1865. var parentRect = this.element.parentNode.getBoundingClientRect();
  1866. var error = 0;
  1867. var alignRight = alignables.forEach(function(el) {
  1868. var rect = el.getBoundingClientRect();
  1869. if (!rect.width) {
  1870. return;
  1871. }
  1872. if (rect.right > parentRect.right) {
  1873. error += rect.right - parentRect.right;
  1874. }
  1875. if (rect.left < parentRect.left) {
  1876. error += parentRect.left - rect.left;
  1877. }
  1878. });
  1879. return error;
  1880. },
  1881. _addListeners: function() {
  1882. // Keep reference for later removal.
  1883. this.mousemoveListener = function(e) {
  1884. this.visible = true;
  1885. this.update(e);
  1886. }.bind(this);
  1887. // Add listener.
  1888. this.graph.element.addEventListener(
  1889. 'mousemove',
  1890. this.mousemoveListener,
  1891. false
  1892. );
  1893. this.graph.onUpdate( function() { this.update() }.bind(this) );
  1894. // Keep reference for later removal.
  1895. this.mouseoutListener = function(e) {
  1896. if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
  1897. this.hide();
  1898. }
  1899. }.bind(this);
  1900. // Add listener.
  1901. this.graph.element.addEventListener(
  1902. 'mouseout',
  1903. this.mouseoutListener,
  1904. false
  1905. );
  1906. },
  1907. _removeListeners: function() {
  1908. if (this.mousemoveListener) {
  1909. this.graph.element.removeEventListener('mousemove', this.mousemoveListener, false);
  1910. }
  1911. if (this.mouseoutListener) {
  1912. this.graph.element.removeEventListener('mouseout', this.mouseoutListener, false);
  1913. }
  1914. }
  1915. });
  1916. Rickshaw.namespace('Rickshaw.Graph.JSONP');
  1917. Rickshaw.Graph.JSONP = Rickshaw.Class.create( Rickshaw.Graph.Ajax, {
  1918. request: function() {
  1919. jQuery.ajax( {
  1920. url: this.dataURL,
  1921. dataType: 'jsonp',
  1922. success: this.success.bind(this),
  1923. error: this.error.bind(this)
  1924. } );
  1925. }
  1926. } );
  1927. Rickshaw.namespace('Rickshaw.Graph.Legend');
  1928. Rickshaw.Graph.Legend = Rickshaw.Class.create( {
  1929. className: 'rickshaw_legend',
  1930. initialize: function(args) {
  1931. this.element = args.element;
  1932. this.graph = args.graph;
  1933. this.naturalOrder = args.naturalOrder;
  1934. this.element.classList.add(this.className);
  1935. this.list = document.createElement('ul');
  1936. this.element.appendChild(this.list);
  1937. this.render();
  1938. // we could bind this.render.bind(this) here
  1939. // but triggering the re-render would lose the added
  1940. // behavior of the series toggle
  1941. this.graph.onUpdate( function() {} );
  1942. },
  1943. render: function() {
  1944. var self = this;
  1945. while ( this.list.firstChild ) {
  1946. this.list.removeChild( this.list.firstChild );
  1947. }
  1948. this.lines = [];
  1949. var series = this.graph.series
  1950. .map( function(s) { return s } );
  1951. if (!this.naturalOrder) {
  1952. series = series.reverse();
  1953. }
  1954. series.forEach( function(s) {
  1955. self.addLine(s);
  1956. } );
  1957. },
  1958. addLine: function (series) {
  1959. var line = document.createElement('li');
  1960. line.className = 'line';
  1961. if (series.disabled) {
  1962. line.className += ' disabled';
  1963. }
  1964. if (series.className) {
  1965. d3.select(line).classed(series.className, true);
  1966. }
  1967. var swatch = document.createElement('div');
  1968. swatch.className = 'swatch';
  1969. swatch.style.backgroundColor = series.color;
  1970. line.appendChild(swatch);
  1971. var label = document.createElement('span');
  1972. label.className = 'label';
  1973. label.innerHTML = series.name;
  1974. line.appendChild(label);
  1975. this.list.appendChild(line);
  1976. line.series = series;
  1977. if (series.noLegend) {
  1978. line.style.display = 'none';
  1979. }
  1980. var _line = { element: line, series: series };
  1981. if (this.shelving) {
  1982. this.shelving.addAnchor(_line);
  1983. this.shelving.updateBehaviour();
  1984. }
  1985. if (this.highlighter) {
  1986. this.highlighter.addHighlightEvents(_line);
  1987. }
  1988. this.lines.push(_line);
  1989. return line;
  1990. }
  1991. } );
  1992. Rickshaw.namespace('Rickshaw.Graph.RangeSlider');
  1993. Rickshaw.Graph.RangeSlider = Rickshaw.Class.create({
  1994. initialize: function(args) {
  1995. var $ = jQuery;
  1996. var self = this;
  1997. var element = this.element = args.element;
  1998. var graphs = this.graphs = args.graphs;
  1999. if (!graphs) {
  2000. graphs = this.graph = args.graph;
  2001. }
  2002. if (graphs.constructor !== Array) {
  2003. graphs = [graphs];
  2004. }
  2005. this.graph = graphs[0];
  2006. this.slideCallbacks = [];
  2007. this.build();
  2008. for (var i = 0; i < graphs.length; i++) {
  2009. graphs[i].onUpdate(function() {
  2010. self.update();
  2011. }.bind(self));
  2012. (function(idx){
  2013. graphs[idx].onConfigure(function() {
  2014. $(this.element)[0].style.width = graphs[idx].width + 'px';
  2015. }.bind(self));
  2016. })(i);
  2017. }
  2018. },
  2019. build: function() {
  2020. var domain;
  2021. var element = this.element;
  2022. var $ = jQuery;
  2023. var self = this;
  2024. var graphs = this.graphs || this.graph;
  2025. if (graphs.constructor !== Array) {
  2026. graphs = [graphs];
  2027. }
  2028. // base the slider's min/max on the first graph
  2029. this.graph = graphs[0];
  2030. domain = graphs[0].dataDomain();
  2031. $(function() {
  2032. $(element).slider({
  2033. range: true,
  2034. min: domain[0],
  2035. max: domain[1],
  2036. values: [
  2037. domain[0],
  2038. domain[1]
  2039. ],
  2040. start: function(event, ui) {
  2041. self.slideStarted({ event: event, ui: ui });
  2042. },
  2043. stop: function(event, ui) {
  2044. self.slideFinished({ event: event, ui: ui });
  2045. },
  2046. slide: function(event, ui) {
  2047. if (!self.slideShouldUpdate(event, ui))
  2048. return;
  2049. if (ui.values[1] <= ui.values[0]) return;
  2050. for (var i = 0; i < graphs.length; i++) {
  2051. self.processSlideChange({
  2052. event: event,
  2053. ui: ui,
  2054. graph: graphs[i]
  2055. });
  2056. }
  2057. }
  2058. } );
  2059. } );
  2060. graphs[0].onConfigure(function() {
  2061. $(this.element)[0].style.width = graphs[0].width + 'px';
  2062. }.bind(this));
  2063. },
  2064. update: function() {
  2065. var element = this.element;
  2066. var graph = this.graph;
  2067. var $ = jQuery;
  2068. var values = $(element).slider('option', 'values');
  2069. var domain = graph.dataDomain();
  2070. $(element).slider('option', 'min', domain[0]);
  2071. $(element).slider('option', 'max', domain[1]);
  2072. if (graph.window.xMin == null) {
  2073. values[0] = domain[0];
  2074. }
  2075. if (graph.window.xMax == null) {
  2076. values[1] = domain[1];
  2077. }
  2078. $(element).slider('option', 'values', values);
  2079. },
  2080. onSlide: function(callback) {
  2081. this.slideCallbacks.push(callback);
  2082. },
  2083. processSlideChange: function(args) {
  2084. var event = args.event;
  2085. var ui = args.ui;
  2086. var graph = args.graph;
  2087. graph.window.xMin = ui.values[0];
  2088. graph.window.xMax = ui.values[1];
  2089. graph.update();
  2090. var domain = graph.dataDomain();
  2091. // if we're at an extreme, stick there
  2092. if (domain[0] == ui.values[0]) {
  2093. graph.window.xMin = undefined;
  2094. }
  2095. if (domain[1] == ui.values[1]) {
  2096. graph.window.xMax = undefined;
  2097. }
  2098. this.slideCallbacks.forEach(function(callback) {
  2099. callback(graph, graph.window.xMin, graph.window.xMax);
  2100. });
  2101. },
  2102. // allows the slide updates to bail out if sliding is not permitted
  2103. slideShouldUpdate: function() {
  2104. return true;
  2105. },
  2106. slideStarted: function() {
  2107. return;
  2108. },
  2109. slideFinished: function() {
  2110. return;
  2111. }
  2112. });
  2113. Rickshaw.namespace('Rickshaw.Graph.RangeSlider.Preview');
  2114. Rickshaw.Graph.RangeSlider.Preview = Rickshaw.Class.create({
  2115. initialize: function(args) {
  2116. if (!args.element) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an element";
  2117. if (!args.graph && !args.graphs) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an graph or an array of graphs";
  2118. this.element = args.element;
  2119. this.element.style.position = 'relative';
  2120. this.graphs = args.graph ? [ args.graph ] : args.graphs;
  2121. this.defaults = {
  2122. height: 75,
  2123. width: 400,
  2124. gripperColor: undefined,
  2125. frameTopThickness: 3,
  2126. frameHandleThickness: 10,
  2127. frameColor: "#d4d4d4",
  2128. frameOpacity: 1,
  2129. minimumFrameWidth: 0,
  2130. heightRatio: 0.2
  2131. };
  2132. this.heightRatio = args.heightRatio || this.defaults.heightRatio;
  2133. this.defaults.gripperColor = d3.rgb(this.defaults.frameColor).darker().toString();
  2134. this.configureCallbacks = [];
  2135. this.slideCallbacks = [];
  2136. this.previews = [];
  2137. if (!args.width) this.widthFromGraph = true;
  2138. if (!args.height) this.heightFromGraph = true;
  2139. if (this.widthFromGraph || this.heightFromGraph) {
  2140. this.graphs[0].onConfigure(function () {
  2141. this.configure(args); this.render();
  2142. }.bind(this));
  2143. }
  2144. args.width = args.width || this.graphs[0].width || this.defaults.width;
  2145. args.height = args.height || this.graphs[0].height * this.heightRatio || this.defaults.height;
  2146. this.configure(args);
  2147. this.render();
  2148. },
  2149. onSlide: function(callback) {
  2150. this.slideCallbacks.push(callback);
  2151. },
  2152. onConfigure: function(callback) {
  2153. this.configureCallbacks.push(callback);
  2154. },
  2155. configure: function(args) {
  2156. this.config = this.config || {};
  2157. this.configureCallbacks.forEach(function(callback) {
  2158. callback(args);
  2159. });
  2160. Rickshaw.keys(this.defaults).forEach(function(k) {
  2161. this.config[k] = k in args ? args[k]
  2162. : k in this.config ? this.config[k]
  2163. : this.defaults[k];
  2164. }, this);
  2165. if ('width' in args || 'height' in args) {
  2166. if (this.widthFromGraph) {
  2167. this.config.width = this.graphs[0].width;
  2168. }
  2169. if (this.heightFromGraph) {
  2170. this.config.height = this.graphs[0].height * this.heightRatio;
  2171. this.previewHeight = this.config.height;
  2172. }
  2173. this.previews.forEach(function(preview) {
  2174. var height = this.previewHeight / this.graphs.length - this.config.frameTopThickness * 2;
  2175. var width = this.config.width - this.config.frameHandleThickness * 2;
  2176. preview.setSize({ width: width, height: height });
  2177. if (this.svg) {
  2178. var svgHeight = height + this.config.frameHandleThickness * 2;
  2179. var svgWidth = width + this.config.frameHandleThickness * 2;
  2180. this.svg.style("width", svgWidth + "px");
  2181. this.svg.style("height", svgHeight + "px");
  2182. }
  2183. }, this);
  2184. }
  2185. },
  2186. render: function() {
  2187. var self = this;
  2188. this.svg = d3.select(this.element)
  2189. .selectAll("svg.rickshaw_range_slider_preview")
  2190. .data([null]);
  2191. this.previewHeight = this.config.height - (this.config.frameTopThickness * 2);
  2192. this.previewWidth = this.config.width - (this.config.frameHandleThickness * 2);
  2193. this.currentFrame = [0, this.previewWidth];
  2194. var buildGraph = function(parent, index) {
  2195. var graphArgs = Rickshaw.extend({}, parent.config);
  2196. var height = self.previewHeight / self.graphs.length;
  2197. var renderer = parent.renderer.name;
  2198. Rickshaw.extend(graphArgs, {
  2199. element: this.appendChild(document.createElement("div")),
  2200. height: height,
  2201. width: self.previewWidth,
  2202. series: parent.series,
  2203. renderer: renderer
  2204. });
  2205. var graph = new Rickshaw.Graph(graphArgs);
  2206. self.previews.push(graph);
  2207. parent.onUpdate(function() { graph.render(); self.render() });
  2208. parent.onConfigure(function(args) {
  2209. // don't propagate height
  2210. delete args.height;
  2211. args.width = args.width - self.config.frameHandleThickness * 2;
  2212. graph.configure(args);
  2213. graph.render();
  2214. });
  2215. graph.render();
  2216. };
  2217. var graphContainer = d3.select(this.element)
  2218. .selectAll("div.rickshaw_range_slider_preview_container")
  2219. .data(this.graphs);
  2220. var translateCommand = "translate(" +
  2221. this.config.frameHandleThickness + "px, " +
  2222. this.config.frameTopThickness + "px)";
  2223. graphContainer.enter()
  2224. .append("div")
  2225. .classed("rickshaw_range_slider_preview_container", true)
  2226. .style("-webkit-transform", translateCommand)
  2227. .style("-moz-transform", translateCommand)
  2228. .style("-ms-transform", translateCommand)
  2229. .style("transform", translateCommand)
  2230. .each(buildGraph);
  2231. graphContainer.exit()
  2232. .remove();
  2233. // Use the first graph as the "master" for the frame state
  2234. var masterGraph = this.graphs[0];
  2235. var domainScale = d3.scale.linear()
  2236. .domain([0, this.previewWidth])
  2237. .range(masterGraph.dataDomain());
  2238. var currentWindow = [masterGraph.window.xMin, masterGraph.window.xMax];
  2239. this.currentFrame[0] = currentWindow[0] === undefined ?
  2240. 0 : Math.round(domainScale.invert(currentWindow[0]));
  2241. if (this.currentFrame[0] < 0) this.currentFrame[0] = 0;
  2242. this.currentFrame[1] = currentWindow[1] === undefined ?
  2243. this.previewWidth : domainScale.invert(currentWindow[1]);
  2244. if (this.currentFrame[1] - this.currentFrame[0] < self.config.minimumFrameWidth) {
  2245. this.currentFrame[1] = (this.currentFrame[0] || 0) + self.config.minimumFrameWidth;
  2246. }
  2247. this.svg.enter()
  2248. .append("svg")
  2249. .classed("rickshaw_range_slider_preview", true)
  2250. .style("height", this.config.height + "px")
  2251. .style("width", this.config.width + "px")
  2252. .style("position", "absolute")
  2253. .style("top", 0);
  2254. this._renderDimming();
  2255. this._renderFrame();
  2256. this._renderGrippers();
  2257. this._renderHandles();
  2258. this._renderMiddle();
  2259. this._registerMouseEvents();
  2260. },
  2261. _renderDimming: function() {
  2262. var element = this.svg
  2263. .selectAll("path.dimming")
  2264. .data([null]);
  2265. element.enter()
  2266. .append("path")
  2267. .attr("fill", "white")
  2268. .attr("fill-opacity", "0.7")
  2269. .attr("fill-rule", "evenodd")
  2270. .classed("dimming", true);
  2271. var path = "";
  2272. path += " M " + this.config.frameHandleThickness + " " + this.config.frameTopThickness;
  2273. path += " h " + this.previewWidth;
  2274. path += " v " + this.previewHeight;
  2275. path += " h " + -this.previewWidth;
  2276. path += " z ";
  2277. path += " M " + Math.max(this.currentFrame[0], this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
  2278. path += " H " + Math.min(this.currentFrame[1] + this.config.frameHandleThickness * 2, this.previewWidth + this.config.frameHandleThickness);
  2279. path += " v " + this.previewHeight;
  2280. path += " H " + Math.max(this.currentFrame[0], this.config.frameHandleThickness);
  2281. path += " z";
  2282. element.attr("d", path);
  2283. },
  2284. _renderFrame: function() {
  2285. var element = this.svg
  2286. .selectAll("path.frame")
  2287. .data([null]);
  2288. element.enter()
  2289. .append("path")
  2290. .attr("stroke", "white")
  2291. .attr("stroke-width", "1px")
  2292. .attr("stroke-linejoin", "round")
  2293. .attr("fill", this.config.frameColor)
  2294. .attr("fill-opacity", this.config.frameOpacity)
  2295. .attr("fill-rule", "evenodd")
  2296. .classed("frame", true);
  2297. var path = "";
  2298. path += " M " + this.currentFrame[0] + " 0";
  2299. path += " H " + (this.currentFrame[1] + (this.config.frameHandleThickness * 2));
  2300. path += " V " + this.config.height;
  2301. path += " H " + (this.currentFrame[0]);
  2302. path += " z";
  2303. path += " M " + (this.currentFrame[0] + this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
  2304. path += " H " + (this.currentFrame[1] + this.config.frameHandleThickness);
  2305. path += " v " + this.previewHeight;
  2306. path += " H " + (this.currentFrame[0] + this.config.frameHandleThickness);
  2307. path += " z";
  2308. element.attr("d", path);
  2309. },
  2310. _renderGrippers: function() {
  2311. var gripper = this.svg.selectAll("path.gripper")
  2312. .data([null]);
  2313. gripper.enter()
  2314. .append("path")
  2315. .attr("stroke", this.config.gripperColor)
  2316. .classed("gripper", true);
  2317. var path = "";
  2318. [0.4, 0.6].forEach(function(spacing) {
  2319. path += " M " + Math.round((this.currentFrame[0] + (this.config.frameHandleThickness * spacing))) + " " + Math.round(this.config.height * 0.3);
  2320. path += " V " + Math.round(this.config.height * 0.7);
  2321. path += " M " + Math.round((this.currentFrame[1] + (this.config.frameHandleThickness * (1 + spacing)))) + " " + Math.round(this.config.height * 0.3);
  2322. path += " V " + Math.round(this.config.height * 0.7);
  2323. }.bind(this));
  2324. gripper.attr("d", path);
  2325. },
  2326. _renderHandles: function() {
  2327. var leftHandle = this.svg.selectAll("rect.left_handle")
  2328. .data([null]);
  2329. leftHandle.enter()
  2330. .append("rect")
  2331. .attr('width', this.config.frameHandleThickness)
  2332. .style("cursor", "ew-resize")
  2333. .style("fill-opacity", "0")
  2334. .classed("left_handle", true);
  2335. leftHandle
  2336. .attr('x', this.currentFrame[0])
  2337. .attr('height', this.config.height);
  2338. var rightHandle = this.svg.selectAll("rect.right_handle")
  2339. .data([null]);
  2340. rightHandle.enter()
  2341. .append("rect")
  2342. .attr('width', this.config.frameHandleThickness)
  2343. .style("cursor", "ew-resize")
  2344. .style("fill-opacity", "0")
  2345. .classed("right_handle", true);
  2346. rightHandle
  2347. .attr('x', this.currentFrame[1] + this.config.frameHandleThickness)
  2348. .attr('height', this.config.height);
  2349. },
  2350. _renderMiddle: function() {
  2351. var middleHandle = this.svg.selectAll("rect.middle_handle")
  2352. .data([null]);
  2353. middleHandle.enter()
  2354. .append("rect")
  2355. .style("cursor", "move")
  2356. .style("fill-opacity", "0")
  2357. .classed("middle_handle", true);
  2358. middleHandle
  2359. .attr('width', Math.max(0, this.currentFrame[1] - this.currentFrame[0]))
  2360. .attr('x', this.currentFrame[0] + this.config.frameHandleThickness)
  2361. .attr('height', this.config.height);
  2362. },
  2363. _registerMouseEvents: function() {
  2364. var element = d3.select(this.element);
  2365. var drag = {
  2366. target: null,
  2367. start: null,
  2368. stop: null,
  2369. left: false,
  2370. right: false,
  2371. rigid: false
  2372. };
  2373. var self = this;
  2374. function onMousemove(datum, index) {
  2375. drag.stop = self._getClientXFromEvent(d3.event, drag);
  2376. var distanceTraveled = drag.stop - drag.start;
  2377. var frameAfterDrag = self.frameBeforeDrag.slice(0);
  2378. var minimumFrameWidth = self.config.minimumFrameWidth;
  2379. if (drag.rigid) {
  2380. minimumFrameWidth = self.frameBeforeDrag[1] - self.frameBeforeDrag[0];
  2381. }
  2382. if (drag.left) {
  2383. frameAfterDrag[0] = Math.max(frameAfterDrag[0] + distanceTraveled, 0);
  2384. }
  2385. if (drag.right) {
  2386. frameAfterDrag[1] = Math.min(frameAfterDrag[1] + distanceTraveled, self.previewWidth);
  2387. }
  2388. var currentFrameWidth = frameAfterDrag[1] - frameAfterDrag[0];
  2389. if (currentFrameWidth <= minimumFrameWidth) {
  2390. if (drag.left) {
  2391. frameAfterDrag[0] = frameAfterDrag[1] - minimumFrameWidth;
  2392. }
  2393. if (drag.right) {
  2394. frameAfterDrag[1] = frameAfterDrag[0] + minimumFrameWidth;
  2395. }
  2396. if (frameAfterDrag[0] <= 0) {
  2397. frameAfterDrag[1] -= frameAfterDrag[0];
  2398. frameAfterDrag[0] = 0;
  2399. }
  2400. if (frameAfterDrag[1] >= self.previewWidth) {
  2401. frameAfterDrag[0] -= (frameAfterDrag[1] - self.previewWidth);
  2402. frameAfterDrag[1] = self.previewWidth;
  2403. }
  2404. }
  2405. self.graphs.forEach(function(graph) {
  2406. var domainScale = d3.scale.linear()
  2407. .interpolate(d3.interpolateNumber)
  2408. .domain([0, self.previewWidth])
  2409. .range(graph.dataDomain());
  2410. var windowAfterDrag = [
  2411. domainScale(frameAfterDrag[0]),
  2412. domainScale(frameAfterDrag[1])
  2413. ];
  2414. self.slideCallbacks.forEach(function(callback) {
  2415. callback(graph, windowAfterDrag[0], windowAfterDrag[1]);
  2416. });
  2417. if (frameAfterDrag[0] === 0) {
  2418. windowAfterDrag[0] = undefined;
  2419. }
  2420. if (frameAfterDrag[1] === self.previewWidth) {
  2421. windowAfterDrag[1] = undefined;
  2422. }
  2423. graph.window.xMin = windowAfterDrag[0];
  2424. graph.window.xMax = windowAfterDrag[1];
  2425. graph.update();
  2426. });
  2427. }
  2428. function onMousedown() {
  2429. drag.target = d3.event.target;
  2430. drag.start = self._getClientXFromEvent(d3.event, drag);
  2431. self.frameBeforeDrag = self.currentFrame.slice();
  2432. d3.event.preventDefault ? d3.event.preventDefault() : d3.event.returnValue = false;
  2433. d3.select(document).on("mousemove.rickshaw_range_slider_preview", onMousemove);
  2434. d3.select(document).on("mouseup.rickshaw_range_slider_preview", onMouseup);
  2435. d3.select(document).on("touchmove.rickshaw_range_slider_preview", onMousemove);
  2436. d3.select(document).on("touchend.rickshaw_range_slider_preview", onMouseup);
  2437. d3.select(document).on("touchcancel.rickshaw_range_slider_preview", onMouseup);
  2438. }
  2439. function onMousedownLeftHandle(datum, index) {
  2440. drag.left = true;
  2441. onMousedown();
  2442. }
  2443. function onMousedownRightHandle(datum, index) {
  2444. drag.right = true;
  2445. onMousedown();
  2446. }
  2447. function onMousedownMiddleHandle(datum, index) {
  2448. drag.left = true;
  2449. drag.right = true;
  2450. drag.rigid = true;
  2451. onMousedown();
  2452. }
  2453. function onMouseup(datum, index) {
  2454. d3.select(document).on("mousemove.rickshaw_range_slider_preview", null);
  2455. d3.select(document).on("mouseup.rickshaw_range_slider_preview", null);
  2456. d3.select(document).on("touchmove.rickshaw_range_slider_preview", null);
  2457. d3.select(document).on("touchend.rickshaw_range_slider_preview", null);
  2458. d3.select(document).on("touchcancel.rickshaw_range_slider_preview", null);
  2459. delete self.frameBeforeDrag;
  2460. drag.left = false;
  2461. drag.right = false;
  2462. drag.rigid = false;
  2463. }
  2464. element.select("rect.left_handle").on("mousedown", onMousedownLeftHandle);
  2465. element.select("rect.right_handle").on("mousedown", onMousedownRightHandle);
  2466. element.select("rect.middle_handle").on("mousedown", onMousedownMiddleHandle);
  2467. element.select("rect.left_handle").on("touchstart", onMousedownLeftHandle);
  2468. element.select("rect.right_handle").on("touchstart", onMousedownRightHandle);
  2469. element.select("rect.middle_handle").on("touchstart", onMousedownMiddleHandle);
  2470. },
  2471. _getClientXFromEvent: function(event, drag) {
  2472. switch (event.type) {
  2473. case 'touchstart':
  2474. case 'touchmove':
  2475. var touchList = event.changedTouches;
  2476. var touch = null;
  2477. for (var touchIndex = 0; touchIndex < touchList.length; touchIndex++) {
  2478. if (touchList[touchIndex].target === drag.target) {
  2479. touch = touchList[touchIndex];
  2480. break;
  2481. }
  2482. }
  2483. return touch !== null ? touch.clientX : undefined;
  2484. default:
  2485. return event.clientX;
  2486. }
  2487. }
  2488. });
  2489. Rickshaw.namespace("Rickshaw.Graph.Renderer");
  2490. Rickshaw.Graph.Renderer = Rickshaw.Class.create( {
  2491. initialize: function(args) {
  2492. this.graph = args.graph;
  2493. this.tension = args.tension || this.tension;
  2494. this.configure(args);
  2495. },
  2496. seriesPathFactory: function() {
  2497. //implement in subclass
  2498. },
  2499. seriesStrokeFactory: function() {
  2500. // implement in subclass
  2501. },
  2502. defaults: function() {
  2503. return {
  2504. tension: 0.8,
  2505. strokeWidth: 2,
  2506. unstack: true,
  2507. padding: { top: 0.01, right: 0, bottom: 0.01, left: 0 },
  2508. stroke: false,
  2509. fill: false,
  2510. opacity: 1
  2511. };
  2512. },
  2513. domain: function(data) {
  2514. // Requires that at least one series contains some data
  2515. var stackedData = data || this.graph.stackedData || this.graph.stackData();
  2516. // filter out any series that may be empty in the current x-domain
  2517. stackedData = stackedData.filter(function (a) { return a && a.length !== 0; });
  2518. var xMin = +Infinity;
  2519. var xMax = -Infinity;
  2520. var yMin = +Infinity;
  2521. var yMax = -Infinity;
  2522. stackedData.forEach( function(series) {
  2523. series.forEach( function(d) {
  2524. if (d.y == null) return;
  2525. var y = d.y + d.y0;
  2526. if (y < yMin) yMin = y;
  2527. if (y > yMax) yMax = y;
  2528. } );
  2529. if (!series.length) return;
  2530. if (series[0].x < xMin) xMin = series[0].x;
  2531. if (series[series.length - 1].x > xMax) xMax = series[series.length - 1].x;
  2532. } );
  2533. xMin -= (xMax - xMin) * this.padding.left;
  2534. xMax += (xMax - xMin) * this.padding.right;
  2535. yMin = this.graph.min === 'auto' ? yMin : this.graph.min || 0;
  2536. yMax = this.graph.max === undefined ? yMax : this.graph.max;
  2537. if (this.graph.min === 'auto' || yMin < 0) {
  2538. yMin -= (yMax - yMin) * this.padding.bottom;
  2539. }
  2540. if (this.graph.max === undefined) {
  2541. yMax += (yMax - yMin) * this.padding.top;
  2542. }
  2543. return { x: [xMin, xMax], y: [yMin, yMax] };
  2544. },
  2545. render: function(args) {
  2546. args = args || {};
  2547. var graph = this.graph;
  2548. var series = args.series || graph.series;
  2549. var vis = args.vis || graph.vis;
  2550. vis.selectAll('*').remove();
  2551. var data = series
  2552. .filter(function(s) { return !s.disabled })
  2553. .map(function(s) { return s.stack });
  2554. var pathNodes = vis.selectAll("path.path")
  2555. .data(data)
  2556. .enter().append("svg:path")
  2557. .classed('path', true)
  2558. .attr("d", this.seriesPathFactory());
  2559. if (this.stroke) {
  2560. var strokeNodes = vis.selectAll('path.stroke')
  2561. .data(data)
  2562. .enter().append("svg:path")
  2563. .classed('stroke', true)
  2564. .attr("d", this.seriesStrokeFactory());
  2565. }
  2566. var i = 0;
  2567. series.forEach( function(series) {
  2568. if (series.disabled) return;
  2569. series.path = pathNodes[0][i];
  2570. if (this.stroke) series.stroke = strokeNodes[0][i];
  2571. this._styleSeries(series);
  2572. i++;
  2573. }, this );
  2574. },
  2575. _styleSeries: function(series) {
  2576. var fill = this.fill ? series.color : 'none';
  2577. var stroke = this.stroke ? series.color : 'none';
  2578. var strokeWidth = series.strokeWidth ? series.strokeWidth : this.strokeWidth;
  2579. var opacity = series.opacity ? series.opacity : this.opacity;
  2580. series.path.setAttribute('fill', fill);
  2581. series.path.setAttribute('stroke', stroke);
  2582. series.path.setAttribute('stroke-width', strokeWidth);
  2583. series.path.setAttribute('opacity', opacity);
  2584. if (series.className) {
  2585. d3.select(series.path).classed(series.className, true);
  2586. }
  2587. if (series.className && this.stroke) {
  2588. d3.select(series.stroke).classed(series.className, true);
  2589. }
  2590. },
  2591. configure: function(args) {
  2592. args = args || {};
  2593. Rickshaw.keys(this.defaults()).forEach( function(key) {
  2594. if (!args.hasOwnProperty(key)) {
  2595. this[key] = this[key] || this.graph[key] || this.defaults()[key];
  2596. return;
  2597. }
  2598. if (typeof this.defaults()[key] == 'object') {
  2599. Rickshaw.keys(this.defaults()[key]).forEach( function(k) {
  2600. this[key][k] =
  2601. args[key][k] !== undefined ? args[key][k] :
  2602. this[key][k] !== undefined ? this[key][k] :
  2603. this.defaults()[key][k];
  2604. }, this );
  2605. } else {
  2606. this[key] =
  2607. args[key] !== undefined ? args[key] :
  2608. this[key] !== undefined ? this[key] :
  2609. this.graph[key] !== undefined ? this.graph[key] :
  2610. this.defaults()[key];
  2611. }
  2612. }, this );
  2613. },
  2614. setStrokeWidth: function(strokeWidth) {
  2615. if (strokeWidth !== undefined) {
  2616. this.strokeWidth = strokeWidth;
  2617. }
  2618. },
  2619. setTension: function(tension) {
  2620. if (tension !== undefined) {
  2621. this.tension = tension;
  2622. }
  2623. }
  2624. } );
  2625. Rickshaw.namespace('Rickshaw.Graph.Renderer.Line');
  2626. Rickshaw.Graph.Renderer.Line = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2627. name: 'line',
  2628. defaults: function($super) {
  2629. return Rickshaw.extend( $super(), {
  2630. unstack: true,
  2631. fill: false,
  2632. stroke: true
  2633. } );
  2634. },
  2635. seriesPathFactory: function() {
  2636. var graph = this.graph;
  2637. var factory = d3.svg.line()
  2638. .x( function(d) { return graph.x(d.x) } )
  2639. .y( function(d) { return graph.y(d.y) } )
  2640. .interpolate(this.graph.interpolation).tension(this.tension);
  2641. factory.defined && factory.defined( function(d) { return d.y !== null } );
  2642. return factory;
  2643. }
  2644. } );
  2645. Rickshaw.namespace('Rickshaw.Graph.Renderer.Stack');
  2646. Rickshaw.Graph.Renderer.Stack = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2647. name: 'stack',
  2648. defaults: function($super) {
  2649. return Rickshaw.extend( $super(), {
  2650. fill: true,
  2651. stroke: false,
  2652. unstack: false
  2653. } );
  2654. },
  2655. seriesPathFactory: function() {
  2656. var graph = this.graph;
  2657. var factory = d3.svg.area()
  2658. .x( function(d) { return graph.x(d.x) } )
  2659. .y0( function(d) { return graph.y(d.y0) } )
  2660. .y1( function(d) { return graph.y(d.y + d.y0) } )
  2661. .interpolate(this.graph.interpolation).tension(this.tension);
  2662. factory.defined && factory.defined( function(d) { return d.y !== null } );
  2663. return factory;
  2664. }
  2665. } );
  2666. Rickshaw.namespace('Rickshaw.Graph.Renderer.Bar');
  2667. Rickshaw.Graph.Renderer.Bar = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2668. name: 'bar',
  2669. defaults: function($super) {
  2670. var defaults = Rickshaw.extend( $super(), {
  2671. gapSize: 0.05,
  2672. unstack: false,
  2673. opacity: 1.0
  2674. } );
  2675. delete defaults.tension;
  2676. return defaults;
  2677. },
  2678. initialize: function($super, args) {
  2679. args = args || {};
  2680. this.gapSize = args.gapSize || this.gapSize;
  2681. $super(args);
  2682. },
  2683. domain: function($super) {
  2684. var domain = $super();
  2685. var frequentInterval = this._frequentInterval(this.graph.stackedData.slice(-1).shift());
  2686. domain.x[1] += Number(frequentInterval.magnitude);
  2687. return domain;
  2688. },
  2689. barWidth: function(series) {
  2690. var frequentInterval = this._frequentInterval(series.stack);
  2691. var barWidth = this.graph.x.magnitude(frequentInterval.magnitude) * (1 - this.gapSize);
  2692. return barWidth;
  2693. },
  2694. render: function(args) {
  2695. args = args || {};
  2696. var graph = this.graph;
  2697. var series = args.series || graph.series;
  2698. var vis = args.vis || graph.vis;
  2699. vis.selectAll('*').remove();
  2700. var barWidth = this.barWidth(series.active()[0]);
  2701. var barXOffset = 0;
  2702. var activeSeriesCount = series.filter( function(s) { return !s.disabled; } ).length;
  2703. var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth;
  2704. var transform = function(d) {
  2705. // add a matrix transform for negative values
  2706. var matrix = [ 1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0) ];
  2707. return "matrix(" + matrix.join(',') + ")";
  2708. };
  2709. series.forEach( function(series) {
  2710. if (series.disabled) return;
  2711. var barWidth = this.barWidth(series);
  2712. var nodes = vis.selectAll("path")
  2713. .data(series.stack.filter( function(d) { return d.y !== null } ))
  2714. .enter().append("svg:rect")
  2715. .attr("x", function(d) { return graph.x(d.x) + barXOffset })
  2716. .attr("y", function(d) { return (graph.y(d.y0 + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 ) })
  2717. .attr("width", seriesBarWidth)
  2718. .attr("height", function(d) { return graph.y.magnitude(Math.abs(d.y)) })
  2719. .attr("opacity", series.opacity)
  2720. .attr("transform", transform);
  2721. Array.prototype.forEach.call(nodes[0], function(n) {
  2722. n.setAttribute('fill', series.color);
  2723. } );
  2724. if (this.unstack) barXOffset += seriesBarWidth;
  2725. }, this );
  2726. },
  2727. _frequentInterval: function(data) {
  2728. var intervalCounts = {};
  2729. for (var i = 0; i < data.length - 1; i++) {
  2730. var interval = data[i + 1].x - data[i].x;
  2731. intervalCounts[interval] = intervalCounts[interval] || 0;
  2732. intervalCounts[interval]++;
  2733. }
  2734. var frequentInterval = { count: 0, magnitude: 1 };
  2735. // Sorting object's keys returned to guarantee consistency when iterating over
  2736. // Keys order in `for .. in` loop is not specified and browsers behave differently here
  2737. // This results with different invterval value being calculated for different browsers
  2738. // See last but one section here: http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.4
  2739. var keysSorted = Rickshaw.keys(intervalCounts).sort(function asc(a, b) { return Number(a) - Number(b); });
  2740. keysSorted.forEach( function(i) {
  2741. if (frequentInterval.count < intervalCounts[i]) {
  2742. frequentInterval = {
  2743. count: intervalCounts[i],
  2744. magnitude: i
  2745. };
  2746. }
  2747. } );
  2748. return frequentInterval;
  2749. }
  2750. } );
  2751. Rickshaw.namespace('Rickshaw.Graph.Renderer.Area');
  2752. Rickshaw.Graph.Renderer.Area = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2753. name: 'area',
  2754. defaults: function($super) {
  2755. return Rickshaw.extend( $super(), {
  2756. unstack: false,
  2757. fill: false,
  2758. stroke: false
  2759. } );
  2760. },
  2761. seriesPathFactory: function() {
  2762. var graph = this.graph;
  2763. var factory = d3.svg.area()
  2764. .x( function(d) { return graph.x(d.x) } )
  2765. .y0( function(d) { return graph.y(d.y0) } )
  2766. .y1( function(d) { return graph.y(d.y + d.y0) } )
  2767. .interpolate(graph.interpolation).tension(this.tension);
  2768. factory.defined && factory.defined( function(d) { return d.y !== null } );
  2769. return factory;
  2770. },
  2771. seriesStrokeFactory: function() {
  2772. var graph = this.graph;
  2773. var factory = d3.svg.line()
  2774. .x( function(d) { return graph.x(d.x) } )
  2775. .y( function(d) { return graph.y(d.y + d.y0) } )
  2776. .interpolate(graph.interpolation).tension(this.tension);
  2777. factory.defined && factory.defined( function(d) { return d.y !== null } );
  2778. return factory;
  2779. },
  2780. render: function(args) {
  2781. args = args || {};
  2782. var graph = this.graph;
  2783. var series = args.series || graph.series;
  2784. var vis = args.vis || graph.vis;
  2785. vis.selectAll('*').remove();
  2786. // insert or stacked areas so strokes lay on top of areas
  2787. var method = this.unstack ? 'append' : 'insert';
  2788. var data = series
  2789. .filter(function(s) { return !s.disabled })
  2790. .map(function(s) { return s.stack });
  2791. var nodes = vis.selectAll("path")
  2792. .data(data)
  2793. .enter()[method]("svg:g", 'g');
  2794. nodes.append("svg:path")
  2795. .attr("d", this.seriesPathFactory())
  2796. .attr("class", 'area');
  2797. if (this.stroke) {
  2798. nodes.append("svg:path")
  2799. .attr("d", this.seriesStrokeFactory())
  2800. .attr("class", 'line');
  2801. }
  2802. var i = 0;
  2803. series.forEach( function(series) {
  2804. if (series.disabled) return;
  2805. series.path = nodes[0][i++];
  2806. this._styleSeries(series);
  2807. }, this );
  2808. },
  2809. _styleSeries: function(series) {
  2810. if (!series.path) return;
  2811. d3.select(series.path).select('.area')
  2812. .attr('fill', series.color);
  2813. if (this.stroke) {
  2814. d3.select(series.path).select('.line')
  2815. .attr('fill', 'none')
  2816. .attr('stroke', series.stroke || d3.interpolateRgb(series.color, 'black')(0.125))
  2817. .attr('stroke-width', this.strokeWidth);
  2818. }
  2819. if (series.className) {
  2820. series.path.setAttribute('class', series.className);
  2821. }
  2822. }
  2823. } );
  2824. Rickshaw.namespace('Rickshaw.Graph.Renderer.ScatterPlot');
  2825. Rickshaw.Graph.Renderer.ScatterPlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2826. name: 'scatterplot',
  2827. defaults: function($super) {
  2828. return Rickshaw.extend( $super(), {
  2829. unstack: true,
  2830. fill: true,
  2831. stroke: false,
  2832. padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 },
  2833. dotSize: 4
  2834. } );
  2835. },
  2836. initialize: function($super, args) {
  2837. $super(args);
  2838. },
  2839. render: function(args) {
  2840. args = args || {};
  2841. var graph = this.graph;
  2842. var series = args.series || graph.series;
  2843. var vis = args.vis || graph.vis;
  2844. var dotSize = this.dotSize;
  2845. vis.selectAll('*').remove();
  2846. series.forEach( function(series) {
  2847. if (series.disabled) return;
  2848. var opacity = series.opacity ? series.opacity : 1;
  2849. var nodes = vis.selectAll("path")
  2850. .data(series.stack.filter( function(d) { return d.y !== null } ))
  2851. .enter().append("svg:circle")
  2852. .attr("cx", function(d) { return graph.x(d.x) })
  2853. .attr("cy", function(d) { return graph.y(d.y) })
  2854. .attr("r", function(d) { return ("r" in d) ? d.r : dotSize})
  2855. .attr("opacity", function(d) { return ("opacity" in d) ? d.opacity : opacity});
  2856. if (series.className) {
  2857. nodes.classed(series.className, true);
  2858. }
  2859. Array.prototype.forEach.call(nodes[0], function(n) {
  2860. n.setAttribute('fill', series.color);
  2861. } );
  2862. }, this );
  2863. }
  2864. } );
  2865. Rickshaw.namespace('Rickshaw.Graph.Renderer.Multi');
  2866. Rickshaw.Graph.Renderer.Multi = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2867. name: 'multi',
  2868. initialize: function($super, args) {
  2869. $super(args);
  2870. },
  2871. defaults: function($super) {
  2872. return Rickshaw.extend( $super(), {
  2873. unstack: true,
  2874. fill: false,
  2875. stroke: true
  2876. } );
  2877. },
  2878. configure: function($super, args) {
  2879. args = args || {};
  2880. this.config = args;
  2881. $super(args);
  2882. },
  2883. domain: function($super) {
  2884. this.graph.stackData();
  2885. var domains = [];
  2886. var groups = this._groups();
  2887. this._stack(groups);
  2888. groups.forEach( function(group) {
  2889. var data = group.series
  2890. .filter( function(s) { return !s.disabled } )
  2891. .map( function(s) { return s.stack });
  2892. if (!data.length) return;
  2893. var domain = null;
  2894. if (group.renderer && group.renderer.domain) {
  2895. domain = group.renderer.domain(data);
  2896. }
  2897. else {
  2898. domain = $super(data);
  2899. }
  2900. domains.push(domain);
  2901. });
  2902. var xMin = d3.min(domains.map( function(d) { return d.x[0] } ));
  2903. var xMax = d3.max(domains.map( function(d) { return d.x[1] } ));
  2904. var yMin = d3.min(domains.map( function(d) { return d.y[0] } ));
  2905. var yMax = d3.max(domains.map( function(d) { return d.y[1] } ));
  2906. return { x: [xMin, xMax], y: [yMin, yMax] };
  2907. },
  2908. _groups: function() {
  2909. var graph = this.graph;
  2910. var renderGroups = {};
  2911. graph.series.forEach( function(series) {
  2912. if (series.disabled) return;
  2913. if (!renderGroups[series.renderer]) {
  2914. var ns = "http://www.w3.org/2000/svg";
  2915. var vis = document.createElementNS(ns, 'g');
  2916. graph.vis[0][0].appendChild(vis);
  2917. var renderer = graph._renderers[series.renderer];
  2918. var config = {};
  2919. var defaults = [ this.defaults(), renderer.defaults(), this.config, this.graph ];
  2920. defaults.forEach(function(d) { Rickshaw.extend(config, d) });
  2921. renderer.configure(config);
  2922. renderGroups[series.renderer] = {
  2923. renderer: renderer,
  2924. series: [],
  2925. vis: d3.select(vis)
  2926. };
  2927. }
  2928. renderGroups[series.renderer].series.push(series);
  2929. }, this);
  2930. var groups = [];
  2931. Object.keys(renderGroups).forEach( function(key) {
  2932. var group = renderGroups[key];
  2933. groups.push(group);
  2934. });
  2935. return groups;
  2936. },
  2937. _stack: function(groups) {
  2938. groups.forEach( function(group) {
  2939. var series = group.series
  2940. .filter( function(series) { return !series.disabled } );
  2941. var data = series
  2942. .map( function(series) { return series.stack } );
  2943. if (!group.renderer.unstack) {
  2944. var layout = d3.layout.stack();
  2945. var stackedData = Rickshaw.clone(layout(data));
  2946. series.forEach( function(series, index) {
  2947. series._stack = Rickshaw.clone(stackedData[index]);
  2948. });
  2949. }
  2950. }, this );
  2951. return groups;
  2952. },
  2953. render: function() {
  2954. this.graph.series.forEach( function(series) {
  2955. if (!series.renderer) {
  2956. throw new Error("Each series needs a renderer for graph 'multi' renderer");
  2957. }
  2958. });
  2959. this.graph.vis.selectAll('*').remove();
  2960. var groups = this._groups();
  2961. groups = this._stack(groups);
  2962. groups.forEach( function(group) {
  2963. var series = group.series
  2964. .filter( function(series) { return !series.disabled } );
  2965. series.active = function() { return series };
  2966. group.renderer.render({ series: series, vis: group.vis });
  2967. series.forEach(function(s) { s.stack = s._stack || s.stack || s.data; });
  2968. });
  2969. }
  2970. } );
  2971. Rickshaw.namespace('Rickshaw.Graph.Renderer.LinePlot');
  2972. Rickshaw.Graph.Renderer.LinePlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  2973. name: 'lineplot',
  2974. defaults: function($super) {
  2975. return Rickshaw.extend( $super(), {
  2976. unstack: true,
  2977. fill: false,
  2978. stroke: true,
  2979. padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 },
  2980. dotSize: 3,
  2981. strokeWidth: 2
  2982. } );
  2983. },
  2984. seriesPathFactory: function() {
  2985. var graph = this.graph;
  2986. var factory = d3.svg.line()
  2987. .x( function(d) { return graph.x(d.x) } )
  2988. .y( function(d) { return graph.y(d.y) } )
  2989. .interpolate(this.graph.interpolation).tension(this.tension);
  2990. factory.defined && factory.defined( function(d) { return d.y !== null } );
  2991. return factory;
  2992. },
  2993. render: function(args) {
  2994. args = args || {};
  2995. var graph = this.graph;
  2996. var series = args.series || graph.series;
  2997. var vis = args.vis || graph.vis;
  2998. var dotSize = this.dotSize;
  2999. vis.selectAll('*').remove();
  3000. var data = series
  3001. .filter(function(s) { return !s.disabled })
  3002. .map(function(s) { return s.stack });
  3003. var nodes = vis.selectAll("path")
  3004. .data(data)
  3005. .enter().append("svg:path")
  3006. .attr("d", this.seriesPathFactory());
  3007. var i = 0;
  3008. series.forEach(function(series) {
  3009. if (series.disabled) return;
  3010. series.path = nodes[0][i++];
  3011. this._styleSeries(series);
  3012. }, this);
  3013. series.forEach(function(series) {
  3014. if (series.disabled) return;
  3015. var nodes = vis.selectAll("x")
  3016. .data(series.stack.filter( function(d) { return d.y !== null } ))
  3017. .enter().append("svg:circle")
  3018. .attr("cx", function(d) { return graph.x(d.x) })
  3019. .attr("cy", function(d) { return graph.y(d.y) })
  3020. .attr("r", function(d) { return ("r" in d) ? d.r : dotSize});
  3021. Array.prototype.forEach.call(nodes[0], function(n) {
  3022. if (!n) return;
  3023. n.setAttribute('data-color', series.color);
  3024. n.setAttribute('fill', 'white');
  3025. n.setAttribute('stroke', series.color);
  3026. n.setAttribute('stroke-width', this.strokeWidth);
  3027. }.bind(this));
  3028. }, this);
  3029. }
  3030. } );
  3031. Rickshaw.namespace('Rickshaw.Graph.Smoother');
  3032. Rickshaw.Graph.Smoother = Rickshaw.Class.create({
  3033. initialize: function(args) {
  3034. this.graph = args.graph;
  3035. this.element = args.element;
  3036. this.aggregationScale = 1;
  3037. this.build();
  3038. this.graph.stackData.hooks.data.push( {
  3039. name: 'smoother',
  3040. orderPosition: 50,
  3041. f: this.transformer.bind(this)
  3042. } );
  3043. },
  3044. build: function() {
  3045. var self = this;
  3046. var $ = jQuery;
  3047. if (this.element) {
  3048. $( function() {
  3049. $(self.element).slider( {
  3050. min: 1,
  3051. max: 100,
  3052. slide: function( event, ui ) {
  3053. self.setScale(ui.value);
  3054. }
  3055. } );
  3056. } );
  3057. }
  3058. },
  3059. setScale: function(scale) {
  3060. if (scale < 1) {
  3061. throw "scale out of range: " + scale;
  3062. }
  3063. this.aggregationScale = scale;
  3064. this.graph.update();
  3065. },
  3066. transformer: function(data) {
  3067. if (this.aggregationScale == 1) return data;
  3068. var aggregatedData = [];
  3069. data.forEach( function(seriesData) {
  3070. var aggregatedSeriesData = [];
  3071. while (seriesData.length) {
  3072. var avgX = 0, avgY = 0;
  3073. var slice = seriesData.splice(0, this.aggregationScale);
  3074. slice.forEach( function(d) {
  3075. avgX += d.x / slice.length;
  3076. avgY += d.y / slice.length;
  3077. } );
  3078. aggregatedSeriesData.push( { x: avgX, y: avgY } );
  3079. }
  3080. aggregatedData.push(aggregatedSeriesData);
  3081. }.bind(this) );
  3082. return aggregatedData;
  3083. }
  3084. });
  3085. Rickshaw.namespace('Rickshaw.Graph.Socketio');
  3086. Rickshaw.Graph.Socketio = Rickshaw.Class.create( Rickshaw.Graph.Ajax, {
  3087. request: function() {
  3088. var socket = io.connect(this.dataURL);
  3089. var self = this;
  3090. socket.on('rickshaw', function (data) {
  3091. self.success(data);
  3092. });
  3093. }
  3094. } );
  3095. Rickshaw.namespace('Rickshaw.Series');
  3096. Rickshaw.Series = Rickshaw.Class.create( Array, {
  3097. initialize: function (data, palette, options) {
  3098. options = options || {};
  3099. this.palette = new Rickshaw.Color.Palette(palette);
  3100. this.timeBase = typeof(options.timeBase) === 'undefined' ?
  3101. Math.floor(new Date().getTime() / 1000) :
  3102. options.timeBase;
  3103. var timeInterval = typeof(options.timeInterval) == 'undefined' ?
  3104. 1000 :
  3105. options.timeInterval;
  3106. this.setTimeInterval(timeInterval);
  3107. if (data && (typeof(data) == "object") && Array.isArray(data)) {
  3108. data.forEach( function(item) { this.addItem(item) }, this );
  3109. }
  3110. },
  3111. addItem: function(item) {
  3112. if (typeof(item.name) === 'undefined') {
  3113. throw('addItem() needs a name');
  3114. }
  3115. item.color = (item.color || this.palette.color(item.name));
  3116. item.data = (item.data || []);
  3117. // backfill, if necessary
  3118. if ((item.data.length === 0) && this.length && (this.getIndex() > 0)) {
  3119. this[0].data.forEach( function(plot) {
  3120. item.data.push({ x: plot.x, y: 0 });
  3121. } );
  3122. } else if (item.data.length === 0) {
  3123. item.data.push({ x: this.timeBase - (this.timeInterval || 0), y: 0 });
  3124. }
  3125. this.push(item);
  3126. if (this.legend) {
  3127. this.legend.addLine(this.itemByName(item.name));
  3128. }
  3129. },
  3130. addData: function(data, x) {
  3131. var index = this.getIndex();
  3132. Rickshaw.keys(data).forEach( function(name) {
  3133. if (! this.itemByName(name)) {
  3134. this.addItem({ name: name });
  3135. }
  3136. }, this );
  3137. this.forEach( function(item) {
  3138. item.data.push({
  3139. x: x || (index * this.timeInterval || 1) + this.timeBase,
  3140. y: (data[item.name] || 0)
  3141. });
  3142. }, this );
  3143. },
  3144. getIndex: function () {
  3145. return (this[0] && this[0].data && this[0].data.length) ? this[0].data.length : 0;
  3146. },
  3147. itemByName: function(name) {
  3148. for (var i = 0; i < this.length; i++) {
  3149. if (this[i].name == name)
  3150. return this[i];
  3151. }
  3152. },
  3153. setTimeInterval: function(iv) {
  3154. this.timeInterval = iv / 1000;
  3155. },
  3156. setTimeBase: function (t) {
  3157. this.timeBase = t;
  3158. },
  3159. dump: function() {
  3160. var data = {
  3161. timeBase: this.timeBase,
  3162. timeInterval: this.timeInterval,
  3163. items: []
  3164. };
  3165. this.forEach( function(item) {
  3166. var newItem = {
  3167. color: item.color,
  3168. name: item.name,
  3169. data: []
  3170. };
  3171. item.data.forEach( function(plot) {
  3172. newItem.data.push({ x: plot.x, y: plot.y });
  3173. } );
  3174. data.items.push(newItem);
  3175. } );
  3176. return data;
  3177. },
  3178. load: function(data) {
  3179. if (data.timeInterval) {
  3180. this.timeInterval = data.timeInterval;
  3181. }
  3182. if (data.timeBase) {
  3183. this.timeBase = data.timeBase;
  3184. }
  3185. if (data.items) {
  3186. data.items.forEach( function(item) {
  3187. this.push(item);
  3188. if (this.legend) {
  3189. this.legend.addLine(this.itemByName(item.name));
  3190. }
  3191. }, this );
  3192. }
  3193. }
  3194. } );
  3195. Rickshaw.Series.zeroFill = function(series) {
  3196. Rickshaw.Series.fill(series, 0);
  3197. };
  3198. Rickshaw.Series.fill = function(series, fill) {
  3199. var x;
  3200. var i = 0;
  3201. var data = series.map( function(s) { return s.data } );
  3202. while ( i < Math.max.apply(null, data.map( function(d) { return d.length } )) ) {
  3203. x = Math.min.apply( null,
  3204. data
  3205. .filter(function(d) { return d[i] })
  3206. .map(function(d) { return d[i].x })
  3207. );
  3208. data.forEach( function(d) {
  3209. if (!d[i] || d[i].x != x) {
  3210. d.splice(i, 0, { x: x, y: fill });
  3211. }
  3212. } );
  3213. i++;
  3214. }
  3215. };
  3216. Rickshaw.namespace('Rickshaw.Series.FixedDuration');
  3217. Rickshaw.Series.FixedDuration = Rickshaw.Class.create(Rickshaw.Series, {
  3218. initialize: function (data, palette, options) {
  3219. options = options || {};
  3220. if (typeof(options.timeInterval) === 'undefined') {
  3221. throw new Error('FixedDuration series requires timeInterval');
  3222. }
  3223. if (typeof(options.maxDataPoints) === 'undefined') {
  3224. throw new Error('FixedDuration series requires maxDataPoints');
  3225. }
  3226. this.palette = new Rickshaw.Color.Palette(palette);
  3227. this.timeBase = typeof(options.timeBase) === 'undefined' ? Math.floor(new Date().getTime() / 1000) : options.timeBase;
  3228. this.setTimeInterval(options.timeInterval);
  3229. if (this[0] && this[0].data && this[0].data.length) {
  3230. this.currentSize = this[0].data.length;
  3231. this.currentIndex = this[0].data.length;
  3232. } else {
  3233. this.currentSize = 0;
  3234. this.currentIndex = 0;
  3235. }
  3236. this.maxDataPoints = options.maxDataPoints;
  3237. if (data && (typeof(data) == "object") && Array.isArray(data)) {
  3238. data.forEach( function (item) { this.addItem(item) }, this );
  3239. this.currentSize += 1;
  3240. this.currentIndex += 1;
  3241. }
  3242. // reset timeBase for zero-filled values if needed
  3243. this.timeBase -= (this.maxDataPoints - this.currentSize) * this.timeInterval;
  3244. // zero-fill up to maxDataPoints size if we don't have that much data yet
  3245. if ((typeof(this.maxDataPoints) !== 'undefined') && (this.currentSize < this.maxDataPoints)) {
  3246. for (var i = this.maxDataPoints - this.currentSize - 1; i > 1; i--) {
  3247. this.currentSize += 1;
  3248. this.currentIndex += 1;
  3249. this.forEach( function (item) {
  3250. item.data.unshift({ x: ((i-1) * this.timeInterval || 1) + this.timeBase, y: 0, i: i });
  3251. }, this );
  3252. }
  3253. }
  3254. },
  3255. addData: function($super, data, x) {
  3256. $super(data, x);
  3257. this.currentSize += 1;
  3258. this.currentIndex += 1;
  3259. if (this.maxDataPoints !== undefined) {
  3260. while (this.currentSize > this.maxDataPoints) {
  3261. this.dropData();
  3262. }
  3263. }
  3264. },
  3265. dropData: function() {
  3266. this.forEach(function(item) {
  3267. item.data.splice(0, 1);
  3268. } );
  3269. this.currentSize -= 1;
  3270. },
  3271. getIndex: function () {
  3272. return this.currentIndex;
  3273. }
  3274. } );
  3275. return Rickshaw;
  3276. }));