Compare commits
1573 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60b8b0080c | |||
| b30ff9b41b | |||
| cf1a0e3c33 | |||
| e94fe13178 | |||
| b2c4561a7d | |||
| ef63c8d66e | |||
| 1697b99bbc | |||
| c419f87b64 | |||
| 9ae22a1ad7 | |||
| b4dfd35be6 | |||
| 2d217edc15 | |||
| b55d13bc21 | |||
| 1f9468e029 | |||
| 593804096d | |||
| befb14682d | |||
| 07a6700ab6 | |||
| 8131953e49 | |||
| 7e360aa915 | |||
| 0710fba638 | |||
| afb7a17e5f | |||
| badf302756 | |||
| 99941dde8f | |||
| e5c55b6962 | |||
| 7a7f4cb306 | |||
| d084e31065 | |||
| 12008ef1c6 | |||
| 6dc0823b73 | |||
| 0f9220c965 | |||
| 5c21261aea | |||
| 1fda0df03e | |||
| d4406bb806 | |||
| b1095e29bc | |||
| 3e56aa43e4 | |||
| c2dc030fae | |||
| 340cd0a1fe | |||
| 2844e706a9 | |||
| 55ee64b4e6 | |||
| 3849700a28 | |||
| 3dd0049973 | |||
| 8e6ccf1b20 | |||
| c544dbbbb7 | |||
| 5205ac38ad | |||
| 0143807515 | |||
| 5176ab1382 | |||
| b0d54f7c5d | |||
| 96f0335af4 | |||
| 572f8ebfde | |||
| 07a8d844d2 | |||
| c48abef5f8 | |||
| 9b3679792d | |||
| cb00983db3 | |||
| 83686866cc | |||
| 7a49249384 | |||
| 0548793bfd | |||
| cd770f70c0 | |||
| a3f1950a18 | |||
| 1874c28cad | |||
| 317f01a6b4 | |||
| 5e8eff074d | |||
| db028f2774 | |||
| a82a154be9 | |||
| af01927807 | |||
| f60aac93ab | |||
| 4860bf3f19 | |||
| adf2af82b4 | |||
| 8d16563014 | |||
| da765a5eec | |||
| 72c4962e97 | |||
| 789cf1d66b | |||
| 64ec922d7f | |||
| 85b3aefd67 | |||
| af0dabec99 | |||
| de85983247 | |||
| 7eaae3a6d5 | |||
| 403b57c076 | |||
| dd434b89d2 | |||
| 1a3d58e09b | |||
| 89ae239743 | |||
| c785d1509b | |||
| 9e412555bb | |||
| 0717805773 | |||
| d045c5a390 | |||
| 3cc0d0c735 | |||
| 9a3c727532 | |||
| a7f2484d45 | |||
| 96d97699a0 | |||
| e92e832258 | |||
| 3a769549d8 | |||
| 0eab0dc680 | |||
| 2b48f50de4 | |||
| fa60eacfc5 | |||
| d2a4136d4f | |||
| 7d88a746ea | |||
| 7f4537bdc6 | |||
| d68829820b | |||
| 9cf3036ab5 | |||
| 2467eb71c1 | |||
| 345bb5d1aa | |||
| 8d50828998 | |||
| 201de82baf | |||
| 99519ac3da | |||
| a575ab7c74 | |||
| c4f79814df | |||
| 6135f6271c | |||
| 8ef70f07f0 | |||
| 17b7e37885 | |||
| 35c17044e1 | |||
| 7085d98f41 | |||
| ee73e32b0e | |||
| 371430c172 | |||
| 495753b517 | |||
| 211be0a863 | |||
| 12c9a8e6c3 | |||
| fb5d9e0c01 | |||
| 61a2d962c0 | |||
| 440622830a | |||
| 2652990432 | |||
| 652e2f27b6 | |||
| 77a82eaa6a | |||
| 624a181b23 | |||
| 865ac52442 | |||
| 4f064d7c02 | |||
| facb67e94e | |||
| 124c838e06 | |||
| 026e894e1d | |||
| 2ed0f1661a | |||
| 00f285cba3 | |||
| ddf5bf472f | |||
| 534c7799e5 | |||
| b254aaa081 | |||
| 76f2860c74 | |||
| 5ebcb77d0d | |||
| 4847752ef4 | |||
| 84ec8d61ae | |||
| 0f13551b6d | |||
| 3e66c6cce4 | |||
| 09de3a5e7a | |||
| 00483d4c0d | |||
| 1e3d10ad31 | |||
| dc31cd7103 | |||
| cffb670f72 | |||
| 781fea7431 | |||
| 78b0b7e01d | |||
| e96367cd4b | |||
| 30af3c7f8a | |||
| 264405827e | |||
| 083721bbc1 | |||
| 91ed04ff83 | |||
| cbbd4fd029 | |||
| f6bc7721a8 | |||
| 32ec9e82bf | |||
| 69a7a8a04e | |||
| ccb5194bb5 | |||
| 2d280edf0c | |||
| afe4817150 | |||
| 6754b3746e | |||
| 287f83f4d0 | |||
| bf518a961e | |||
| 4a55467eef | |||
| 56cf5249f9 | |||
| 17e8bc6949 | |||
| 02ede8020f | |||
| 54158a0340 | |||
| 7ce96a4e3d | |||
| 2067b70445 | |||
| d02658e0ba | |||
| 27e68ed6a9 | |||
| b3e0ac017f | |||
| 8bc437a84e | |||
| 11e5a94fc4 | |||
| c11941cbc1 | |||
| f792aea98e | |||
| 12e5937a91 | |||
| 60b9cd8789 | |||
| 41e4c80494 | |||
| bd2756c0bf | |||
| 1f3c4948c6 | |||
| 062f9a195c | |||
| a3e8af40b2 | |||
| 49f4773e81 | |||
| ee78385b4c | |||
| e99ccd0051 | |||
| cbc022524b | |||
| 34d9d042aa | |||
| 22f69a99e2 | |||
| 4ee5539e3d | |||
| 048636e894 | |||
| 5797ad2d5b | |||
| ba9a8d52cc | |||
| ee18a9dd6c | |||
| 517c2f456c | |||
| e8734daa4d | |||
| 1fd7c6e2f1 | |||
| bc638ea06e | |||
| 5b0ba79a35 | |||
| 173c7b2aec | |||
| fd37a9c660 | |||
| fc460554e7 | |||
| 73490903a3 | |||
| 964c0ac420 | |||
| f59ad1620a | |||
| 4797f18819 | |||
| b0d73a3314 | |||
| 562e60f964 | |||
| fde0fa673b | |||
| c93d5e7830 | |||
| e305b973b2 | |||
| 506d3611fd | |||
| 14b644777d | |||
| b9c23c75f4 | |||
| af60d03060 | |||
| adb66cae9d | |||
| aeaa0c5f8d | |||
| b009da4b47 | |||
| 8abd70bed8 | |||
| 91cd6c1954 | |||
| 7a702489d0 | |||
| d33331f650 | |||
| 8e3bd07c91 | |||
| ad55551819 | |||
| 1d38e50281 | |||
| cef0882e80 | |||
| 5a07c4917e | |||
| e498d091cd | |||
| 8ba30c5453 | |||
| 685e2eaff7 | |||
| 6ca8471c64 | |||
| e30f4652af | |||
| 9acb89ba8a | |||
| 9b9a775a41 | |||
| a6f11cdb31 | |||
| 777642ef55 | |||
| d1d7fd258f | |||
| 7e4635de26 | |||
| d7b2173066 | |||
| 55e9c8c9eb | |||
| c34e1d3c19 | |||
| f89b1ba303 | |||
| fc7b6dd0c6 | |||
| 1b5240a513 | |||
| 4bcf43c84b | |||
| b57abfd563 | |||
| bfc26fa407 | |||
| e7b435b47f | |||
| dd6842522f | |||
| f56d944870 | |||
| 772b79306d | |||
| 4ef2c6286b | |||
| a5f7c1b867 | |||
| 4d8ef2a008 | |||
| 8bc28d472f | |||
| 3753ab1b55 | |||
| babc600915 | |||
| b2f92e246e | |||
| 92f9ccce5c | |||
| 0ace28b784 | |||
| bd06f0c713 | |||
| 4e5c88eb74 | |||
| 004ad15875 | |||
| 1c0a731ff6 | |||
| 9a5f42c251 | |||
| c8cdade884 | |||
| bbff66a041 | |||
| 6b6394ba97 | |||
| d11c6a20bf | |||
| 6c396e1d88 | |||
| bffd9bcb53 | |||
| 7cee448d68 | |||
| c273bf6a7b | |||
| 047a13a8dc | |||
| 717aed5a06 | |||
| 7d58090e59 | |||
| 501df8cfb9 | |||
| 7f6fe09a00 | |||
| d183d579cf | |||
| bfa2fd2edc | |||
| 7c257a09f6 | |||
| da4111579d | |||
| 6b117234bc | |||
| c4bac660cf | |||
| ee2e7d509c | |||
| 461e93fc56 | |||
| c74adc6521 | |||
| 7cc60d0b0c | |||
| d8f0612f5a | |||
| cd4b41a537 | |||
| fdb2ee2847 | |||
| 9c4cc5acda | |||
| f901254519 | |||
| 18032bb9af | |||
| a7a5fe36f9 | |||
| 32e1536955 | |||
| 77c2ee6782 | |||
| dbe5b78307 | |||
| 9578fe03b9 | |||
| ef48b66ac4 | |||
| b9d586c009 | |||
| 1d51982cb0 | |||
| 046985277d | |||
| d679337700 | |||
| 2d5f896069 | |||
| de51beebe8 | |||
| 80b25657bc | |||
| 0db60348aa | |||
| 5017024557 | |||
| 94863d69b1 | |||
| 06466b060f | |||
| 3ac9f3d9cc | |||
| d335a0221d | |||
| a118d73871 | |||
| 6c13d87a00 | |||
| 9e2264c841 | |||
| c0248069a1 | |||
| 5ba4c10a18 | |||
| 82ceb71bd0 | |||
| a4636fd88d | |||
| e19e25473c | |||
| bde415edd7 | |||
| 2004090ce5 | |||
| 18ea542cc4 | |||
| 11a856fbb3 | |||
| 1ed7899296 | |||
| a79a40b1e8 | |||
| d4075a1ef5 | |||
| 99f3b7ae99 | |||
| fcb5b355b1 | |||
| 43d26af34d | |||
| 5e1c67c042 | |||
| 11ef2be506 | |||
| 0f9bb7793c | |||
| decd7c2f60 | |||
| edf88ffdf6 | |||
| 435a7c809b | |||
| d79c66e5b6 | |||
| 10a8520805 | |||
| cf6c16102b | |||
| 32baaea5cb | |||
| 27e2795576 | |||
| c21f3810b9 | |||
| 877e7ab940 | |||
| 7a9a871be3 | |||
| de31ce1c73 | |||
| 8b6dba8301 | |||
| af859b4c13 | |||
| 9b5295ff00 | |||
| c1a94d704e | |||
| c2b56878fc | |||
| 4757bd89c0 | |||
| 1e56bf939d | |||
| dbd5ae46dd | |||
| 7c6095b6e8 | |||
| 04888646e7 | |||
| f6ed3fbb76 | |||
| 7bf2918438 | |||
| 92fefc7089 | |||
| e6df4d977d | |||
| 62ca787e6e | |||
| 32d59fff61 | |||
| 1949b6fe0f | |||
| 95a5b5025a | |||
| aa22fd2ff2 | |||
| 50df9a70c9 | |||
| 6d88e150d0 | |||
| b1817e513b | |||
| ce737a9491 | |||
| 6ff4dce62a | |||
| 89b5582d0d | |||
| 09c5e8a375 | |||
| a1a7dcd6b8 | |||
| d223d872fb | |||
| 91eaa32ad7 | |||
| 6cdccf4353 | |||
| ac9aee97fd | |||
| 5be95be9ae | |||
| e402f5dfce | |||
| 61be113714 | |||
| a637f43edd | |||
| 4bb43887a4 | |||
| 802b20be6f | |||
| 216a9c3666 | |||
| d52b2a9adc | |||
| 81782c3927 | |||
| 2c2ebbba0e | |||
| 3d323a734d | |||
| 93263a61b1 | |||
| 8b551e5285 | |||
| e11c91ddd9 | |||
| 938c83533b | |||
| 46136d616f | |||
| 8fe902e96c | |||
| 2fb7dddffb | |||
| 5d1876ab6d | |||
| 1aa4e7eee6 | |||
| 995e12bd57 | |||
| 349b6e8254 | |||
| ee6dbb9ac7 | |||
| 5b85cd83f2 | |||
| 1645c3f66b | |||
| 084d45141d | |||
| 50bec7a461 | |||
| f80afddd8f | |||
| 5d2c630b05 | |||
| f655b2313c | |||
| 2a819c2849 | |||
| 0ef13dcf7a | |||
| 5e5f3c1e95 | |||
| c76ca2dd6e | |||
| ab490b8f95 | |||
| bd42d9e7cc | |||
| e3d8f4a223 | |||
| 34567e1282 | |||
| acfb14a1f5 | |||
| 6a3495819a | |||
| bf6e8d3c1f | |||
| 66f08cc1a3 | |||
| 470012501b | |||
| 986bd02e5e | |||
| 9b8510ab3e | |||
| 0db9710e0f | |||
| 7f231057fd | |||
| bb687e47cc | |||
| 0f3928ec9a | |||
| a8380334df | |||
| 70bead1eb1 | |||
| df63530b89 | |||
| 34dedd36e6 | |||
| eb6c15caba | |||
| 39a7815f80 | |||
| 4bbe367aab | |||
| 7d50c7e4fe | |||
| 5d0adb1783 | |||
| 81ef4bdee8 | |||
| 2cf365d36d | |||
| 22d8ecffe5 | |||
| b987331b4f | |||
| eff8159d46 | |||
| f0e4f4dc9d | |||
| e02a70249e | |||
| 899afd625a | |||
| 863ec08681 | |||
| 28c958fb20 | |||
| 4c9f4a55d5 | |||
| a7fcf77251 | |||
| a57745ed64 | |||
| dd16c7d560 | |||
| 7f3780a20d | |||
| 99c8713e7f | |||
| 49eb2c426d | |||
| 4c55ff5df8 | |||
| 714f8dea1d | |||
| 1419a919aa | |||
| 6d4717fc96 | |||
| 6a8f4caee3 | |||
| dbf96a69d7 | |||
| 0519f18bd7 | |||
| f7765c80b8 | |||
| f2e2ffa170 | |||
| b2a816452d | |||
| 299e9a85f2 | |||
| ad34863bd6 | |||
| 129a91b36f | |||
| 78f10de8cd | |||
| 189b2e5cd6 | |||
| d4f67ec3b5 | |||
| 7ec54b9c2e | |||
| 60d20ea998 | |||
| e80a73cc1c | |||
| 6e51a08694 | |||
| 0da6f6b4a4 | |||
| ba3e75e710 | |||
| 8fee5e0de7 | |||
| 68a060b7eb | |||
| 9f5be974b6 | |||
| ea976d5f08 | |||
| 892d68e792 | |||
| 5a0744e2f1 | |||
| c01d8199f9 | |||
| df023d6bdb | |||
| 4bfb562477 | |||
| 4bc01b92bd | |||
| ee0f4e11ea | |||
| 1aa780f0a2 | |||
| cf58f3cbf2 | |||
| 18619e9010 | |||
| ae37dce0f3 | |||
| e48831d092 | |||
| 99f25ef744 | |||
| acf6e86c0f | |||
| 19192b622b | |||
| 183f399341 | |||
| 240c1efab9 | |||
| 8bbdb0f1b8 | |||
| ecee135a63 | |||
| 51d4630405 | |||
| 60e7695546 | |||
| 6d14a858a3 | |||
| 08a6f3af36 | |||
| cc43202e8f | |||
| 8fb3f3b1c9 | |||
| 2fe2ff474c | |||
| f0ad851aa7 | |||
| ab29a712c0 | |||
| 8dd264c4da | |||
| 40282ae23a | |||
| ed75369c6d | |||
| b34f4141a2 | |||
| 5d5d8f073a | |||
| f690961af2 | |||
| 12aa43ff5f | |||
| 4f171e3eb5 | |||
| 671f5c3a68 | |||
| 1f12185441 | |||
| a3e599e38d | |||
| 5a3779e431 | |||
| 5ddcdef1b4 | |||
| 7b6b44b284 | |||
| a691d5c7e8 | |||
| 7cf40b16df | |||
| ad6b707abf | |||
| 133052b58c | |||
| 226bf7e705 | |||
| cdcbdc308c | |||
| 5bfa6151ac | |||
| 020760f0c8 | |||
| 0c4750547a | |||
| 9c46509f84 | |||
| 928a2d646b | |||
| 7ffd1fc9c2 | |||
| 6d4f456826 | |||
| e78bf87377 | |||
| c76fc5b909 | |||
| eecd14f53a | |||
| ef29b4a5b0 | |||
| d6748792e9 | |||
| ae9c26504d | |||
| 7f7b543e18 | |||
| 2464d7bd9a | |||
| 3ce71999e2 | |||
| 7f9d83c088 | |||
| e8f6e7b56c | |||
| 94c0957e11 | |||
| b0ffe03c99 | |||
| 8ea878a2a2 | |||
| bbbab914d8 | |||
| 735a45b4e9 | |||
| a4558302f2 | |||
| d8ba26f790 | |||
| dcc3055dec | |||
| 6db7116db9 | |||
| 422988b3f6 | |||
| 2f04c5fec8 | |||
| e811aa44b0 | |||
| 4c7ab232d5 | |||
| fc42641469 | |||
| b71cfce105 | |||
| be55c1ad6f | |||
| 979a846298 | |||
| a4c59ce964 | |||
| 208c70f933 | |||
| 9dcbc06b3d | |||
| e3f6da382d | |||
| cc201ce4e7 | |||
| 08bdd11410 | |||
| bac192d825 | |||
| 55c968f138 | |||
| 6df2bc2790 | |||
| f8576c7135 | |||
| 7d220f571d | |||
| eda1ebe36b | |||
| ae4288719f | |||
| a016b655f4 | |||
| f4bc2e0649 | |||
| cf6f7035b6 | |||
| cb7498a30d | |||
| ccb3cadd2a | |||
| 0fb1a0ed6b | |||
| 94e79f1e4c | |||
| 65a28e76e5 | |||
| 0c5a9172ab | |||
| 1c21a0d609 | |||
| 16d18552f8 | |||
| 001cb80cfe | |||
| bd14d9c07b | |||
| 08bbde327f | |||
| 12488bf2b0 | |||
| 4bf0d69f79 | |||
| 78e3b69cc5 | |||
| 531a38bc39 | |||
| 77fa5454e2 | |||
| 0cd382a1ce | |||
| 9fc0c98722 | |||
| af23074820 | |||
| fdf2d5b20b | |||
| b37f2141a1 | |||
| 520dc80eeb | |||
| 878fa48afa | |||
| 514cd1bd37 | |||
| f5ee5ea814 | |||
| e3b0f7cd94 | |||
| 5567038cec | |||
| 0327c9101e | |||
| 3d86d5f530 | |||
| 073724a84c | |||
| 6a129bfe92 | |||
| 53a4c36aab | |||
| eb36f4eca0 | |||
| 54838d386d | |||
| 0235821f24 | |||
| efd116ec52 | |||
| ff0f1c5c50 | |||
| 6bbb26d2ea | |||
| 2a1140c0b3 | |||
| a80c7ada27 | |||
| 9bc6d0c7d1 | |||
| f3566a4933 | |||
| cd958a6c68 | |||
| 4f6a920f72 | |||
| b717cf1dc0 | |||
| 33e5a51a28 | |||
| ff751ca423 | |||
| 93df7f4367 | |||
| dca5a7ce4d | |||
| bf4dd78a7d | |||
| 489e484117 | |||
| 6e1cc8449b | |||
| 148ad8032f | |||
| 6d53594850 | |||
| ee7a3b3686 | |||
| 18188516ec | |||
| bcc2f089be | |||
| 07843c00d3 | |||
| de12915eba | |||
| 4395718e0c | |||
| 913fdf5247 | |||
| 650c1730c9 | |||
| fba1c66499 | |||
| 1c04823877 | |||
| 6c84624d71 | |||
| 69fa9e7b10 | |||
| 350237a774 | |||
| b73ea55bbd | |||
| 9eac6a1db0 | |||
| ea6ab46a13 | |||
| 5f84865365 | |||
| 5b3c41bd77 | |||
| c6b7ff6c75 | |||
| 337a107168 | |||
| a7485cc7f5 | |||
| 8b4edae358 | |||
| b6dca5e9b1 | |||
| 371f0a6a73 | |||
| b777a2d3c8 | |||
| bdfc4636f0 | |||
| 7ba128db71 | |||
| 0f1d8478ce | |||
| 9eae2ad76b | |||
| f43f2956f5 | |||
| 2dc03cc537 | |||
| 233b0d0de2 | |||
| dc9a044141 | |||
| 518fba759f | |||
| 7dfa1ec984 | |||
| da5a538f89 | |||
| a518796736 | |||
| 13111b3779 | |||
| 8412d77067 | |||
| fe0ea168f3 | |||
| af9d97d561 | |||
| 478f5b6e20 | |||
| c89b125b9a | |||
| a981dc71f9 | |||
| 331dd49326 | |||
| b69c37f1bb | |||
| 20ef846c38 | |||
| cd9f00300c | |||
| d6e5b57a08 | |||
| d5e5d81ee2 | |||
| dee24e6f08 | |||
| 9a2a54b885 | |||
| 4c2b01ccb0 | |||
| ddf4136022 | |||
| 4f8fc61689 | |||
| c8a7a050de | |||
| fb04b3d78b | |||
| 0f28b59c5f | |||
| e2795341d9 | |||
| 04c20b84ad | |||
| 04f5d7e411 | |||
| 82877dc53e | |||
| c7eb22021f | |||
| 654fa0426b | |||
| ae6c1c0c66 | |||
| b3f5939689 | |||
| 184404beac | |||
| 456b8258c3 | |||
| a1896d189c | |||
| e47dde9f41 | |||
| 4be19ba6d8 | |||
| 73a2839e34 | |||
| 224472c90a | |||
| d77be82a99 | |||
| 4320c7b9fb | |||
| 2dcf7fb3dd | |||
| cd1913c008 | |||
| 62694e4a93 | |||
| e92fbadc3a | |||
| 17c10bbe68 | |||
| 6c548f025e | |||
| cc15882faf | |||
| 3fd745a5df | |||
| 5e968e0aa8 | |||
| 1e3783b4dc | |||
| c355e02a20 | |||
| e178dca167 | |||
| 8beee23e21 | |||
| a894d377dc | |||
| f41426c85a | |||
| 41877e362a | |||
| de32b02c75 | |||
| e3cba22ab7 | |||
| 25bcc3cbc1 | |||
| ca5310025f | |||
| 5b9561c13e | |||
| d594ac2f00 | |||
| 5157089d87 | |||
| d5b455b1f9 | |||
| 6cb1ed6c92 | |||
| 3d0d52f82a | |||
| be04828d35 | |||
| 5f9aa2edc0 | |||
| a5751895f0 | |||
| 78b0918216 | |||
| 5a7ed24078 | |||
| 509f8f6a95 | |||
| 6f3301e82f | |||
| 80f11ce30b | |||
| 95a6d24111 | |||
| 1fc2c511af | |||
| 3bcaac7051 | |||
| 25afd5c0e7 | |||
| dbbbeddad0 | |||
| c0f274d54b | |||
| 7816e078b5 | |||
| 56bda82529 | |||
| 54adc34348 | |||
| a4ed700338 | |||
| e2d107a70b | |||
| 31f080e4f4 | |||
| 6e4b9af6b1 | |||
| d301d2a06b | |||
| 2bbd4cbb17 | |||
| 5a2d39320f | |||
| 91158b1c20 | |||
| 06115f25ba | |||
| b3b533db94 | |||
| e16125cb50 | |||
| eb0dbac0c1 | |||
| eb1d1876d3 | |||
| f751b1b7ff | |||
| 34f9cfaa02 | |||
| 6dba32bf99 | |||
| 9529246bfb | |||
| 25774ee4c3 | |||
| 1137513236 | |||
| c76855698e | |||
| be8c66adc9 | |||
| be4a374122 | |||
| d76a6d1c38 | |||
| 5945196a4f | |||
| e164ea4b46 | |||
| db3dca1d73 | |||
| eea30fa253 | |||
| b585a0ef61 | |||
| db82f22320 | |||
| 66ce38d948 | |||
| e61d435b00 | |||
| d0a8985800 | |||
| 4c1c092234 | |||
| f02079dafe | |||
| 4be0093a8a | |||
| 130eb778d3 | |||
| be177e0f80 | |||
| 42f54b8df9 | |||
| 4b60b05f75 | |||
| 6afd0f9f82 | |||
| 8288e85c25 | |||
| bb2f4ff16b | |||
| 682ba1f764 | |||
| e0c679f5ee | |||
| a1d0d028b6 | |||
| 7741872578 | |||
| d1dcd536f5 | |||
| 177d380092 | |||
| 33a15f1e93 | |||
| ad2eaf7aef | |||
| 23ae99b97e | |||
| 3c37de152d | |||
| 2dd99d053a | |||
| c60d66d94a | |||
| 6b814b8dca | |||
| 11c71d7a5d | |||
| d90b2e6142 | |||
| 2ab7f3c767 | |||
| a937fa0ef8 | |||
| bc36749f6d | |||
| 88d1735380 | |||
| 92672ffddf | |||
| 2ccfa9b4fa | |||
| 1f07be6ae0 | |||
| 9a8c7b290f | |||
| 2e3bc15d2b | |||
| 9f0117d914 | |||
| 5e5412887c | |||
| b2efd5150b | |||
| 50ec31a7a3 | |||
| 20bd9d6160 | |||
| 8c3c07acb7 | |||
| 26a56c3105 | |||
| d0070c72fd | |||
| 780f3c49e9 | |||
| 2c191dbdbf | |||
| 4b617f96a3 | |||
| dabc3f998b | |||
| 23a22102c5 | |||
| 8d9ff7af11 | |||
| fdcb6086d1 | |||
| 00e05efd7c | |||
| 0dd5eef3f2 | |||
| 6ecd30d42b | |||
| 317091639f | |||
| 9c8e879465 | |||
| dc5a04346a | |||
| 44b533f501 | |||
| 0fa459d590 | |||
| 96d34bb5ee | |||
| 9f712b44f3 | |||
| 1ff8663e44 | |||
| 52f042263b | |||
| da7e57989c | |||
| 65c3d90642 | |||
| 3c044d729d | |||
| 82ecf894c0 | |||
| 7b15f7d74b | |||
| 371406845c | |||
| ccd26a6d42 | |||
| e992930ff6 | |||
| f66d8bed8f | |||
| 155634ef34 | |||
| dbc7bc4253 | |||
| fc427e618a | |||
| 6d66190644 | |||
| 9b2da0d645 | |||
| 95c60b9554 | |||
| bc7f6a6fac | |||
| 0b1118540a | |||
| 96bd0bb46c | |||
| 33e97ee49b | |||
| 8414facde2 | |||
| 2a0d5a0a03 | |||
| aec30301a1 | |||
| 9cf246a03e | |||
| c385035ac5 | |||
| 409cf2c630 | |||
| 02ddbc8381 | |||
| 1c2a588864 | |||
| de043eeaa2 | |||
| 5e5f23b5e5 | |||
| d671ccd6ba | |||
| f4f2f09d17 | |||
| 2f1c70e49f | |||
| d9890498d1 | |||
| 46ae505bab | |||
| b0e84a68d0 | |||
| 356a5b6136 | |||
| e86193cebe | |||
| e35b2ec3ff | |||
| 69d421d785 | |||
| 47c51b28c1 | |||
| dd2653968e | |||
| b091d95eb2 | |||
| 33327d3992 | |||
| 027ce58655 | |||
| cd611ef4bf | |||
| bc7f5118ea | |||
| 53ae915044 | |||
| a47f14ec13 | |||
| 86149ba3a7 | |||
| cc3822b222 | |||
| 57b9602b13 | |||
| c9839dbefa | |||
| 5b6423ec6c | |||
| b3efcf9b68 | |||
| cadb0f96e2 | |||
| d3b825b49f | |||
| c808cbb4fa | |||
| 228d3af1fa | |||
| a1df325051 | |||
| 8e6fdfac3c | |||
| 9a1afb2376 | |||
| e9ab8c4dac | |||
| 1d98acc407 | |||
| 034613b50c | |||
| 282e611ef7 | |||
| 49fc903334 | |||
| 44b6157f17 | |||
| 0a8d3f9251 | |||
| ac505ede44 | |||
| 4770f8b580 | |||
| 67e5598d28 | |||
| 554d7ab887 | |||
| 40734bf90e | |||
| 3e720f8bae | |||
| 66d9c79bef | |||
| 89a5e5d57a | |||
| 8f6b0c50d6 | |||
| d85dfaf38a | |||
| 8511152982 | |||
| 40f9a51d4f | |||
| 30ad6d93bc | |||
| 770d907fb8 | |||
| 46cdbacf0b | |||
| c0a8adc167 | |||
| 4dd176a7f2 | |||
| eac9023b9a | |||
| a4c403286d | |||
| 3a2ee0fc72 | |||
| f097fb57b5 | |||
| 7415a362c4 | |||
| 8a7b7f5c9b | |||
| 4ce35ef352 | |||
| a12f601b9c | |||
| 4106161aa8 | |||
| 58c2f35bcf | |||
| c41839a816 | |||
| df729cd0f6 | |||
| 230ab34fe9 | |||
| 51fabab7b7 | |||
| c7a13a4641 | |||
| f855386bf1 | |||
| b8d1d9d7f1 | |||
| a22412d39d | |||
| f8bc9ba476 | |||
| fe83937368 | |||
| 3def6265c4 | |||
| d9f7c19344 | |||
| db1dd618ea | |||
| 132306fef1 | |||
| 18d29a0c7c | |||
| 4125fa75dd | |||
| bad7772b15 | |||
| 6a23202e89 | |||
| 21c384fcdd | |||
| b140fddebd | |||
| 24ea1c4847 | |||
| 9d115fc02e | |||
| 5e7756810d | |||
| c65abd19ae | |||
| 1a07c39fad | |||
| ee23cb2185 | |||
| ee5617c6b8 | |||
| dc757cbf6c | |||
| 3d3ec2a194 | |||
| d2e79f33a3 | |||
| 6156228c89 | |||
| 90f7e370f3 | |||
| 92688f03a8 | |||
| 80d0d6ecc8 | |||
| d29df4289e | |||
| 84406f0823 | |||
| 380907af8d | |||
| 0e308d9f50 | |||
| 7805f3f85b | |||
| e04d13323d | |||
| 6e8bfe1ea6 | |||
| 3baceb9dc4 | |||
| abf8ca2dc7 | |||
| 2a0a5f2d08 | |||
| d45b228461 | |||
| 231e11715f | |||
| d6f1120dfc | |||
| 7471bc791c | |||
| effcffff8a | |||
| 76186cab97 | |||
| 4756e04e25 | |||
| 5b73f1e56c | |||
| af7d332453 | |||
| 5439c3a3ce | |||
| 6c4b6bbfbe | |||
| 53719e5a8e | |||
| aed34c3b9b | |||
| a5482453ce | |||
| 712d2011c3 | |||
| 0e6942a182 | |||
| ce169edd08 | |||
| dd526e00ab | |||
| bad2ef63c6 | |||
| e283c6557b | |||
| e8da23c3e7 | |||
| 08d7bee224 | |||
| 16c95c6deb | |||
| 1d03e97721 | |||
| 9abcba3753 | |||
| b501c9bf4b | |||
| 72c506b62b | |||
| f09dfb7035 | |||
| 6cca763284 | |||
| 915c41c9d9 | |||
| 9c2c548874 | |||
| fdf699bf35 | |||
| e3ce1ab1d1 | |||
| af56d6f265 | |||
| 05eb687ca4 | |||
| 99e4408da4 | |||
| 64dd69f550 | |||
| 8b8de6bcd3 | |||
| b920ee8550 | |||
| 89cd67792b | |||
| 1f7ecb969c | |||
| d4ef1761e4 | |||
| dc01cc5c8e | |||
| db341f1814 | |||
| 06e469f059 | |||
| 9ec1e674d0 | |||
| 5c54722626 | |||
| 0a92b13263 | |||
| 2fbc3c954a | |||
| 746f96cdcd | |||
| ee9d78b190 | |||
| 0e6a02b810 | |||
| 864dddb1ff | |||
| 321495f253 | |||
| eced31e3d7 | |||
| 4b8dc06ddb | |||
| ff3ba18b15 | |||
| d24f327342 | |||
| d801a2789e | |||
| b8c8c49176 | |||
| 601f24a006 | |||
| 160b2bf03a | |||
| 944163e889 | |||
| 8656fe002a | |||
| d5e50983d8 | |||
| f3d31a62db | |||
| ee15b2c166 | |||
| 4d84ab155a | |||
| accc063db8 | |||
| b88d26ce77 | |||
| 886386508c | |||
| d893034f47 | |||
| f4b798765d | |||
| 5e6a9da3ed | |||
| 10f214fbb4 | |||
| 698d583d1d | |||
| 598211c349 | |||
| 7843821109 | |||
| 96f3baca41 | |||
| 67b4157e25 | |||
| a523903c15 | |||
| 92e9fa2084 | |||
| c75e1df190 | |||
| 79bf45b823 | |||
| 58989465f4 | |||
| 1e6b442c6c | |||
| 33addf4449 | |||
| c5fed9524a | |||
| cbfadde43c | |||
| cebe1eedeb | |||
| c22a9e51a1 | |||
| e41022c7be | |||
| 56a9389023 | |||
| 1188230641 | |||
| e760b42560 | |||
| 627c03193a | |||
| 0f837f3438 | |||
| af072da0b0 | |||
| c4f3b93da5 | |||
| 906f11ed76 | |||
| 2e7329931c | |||
| 5e5e1850ac | |||
| 93d16a27c1 | |||
| a602309a02 | |||
| 6b250d1ab6 | |||
| 1ee6affcb3 | |||
| 11e9eb3aa3 | |||
| 7a1dd0db29 | |||
| e41918569b | |||
| ec6f75b59e | |||
| ec595c23e5 | |||
| 2c5a37d38e | |||
| 3ec61cd012 | |||
| a3b4b9c8b2 | |||
| 5166d16406 | |||
| 3e7dd9bb77 | |||
| 0ab57b3d76 | |||
| 0be0824207 | |||
| 7def4e8e73 | |||
| 2397c78e9f | |||
| 9a59df793d | |||
| 1852ba99b3 | |||
| 0d665c661f | |||
| f11828075b | |||
| 9a1efefbd4 | |||
| 066ff96b58 | |||
| 7d8b00d1b9 | |||
| 5d4f3182fc | |||
| b8d80f5285 | |||
| ccb9df2a08 | |||
| c3ae9e1ee2 | |||
| abcc4b6739 | |||
| dd3f65496a | |||
| 2d69fc39de | |||
| 731cac4262 | |||
| b4846bd5d5 | |||
| fac39344f4 | |||
| ffb9878893 | |||
| 034a1af649 | |||
| 06fd894f79 | |||
| 5731648479 | |||
| 1ebc2cb9c1 | |||
| 0bd76ef7a4 | |||
| ea15f7746d | |||
| ed102269e7 | |||
| 76e1f4720b | |||
| 2317b77314 | |||
| 1a7bb10a22 | |||
| 0732006d5a | |||
| fb2e12089f | |||
| 7a0e03b374 | |||
| f2235abf08 | |||
| 5435ce20a9 | |||
| 124f1fe701 | |||
| 37e2c18c3c | |||
| 032f5a5dab | |||
| 7e41a2f40d | |||
| 8b90eb37c5 | |||
| ff797b11d1 | |||
| 31c665a837 | |||
| 68c39697a6 | |||
| 6f8881b610 | |||
| 1b468ba5d8 | |||
| 7aca2b1584 | |||
| 41e56b1f2e | |||
| cad96e652f | |||
| c7bde0ff19 | |||
| 6b94bbac2d | |||
| f7de9fbea4 | |||
| 5465479e57 | |||
| cd3ce86dbb | |||
| b109025fac | |||
| 6e74411a8e | |||
| 6c51982793 | |||
| 8f841cdea4 | |||
| eab4cfdf2d | |||
| 34c0361d1a | |||
| 24c27737c1 | |||
| b0dabaafe8 | |||
| 9472c0c5e5 | |||
| 89fe3c9150 | |||
| 9e1d551173 | |||
| 2d61b413d6 | |||
| aec08c74e0 | |||
| 48e8cb5eb1 | |||
| fd947ab887 | |||
| 5395b41852 | |||
| b7536f129b | |||
| 28b026c9ad | |||
| d4b7bfbe1d | |||
| 4b2ce036e8 | |||
| 2a350df5eb | |||
| bdc815066e | |||
| 44f4f22fdd | |||
| 5ae2044d02 | |||
| 5e80a05f32 | |||
| eb7534d319 | |||
| 471b09bd6c | |||
| a4549cbfa2 | |||
| d663267cde | |||
| 35a56962fc | |||
| 82161e60bc | |||
| 51a47cb2b1 | |||
| 9804debe86 | |||
| c93e2b80ca | |||
| 1555876280 | |||
| 1d08e6619b | |||
| 0880487be2 | |||
| 2ac4ffc576 | |||
| 7b2a0a92c4 | |||
| d883274586 | |||
| 803de95367 | |||
| 3ca39728d8 | |||
| 1111000217 | |||
| dd505fedcc | |||
| 48ff2fb8b8 | |||
| d5eafad48e | |||
| 2e1c86c0c7 | |||
| c728bf52b8 | |||
| 95a26f5080 | |||
| 85eaef6aa9 | |||
| dee160f2b8 | |||
| 6d38e52321 | |||
| caed94328f | |||
| ebf92d3033 | |||
| fdadfa3a80 | |||
| f8b84185ac | |||
| caf86686dd | |||
| bcd5d9ef90 | |||
| 45b5922d01 | |||
| 1a91eacdcb | |||
| 0421fe9a82 | |||
| 43a213643b | |||
| cd8939fe0b | |||
| c250d7c073 | |||
| 3b570edea1 | |||
| 199a3306db | |||
| 3236e06fb9 | |||
| 492618ae67 | |||
| c5737db4f5 | |||
| 40b49b9873 | |||
| 1fa34574e8 | |||
| 4c2db68037 | |||
| 1c522159df | |||
| 967dca7912 | |||
| 7d66c502f4 | |||
| 9b9e2fe6cd | |||
| 47ee0a4c97 | |||
| f10bda463c | |||
| 72d336265b | |||
| a087a6694b | |||
| 5eaf6b2adc | |||
| 57840a37d0 | |||
| aedcb372f6 | |||
| 7f554d3c84 | |||
| edf1daa7d7 | |||
| f554bf925e | |||
| 9676309ba2 | |||
| c78444fc24 | |||
| 4ab51d4bfa | |||
| e381c13dc6 | |||
| ec31898f25 | |||
| a88d78a2e9 | |||
| 92430afba9 | |||
| 8f60a4df8d | |||
| 08c9e627ab | |||
| c092d3cab4 | |||
| 46ad076751 | |||
| 07731614a9 | |||
| d8ffd729f4 | |||
| b23d94eeb8 | |||
| 4ed7144ca4 | |||
| ad924e8cc9 | |||
| aa929ec24f | |||
| 07f7fd3c7a | |||
| 83beca098b | |||
| 2c3d35d249 | |||
| 90e655d979 | |||
| 8204b51213 | |||
| c3f40d0555 | |||
| cbd134987b | |||
| 167fd68774 | |||
| 2b8cc7cfde | |||
| 62280fb24b | |||
| 2a7604ef5f | |||
| 0b47aafd14 | |||
| 24eefbed2b | |||
| ab7a73c879 | |||
| 81e0868725 | |||
| eba1e366bb | |||
| e9ccdef447 | |||
| bdce27dd7b | |||
| 436ab89d2d | |||
| 74c2db4909 | |||
| e12c3f332e | |||
| 34a64db91d | |||
| e2a4a1e2ea | |||
| 22f12316ef | |||
| e24acece49 | |||
| dd7462bd3f | |||
| da7e0cba3e | |||
| 50219aa872 | |||
| 000660b43a | |||
| fbeaa59f18 | |||
| 578e601368 | |||
| 247fe27f03 | |||
| ee2e807cc2 | |||
| c4a880c870 | |||
| 4eb6fac881 | |||
| 0ab37f49fa | |||
| 376ee02041 | |||
| c779dc6577 | |||
| ad71e81338 | |||
| 9f2ba09fc4 | |||
| 1e6cee2c5d | |||
| 4dc742489d | |||
| d751b985ac | |||
| 8a58190cce | |||
| 612ca15fc0 | |||
| 2200b76054 | |||
| 08771395b8 | |||
| e3c06cad57 | |||
| 4dfadeaf15 | |||
| af1896dc36 | |||
| 350ffdee25 | |||
| 281b92c262 | |||
| 75f1cf8cbb | |||
| bf0f44773f | |||
| dfb89ef7f5 | |||
| 5ef7126a3a | |||
| c9835a53e3 | |||
| 5ea48e6fcd | |||
| 1d1a703e3c | |||
| 8110fec4ef | |||
| 67ecf4e350 | |||
| e8ed0a3034 | |||
| add962c8ea | |||
| 9bed393210 | |||
| 3aac2b2895 | |||
| aa6814a6d3 | |||
| d291582ad9 | |||
| ebc591747a | |||
| 456602b79f | |||
| b1835a91af | |||
| 0a8d88a4fe | |||
| e2adcbb486 | |||
| aee13d295c | |||
| 6677e0e0a0 | |||
| 9d64914e62 | |||
| d136977fc4 | |||
| 7fb995094e | |||
| f002f66890 | |||
| 9de3ed8cf0 | |||
| 7ff843ec1a | |||
| b181788dc2 | |||
| 81302c1e9d | |||
| 77fbf3eb51 | |||
| 837c0c06ed | |||
| 93e7bc2a2e | |||
| 702ab3c504 | |||
| b9dea42be6 | |||
| ae608d4d78 | |||
| 5e8c88d657 | |||
| 9bbe0f9414 | |||
| f290748c72 | |||
| 96821f6303 | |||
| d290c6702a | |||
| 2877cb7160 | |||
| 14fd3c94b5 | |||
| f615a5d05e | |||
| 04a4357684 | |||
| a4c25321d8 | |||
| 068282af55 | |||
| 24078661c9 | |||
| 54670d4259 | |||
| bc276acb96 | |||
| 9e8869acf1 | |||
| c39f5e1483 | |||
| f41a09b68e | |||
| 4416fc919e | |||
| 5ed913ba5e | |||
| f4329a9483 | |||
| 24a4c31f0d | |||
| 2b1c4685d2 | |||
| 980a92c625 | |||
| 54923c082c | |||
| 7ce783705a | |||
| e23cf3377e | |||
| bed9beafdc | |||
| fe1b759787 | |||
| 8c6c81e8d0 | |||
| 874e94218c | |||
| 755a70bf77 | |||
| c58de34e63 | |||
| c798fe6822 | |||
| 5f4dfdd5d2 | |||
| 1f7043e02a | |||
| c651be7a12 | |||
| 0cc15102fb | |||
| 22aaefd1a0 | |||
| 8638626ff5 | |||
| 24fa5c8cbe | |||
| 90d054c2ec | |||
| 555145a650 | |||
| ec17d557a7 | |||
| 792df32e69 | |||
| adc96917fc | |||
| 9b0e35b4b1 | |||
| 9d652c8fe2 | |||
| 1be5794e05 | |||
| 0e561e1cd6 | |||
| 4475c38140 | |||
| cf02408620 | |||
| 87ab1d9ca4 | |||
| 9c317b8c3f | |||
| ea18b11475 | |||
| 17afa11723 | |||
| fc660e3486 | |||
| dbc12bb9ba | |||
| 2c1f58e773 | |||
| 2bde412fa3 | |||
| 03a9fc3419 | |||
| 2ca7324a45 | |||
| cf381880a5 | |||
| 12230f1fc9 | |||
| 6145214734 | |||
| 412801821f | |||
| c65c7ae252 | |||
| c3511005c7 | |||
| 31ea149820 | |||
| a0aa702725 | |||
| ac1e82075c | |||
| 65649ade14 | |||
| 34135f8e6b | |||
| 31c8a04856 | |||
| 234f9b202e | |||
| 4035c0bbe0 | |||
| 03a228fbe7 | |||
| 6e3de26477 | |||
| d892c57596 | |||
| 576ed7b7b3 | |||
| 966dede6e8 | |||
| af32590314 | |||
| 475eb8f263 | |||
| 8a40ac8991 | |||
| 3335bf21fa | |||
| 011f51fbd9 | |||
| 333714911c | |||
| f165423527 | |||
| 428fbb9cb9 | |||
| 342f9c60b2 | |||
| e13070cd6d | |||
| 76bb6bab96 | |||
| ff7b147888 | |||
| 55a598b796 | |||
| f63eac9a73 | |||
| 8d7c4cd108 | |||
| 99f608defb | |||
| 34596ada49 | |||
| 60c8f6f578 | |||
| 959da6c5ae | |||
| 5c127f945f | |||
| 74ec733444 | |||
| f560884436 | |||
| f68f82e3f4 | |||
| 56bd8006bc | |||
| ca062c3ce2 | |||
| 90aba32ca5 | |||
| e164a8274a | |||
| 23b25d4954 | |||
| bc7c8993c0 | |||
| 9db7de8373 | |||
| f8fddb49e2 | |||
| 991b2e7ca5 | |||
| 54cd96e5e0 | |||
| 599f91860a | |||
| d59564999c | |||
| 5a87adaa7f | |||
| 4e6150fe1e | |||
| 553125713d | |||
| 5583d9f315 | |||
| 20df4967a4 | |||
| 6a30435071 | |||
| 5776ff1fdf | |||
| bbb00816f1 | |||
| 3b1c409fd7 | |||
| ee68798de3 | |||
| 3ae8dc9bd7 | |||
| e9dca92c11 | |||
| 98cce223e9 | |||
| 1d7a470f78 | |||
| 461d5efc4b | |||
| 705dbf60fa | |||
| bb180ad59a | |||
| 463e1bcb90 | |||
| 22ac2df595 | |||
| 73af635be6 | |||
| c1c97a5fc1 | |||
| 6a97dc8825 | |||
| d832f34733 | |||
| 859a600b92 | |||
| 6b3cf50f38 | |||
| 3ccd1bc368 | |||
| 624d33b96f | |||
| 156f61f526 | |||
| fd3a04fce3 | |||
| 62be4e1274 | |||
| db096196c9 | |||
| a0ac70be3c | |||
| 1f0f89f83c | |||
| ee706ad8c7 | |||
| ce31ea8bf3 | |||
| dcab8e3ac2 | |||
| 1ad9a7e176 | |||
| f1bd7ab1dc | |||
| 79399913d7 | |||
| 4a2e88a81c | |||
| 8d5b748b09 | |||
| b2dba8af73 | |||
| c258bc49c3 | |||
| e3e2a354e7 | |||
| 32c1c31139 | |||
| b8b5a33833 | |||
| 5407fa73f8 | |||
| 1cb0c0071b | |||
| a456ac4c90 | |||
| 71b48427c5 | |||
| ec06a01b6c | |||
| e3ccc34a2c | |||
| 396721568f | |||
| e53ab85745 | |||
| 4d99b37def | |||
| 2e24da3dcd | |||
| 0cc1bd2dc6 | |||
| aec162c50e | |||
| d5e9340f8f | |||
| 6fe87c4c69 | |||
| 85f0a87213 | |||
| 853f85be2e | |||
| a7448e3a1d | |||
| 521e753183 | |||
| 9193941fd0 | |||
| f56c6effe4 | |||
| 4cfcb239c1 | |||
| a4be274718 | |||
| 120f861a27 | |||
| 7e56d85b83 | |||
| 20af495581 | |||
| bf94c48225 | |||
| 27a68333b1 | |||
| 63f18dd26a | |||
| 4ea787743e | |||
| 3a4b881576 | |||
| f087594331 | |||
| 5491e40fd1 | |||
| 4b690ec9ad | |||
| 9fecb03b5f | |||
| de6e3d46ec | |||
| 3006a26500 | |||
| 6ffed9387c | |||
| 558627e7a9 | |||
| 4b276f977c | |||
| 99fe4f615d | |||
| 928c306803 | |||
| a3eddf8ba7 | |||
| 78e0ae31af | |||
| 1a90ce0950 | |||
| 2465b3eb5a | |||
| 16a2e34e95 | |||
| 5c52d68091 | |||
| 1a2e180f4f | |||
| 9048053669 | |||
| 69b69714cb | |||
| 1ecd9f9e19 | |||
| 2bca9ca9ec | |||
| d66028ed0d | |||
| 1cf33044bc | |||
| 2c138c8355 | |||
| 73ffcb9bc1 | |||
| 730025e144 | |||
| 91aa1d718a | |||
| f881bfc4fc | |||
| e76c3fff69 | |||
| 9f168071ff | |||
| b6652b7619 | |||
| f6d501f69e | |||
| 4c052cb3b3 | |||
| ea8ccf38db | |||
| cf12ff8070 | |||
| b7a95244db | |||
| 4c884651a8 | |||
| e9eca577aa | |||
| cff78b477c | |||
| b232410d25 | |||
| 5ff338d4ac | |||
| a5ceea153d |
@@ -5,3 +5,4 @@
|
||||
examples/.ipynb_checkpoints/*
|
||||
examples/meetup/.ipynb_checkpoints/*
|
||||
deps/plotly-latest.min.js
|
||||
deps/build.log
|
||||
|
||||
+6
-6
@@ -2,13 +2,13 @@
|
||||
language: julia
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
# - osx
|
||||
julia:
|
||||
- release
|
||||
# - 0.7
|
||||
- nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- julia: nightly
|
||||
# matrix:
|
||||
# allow_failures:
|
||||
# - julia: nightly
|
||||
|
||||
# # before install:
|
||||
# # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
|
||||
@@ -45,7 +45,7 @@ notifications:
|
||||
# uncomment the following lines to override the default test script
|
||||
script:
|
||||
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
|
||||
- julia -e 'Pkg.clone(pwd()); Pkg.build("Plots")'
|
||||
- julia -e 'import Pkg; Pkg.add(Pkg.PackageSpec(path=pwd())); Pkg.build("Plots")'
|
||||
- julia test/travis_commands.jl
|
||||
# - julia -e 'Pkg.clone("ImageMagick"); Pkg.build("ImageMagick")'
|
||||
# - julia -e 'Pkg.clone("GR"); Pkg.build("GR")'
|
||||
|
||||
@@ -3,11 +3,469 @@
|
||||
|
||||
#### notes on release changes, ongoing development, and future planned work
|
||||
|
||||
- All new development should target 0.7!
|
||||
- Minor version 0.17 is the last one to support Julia 0.6!!
|
||||
- Minor version 0.11 is the last one to support Julia 0.5!!
|
||||
- Critical bugfixes only
|
||||
- `backports` branch is for Julia 0.5
|
||||
|
||||
---
|
||||
## (current master)
|
||||
- All new development should target Julia 1.x!
|
||||
|
||||
## 0.19.2
|
||||
- several small fixes for 1.0 compatibility
|
||||
|
||||
## 0.19.1
|
||||
- don't broadcast plot_color
|
||||
|
||||
## 0.19.0
|
||||
- Refactor conditional loading to use Requires
|
||||
- Many fixes for 1.0 compatibility
|
||||
|
||||
## 0.18.0
|
||||
- update minor version to 0.7
|
||||
|
||||
## 0.17.4
|
||||
- fix thickness_scaling for pyplot
|
||||
|
||||
## 0.17.3
|
||||
- Log-scale heatmap edge computation
|
||||
- Fix size and dpi for GR and PyPlot
|
||||
- Fix fillrange with line segments on PyPlot and Plotly
|
||||
- fix flip for heatmap and image on GR
|
||||
- New attributes for PGFPlots
|
||||
- Widen axes for most series types and log scales
|
||||
- Plotly: fix log scale with no ticks
|
||||
- Fix axis flip on Plotly
|
||||
- Fix hover and zcolor interaction in Plotly
|
||||
- WebIO integration for PlotlyJS backend
|
||||
|
||||
## 0.17.2
|
||||
- fix single subplot in plotly
|
||||
- implement `(xyz)lims = :round`
|
||||
- PyPlot: fix bg_legend = invisible()
|
||||
- set fallback tick specification for axes with discrete values
|
||||
- restructure of show methods
|
||||
|
||||
## 0.17.1
|
||||
- Fix contour for PGFPlots
|
||||
- 32Bit fix: Int64 -> Int
|
||||
- Make series of shapes and segments toggle together in Plotly(JS)
|
||||
- Fix marker arguments
|
||||
- Fix processing order of series recipes
|
||||
- Fix Plotly(JS) ribbon
|
||||
- Contour plots with x,y in grid form on PyPlot
|
||||
|
||||
## 0.17.0
|
||||
- Add GR dependency to make it the default backend
|
||||
- Improve histogram2d bin estimation
|
||||
- Allow vector arguments for certain series attributes and support line_z and fill_z on GR, PyPlot, Plotly(JS) and PGFPlots
|
||||
- Automatic scientific notation for tick labels
|
||||
- Allow to set the theme in PLOTS_DEFAULTS
|
||||
- Implement plots_heatmap seriestype providing a Plots recipe for heatmaps
|
||||
|
||||
## 0.16.0
|
||||
- fix 3D plotting in PyPlot
|
||||
- Infinite objects
|
||||
|
||||
## 0.15.1
|
||||
|
||||
- fix scientific notation for labels in GR
|
||||
- fix labels with logscale
|
||||
- fix image cropping with GR
|
||||
- fix grouping of annotations
|
||||
- fix annotations in Plotly
|
||||
- allow saving notebook with plots as pdf from IJulia
|
||||
- fix fillrange and ribbon for step recipes
|
||||
- implement native ticks that respond to zoom
|
||||
- fix bar plot with one bar
|
||||
- contour labels and colorbar fixes
|
||||
- interactive linked axis for PyPlot
|
||||
- add `NamedTuple` syntax to group with named legend
|
||||
- use bar recipe in Plotly
|
||||
- implement categorical ticks
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- improve resolution of png output of GR with savefig()
|
||||
- add check for ticks=nothing
|
||||
- allow transparency in heatmaps
|
||||
- fix line_z for GR
|
||||
- fix legendcolor for pyplot
|
||||
- fix pyplot ignoring alpha values of images
|
||||
- don't let `abline!` change subplot limits
|
||||
- update showtheme recipe
|
||||
|
||||
## 0.14.2
|
||||
|
||||
- fix plotly bar lines bug
|
||||
- allow passing multiple series to `ribbon`
|
||||
- add a new example for `line_z`
|
||||
|
||||
## 0.14.1
|
||||
|
||||
- Add linestyle argument to the legend
|
||||
- Plotly: bar_width and stroke_width support for bar plots
|
||||
- abline! does not change axis limits
|
||||
- Fix default log scale ticks in GR backend
|
||||
- Use the :fontsize keys so the scalefontsizes command works
|
||||
- Prepare support for new PlotTheme type in PlotThemes
|
||||
|
||||
## 0.14.0
|
||||
|
||||
- remove use of imagemagick; saving gifs now requires ffmpeg
|
||||
- improvements to ffmpeg gif quality and speed
|
||||
- overhaul of fonts, allows setting fonts in recipes and with magic arguments
|
||||
- added `camera` attribute to control camera position for 3d plots
|
||||
- added `showaxis` attribute to control which axes to display
|
||||
- improvements of polar plots axes, and better backend consistency
|
||||
- changed the 'spy' recipe back to using heatmap
|
||||
- added `scatterpath` seriestype
|
||||
- allow plotlyjs to save svg
|
||||
- add `reset_defaults()` function to reset plot defaults
|
||||
- update syntax to 0.6
|
||||
- make `fill = true` fill to 0 rather than to 1
|
||||
- use new `@df` syntax in StatPlots examples
|
||||
- allow changing the color of legend box
|
||||
- implement `title_location` for gr
|
||||
- add `hline` marker to pgfplots - fixes errorbars
|
||||
- pyplot legends now show marker types
|
||||
- pyplot colorbars take font style from y axis
|
||||
- pyplot tickmarks color the same as axis color
|
||||
- allow setting linewidth for contour in gr
|
||||
- allow legend to be outside plot area for pgfplots
|
||||
- expand axis extrema for heatmap
|
||||
- extendg grid lines to axis limits
|
||||
- fix `line_z` for pyplot and gr
|
||||
- fixed colorbar problem for flipped axes with gr
|
||||
- fix marker_z for 3d plots in gr
|
||||
- fix `weights` functionality for histograms
|
||||
- fix gr annotations with colorbar
|
||||
- fix aspect ratio in gr
|
||||
- fix "hidden window" problem after savefig in gr
|
||||
- fix pgfplots logscale ticks error
|
||||
- fix pgfplots legends symbols
|
||||
- fix axis linking for plotlyjs
|
||||
- fix plotting of grayscale images
|
||||
|
||||
## 0.13.1
|
||||
|
||||
- fix a bug when passing a vector of functions with no bounds (e.g. `plot([sin, cos])`)
|
||||
- export `pct` and `px` from Plots.PlotMeasures
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- support `plotattributes` rather than `d` in recipes
|
||||
- no longer export `w`, `h` and names from Measures.jl; use `using Plots.PlotMeasures` to get these names back
|
||||
- `bar_width` now depends on the minimum distance between bars, not the mean
|
||||
- better automatic x axis limits for plotting Functions
|
||||
- `tick_direction` attribute now allows ticks to be on the inside of the plot border
|
||||
- removed a bug where `p1 = plot(randn(10)); plot(p1, p2)` made `display(p1)` impossible
|
||||
- allow `plot([])` to generate an empty plot
|
||||
- add `origin` framestyle
|
||||
- ensure finite bin number on histograms with only one unique value
|
||||
- better automatic histogram bins for 2d histograms
|
||||
- more informative error message on passing unsupported seriestype in a recipe
|
||||
- allow grouping in user recipes
|
||||
- GR now has `line_z` and `fill_z` attributes for determining the color of shapes and lines
|
||||
- change GR default view angle for 3D plots to match that of PyPlot
|
||||
- fix `clims` on GR
|
||||
- fix `marker_z` for plotly backend
|
||||
- implement `framestyle` for plotly
|
||||
- fix logscale bug error for values < 1e-16 on pyplot
|
||||
- fix an issue on pyplot where >1 colorbar would be shown if there was >1 series
|
||||
- fix `writemime` for eps
|
||||
|
||||
## 0.12.4
|
||||
|
||||
- added a new `framestyle` argument with choices: :box, :semi, :axes, :grid and :none
|
||||
- changed the default bar width to 0.8
|
||||
- added working ribbon to plotly backend
|
||||
- ensure that automatic ticks always generate 4 to 8 ticks
|
||||
- group now groups keyword arguments of the same length as the input
|
||||
- allow passing DateTime objects as ticks
|
||||
- allow specifying the number of ticks as an integre
|
||||
- fix bug on errorbars in gr
|
||||
- fixed some but not all world age issues
|
||||
- better margin with room for text
|
||||
- added a `match` option for linecolor
|
||||
- better error message un unsupported series types
|
||||
- add a 'stride' keyword for the pyplot backend
|
||||
|
||||
## 0.12.3
|
||||
|
||||
- new grid line style defaults
|
||||
- `grid` is now an axis attribute and a magic argument: it is now possible to modify the grid line style, alpha and line width
|
||||
- Enforce plot order in user recipes
|
||||
- import `plot!` from RecipesBase
|
||||
- GR no longer automatically handles _ and ^ in texts
|
||||
- fix GR colorbar for scatter plots
|
||||
|
||||
#### 0.12.2
|
||||
|
||||
- fix an issue with Juno/PlotlyJS compatibility on new installations
|
||||
- fix markers not showing up in seriesrecipes using :scatter
|
||||
- don't use pywrap in the pyplot backend
|
||||
- improve the bottom margin for the gr backend
|
||||
|
||||
#### 0.12.1
|
||||
|
||||
- fix deprecation warnings
|
||||
- switch from FixedSizeArrays to StaticArrays.FixedSizeArrays
|
||||
- drop FactCheck in tests
|
||||
- remove julia 0.5 compliant uses of transpose operator
|
||||
- fix GR heatmap bugs
|
||||
- fix GR guide padding
|
||||
- improve legend markers in GR
|
||||
- add surface alpha for Plotly(JS)
|
||||
- add fillrange to Plotly(JS)
|
||||
- allow usage of Matplotlib 1.5 with PyPlot
|
||||
- fix GLVisualize for julia 0.6
|
||||
- conform to changes in InspectDR
|
||||
|
||||
#### 0.12.0
|
||||
|
||||
- 0.6 only
|
||||
|
||||
#### 0.11.3
|
||||
|
||||
- add HDF5 backend
|
||||
- GR replaces PyPlot as first-choice backend
|
||||
- support for legend position in GR
|
||||
- smaller markers in GR
|
||||
- better viewport size in GR
|
||||
- fix glvisualize support
|
||||
- remove bug with three-argument method of `text`
|
||||
- `legendtitle` attribute added
|
||||
- add test for `spy`
|
||||
|
||||
#### 0.11.0
|
||||
|
||||
- julia 0.6 compatibility
|
||||
- matplotlib 2.0 compatibility
|
||||
- add inspectdr backend
|
||||
- improved histogram functionality:
|
||||
- added a `:stephist` and `:scatterhist` series type as well as ``:barhist` (the default)
|
||||
- support for log scale axes with histograms
|
||||
- support for plotting `StatsBase.Histogram`
|
||||
- allowing bins to be specified as `:sturges`, `:rice`, `:scott` or :fd
|
||||
- allow `normalization` to be specified as :density (for unequal bins) or :pdf (sum to 1)
|
||||
- add a `plotattr` function to access documentation for Plots attribute
|
||||
- add `fill_z` attribute for pyplot
|
||||
- add colorbar_title to plotlyjs
|
||||
- enable standalone window for plotlyjs
|
||||
- improved support for pgfplots, ticks rotation, clims, series_annotations
|
||||
- restore colorbars for GR
|
||||
- better axis labels for heatmap in GR
|
||||
- better marker sizes in GR
|
||||
- fix color representation in GR
|
||||
- update GR legend
|
||||
- fix image bug on GR
|
||||
- fix glvisualize dependencies
|
||||
- set dotted grid lines for pyplot
|
||||
- several improvements to inspectdr
|
||||
- improved tick positions for TimeType x axes
|
||||
- support for improved color gradient capability in PlotUtils
|
||||
- add a showlibrary recipe to display color libraries
|
||||
- add a showgradient recipe to display color gradients
|
||||
- add `vectorfield` as an alias for `quiver`
|
||||
- use `PlotUtils.adaptedgrid` for functions
|
||||
|
||||
|
||||
#### 0.9.5
|
||||
|
||||
- added dependency on PlotThemes
|
||||
- set_theme --> theme
|
||||
- remove Compat from REQUIRE
|
||||
- warning for DataFrames without StatPlots
|
||||
- closeall exported and implemented for gr/pyplot
|
||||
- fix DateTime recipe
|
||||
- reset theme with theme(:none)
|
||||
- fix link_axes! for nested subplots
|
||||
- fix plotly lims for log scale
|
||||
|
||||
#### 0.9.4
|
||||
|
||||
- optimizations surrounding Subplot.series_list
|
||||
- better Atom support, support plotlyjs
|
||||
- gr:
|
||||
- gks_wstype defaults and gr_set_output
|
||||
- heatmap uses GR.drawimage
|
||||
- histogram2d puts NaN for zeros
|
||||
- improved support of NaN in heatmaps
|
||||
- rebuilt spy recipes to output scatters with marker_z set
|
||||
- deprecate png support in plotly... point to plotlyjs
|
||||
- fixes:
|
||||
- axis widen with lims set
|
||||
- reset_extrema, setxyz
|
||||
- bar plot widen
|
||||
- better tick padding
|
||||
- consistent tick rotation
|
||||
- consistent aspect_ratio
|
||||
- pyplot dpi
|
||||
- plotly horizontal bars
|
||||
- handle series attributes when combining subplots
|
||||
- gr images transposed
|
||||
- converted Date/DateTime to new type recipe approach for arrays
|
||||
- issues closed include: #505 #513 #479 #523 #526 #529
|
||||
|
||||
#### 0.9.3
|
||||
|
||||
- support pdf and eps in plotlyjs backend
|
||||
- allow curly after grid: `@layout [a b; grid(4,4){0.8h}]`
|
||||
- add_backend redesign
|
||||
|
||||
#### 0.9.2
|
||||
|
||||
- glvisualize backend (@SimonDanisch)
|
||||
- too much to list! ready for alpha testing
|
||||
- Volume and volume seriestype
|
||||
- Atom: support for PlotPane and proper gui display
|
||||
- gr:
|
||||
- clims
|
||||
- aspect ratio
|
||||
- pgfplots:
|
||||
- fixes for ticks, axes, and more
|
||||
- pyplot:
|
||||
- font families
|
||||
- colorbar guide
|
||||
- pixel marker
|
||||
- unicodeplots
|
||||
- basic support for shapes
|
||||
- improved add_backend
|
||||
- refactor of is_supported methods
|
||||
- element-wise type recipes (see https://github.com/tbreloff/Plots.jl/issues/460#issuecomment-248428908)
|
||||
- several other fixes/improvements
|
||||
|
||||
#### 0.9.1
|
||||
|
||||
- Pkg.dir --> dirname (@tkelman)
|
||||
- `axis = nothing` magic
|
||||
- fixes:
|
||||
- clim for line_z
|
||||
- sticks default range for log scale
|
||||
- rotate method
|
||||
- pyplot heatmap
|
||||
- spurious scale warnings
|
||||
- gr image/alpha
|
||||
- plotly.js path
|
||||
- orientation extrema
|
||||
- bar, reset orientation
|
||||
- switch transpose_z to use permutedims
|
||||
- skinny x/+ markers
|
||||
- ticks in pgfplots
|
||||
- eps in savefig (@anriseth)
|
||||
- add_backend convenience
|
||||
- type recipes for Date/DateTime (@maximsch2)
|
||||
- mirror attribute and twinx convenience
|
||||
- Axis.sp --> Axis.sps
|
||||
- recipe postprocessing for allowing aliases and magic args in recipe bodies
|
||||
|
||||
|
||||
|
||||
#### 0.9.0
|
||||
|
||||
- fixes to cycle
|
||||
- add back single function recipe: `plot!(cos)`
|
||||
- new axis formatter attribute... accepts functions to convert numbers to strings
|
||||
- fix for inset plots
|
||||
- GR:
|
||||
- fillrange fix
|
||||
- annotations fix
|
||||
- force double buffering in display
|
||||
|
||||
---
|
||||
|
||||
## 0.7 (current master/dev)
|
||||
## 0.8
|
||||
|
||||
#### 0.8.2 (backported bug fixes for julia 0.4)
|
||||
|
||||
- plotly ticks fix
|
||||
- unicodeplots size fix
|
||||
- remove mkdir call in tests
|
||||
|
||||
#### 0.8.1
|
||||
|
||||
- manual drawing of axes/ticks/labels
|
||||
- get_ticks uses optimize_ticks and Showoff
|
||||
- changed PLOTS_DEFAULTS to be a global variable, not ENV key
|
||||
- parameterized Segments for pushing tuples
|
||||
- fix to axis extrema for Bool/nothing
|
||||
- GR:
|
||||
- manually draw 2D axes... fixes several issues and missing features
|
||||
- fontsize fix
|
||||
- PGFPlots: pass axis syle
|
||||
|
||||
#### 0.8.0
|
||||
|
||||
- added dependency on PlotUtils
|
||||
- BREAKING: removed DataFrames support (now in StatPlots.jl)
|
||||
- BREAKING: removed boxplot/violin/density recipes (now in StatPlots.jl)
|
||||
- GR:
|
||||
- inline iterm2 support
|
||||
- trisurface support
|
||||
- heatmap fix
|
||||
- PyPlot:
|
||||
- ijulia display fix
|
||||
- GLVisualize:
|
||||
- first try with shapes
|
||||
- iter_segments improvements
|
||||
- bar_width support
|
||||
- horizontal bars
|
||||
- improve tick display
|
||||
- better shape handling in pyplot, plotly
|
||||
- improved padding calcs
|
||||
- internal reorg of _plots method, add pipeline.jl
|
||||
|
||||
---
|
||||
|
||||
## 0.7
|
||||
|
||||
#### 0.7.5
|
||||
|
||||
- GR: LaTeX support
|
||||
- Changed docs url to juliaplots.github.io
|
||||
- added `contourf` seriestype
|
||||
- allow `plt[1]` to return first Subplot
|
||||
- allow `sp[1]` to return the first Series of the Subplot
|
||||
- `series[k]` now passes through to `series.d[k]`
|
||||
- allow calling `plot!(sp, ...)` to update a target Subplot
|
||||
- PyPlot: zorder fix
|
||||
- new DataFrames logic/recipe: more flexible/robust and allow Symbols for:
|
||||
- `(:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)`
|
||||
- new `display_type` and `extra_kwargs` plot attributes
|
||||
- surface fix
|
||||
|
||||
#### 0.7.4
|
||||
|
||||
- added snooped precompiles, but left commented out
|
||||
- GR fixes: markersize, shapes, legends
|
||||
- fixes to recipes
|
||||
- turned on Appveyor
|
||||
|
||||
#### 0.7.3
|
||||
|
||||
- rebuild violin and boxplot recipes
|
||||
- "plot recipes"
|
||||
- `cgrad` method for easy color gradient creation
|
||||
- improvements to inset subplots
|
||||
- Segments and iter_segments for NaN-separated vectors
|
||||
- `bar` recipe now creates a `shape` series
|
||||
- writemime fix for Interact.jl
|
||||
- `link = :square` option
|
||||
- !!! set `shape` attributes with line/fill, NOT marker/markerstroke !!!
|
||||
- basic DPI support
|
||||
- moved chorddiagram to PlotRecipes
|
||||
- GR:
|
||||
- use temp files for img output
|
||||
- basic support for marker strokes and other marker fixes
|
||||
- PyPlot:
|
||||
- Switch to recipes for bar, histogram, histogram2d
|
||||
- GLVisualize
|
||||
- subplots
|
||||
- path/scatter and path3d/scatter3d
|
||||
- initial drawing of axes
|
||||
- many smaller fixes and improvements
|
||||
|
||||
#### 0.7.2
|
||||
|
||||
@@ -55,7 +513,7 @@
|
||||
|
||||
#### 0.7.0
|
||||
|
||||
- Check out [the summary](http://plots.readthedocs.io/en/latest/plots_v0.7/)
|
||||
- Check out [the summary](http://juliaplots.github.io/plots_v0.7/)
|
||||
- Revamped and simplified internals
|
||||
- [Recipes, recipes, recipes](https://github.com/JuliaPlots/RecipesBase.jl/issues/6)
|
||||
- [Layouts and Subplots](https://github.com/tbreloff/Plots.jl/issues/60)
|
||||
@@ -133,7 +591,7 @@
|
||||
- z-axis keywords
|
||||
- 3D indexing overhaul: `push!`, `append!` support
|
||||
- matplotlib colormap constants (`:inferno` is the new default colormap for Plots)
|
||||
- `typealias KW Dict{Symbol,Any}` used in place of splatting in many places
|
||||
- `const KW = Dict{Symbol,Any}` used in place of splatting in many places
|
||||
- png generation for plotly backend using wkhtmltoimage
|
||||
- `normalize` and `weights` keywords
|
||||
- background/foreground subcategories for fine-tuning of looks
|
||||
@@ -187,7 +645,7 @@
|
||||
- Integration with [PlotlyJS.jl](https://github.com/spencerlyon2/PlotlyJS.jl) for using Plotly inside a Blink window @spencerlyon2
|
||||
- The Plotly backend has been split into my built-in version (`plotly()`) and @spencerlyon2's backend (`plotlyjs()`)
|
||||
- Revamped backend setup code for easily adding new backends
|
||||
- New docs (WIP) at http://plots.readthedocs.org/
|
||||
- New docs (WIP) at http://juliaplots.github.io/
|
||||
- Overhaul to `:legend` keyword (see https://github.com/tbreloff/Plots.jl/issues/135)
|
||||
- New dependency on Requires, allows auto-loading of DataFrames support
|
||||
- Support for plotting lists of Tuples and FixedSizeArrays
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
# Plots
|
||||
|
||||
[](https://travis-ci.org/tbreloff/Plots.jl)
|
||||
[](https://travis-ci.org/JuliaPlots/Plots.jl)
|
||||
[](https://ci.appveyor.com/project/mkborregaard/plots-jl)
|
||||
[](https://gitter.im/tbreloff/Plots.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- [](http://pkg.julialang.org/?pkg=Plots&ver=0.3) -->
|
||||
<!-- [](http://pkg.julialang.org/?pkg=Plots&ver=0.4) -->
|
||||
<!-- [](https://coveralls.io/r/tbreloff/Plots.jl?branch=master) -->
|
||||
<!-- [](http://codecov.io/github/tbreloff/Plots.jl?branch=master) -->
|
||||
|
||||
#### Author: Thomas Breloff (@tbreloff)
|
||||
#### Created by Tom Breloff (@tbreloff)
|
||||
|
||||
#### Maintained by the [JuliaPlot members](https://github.com/orgs/JuliaPlots/people)
|
||||
|
||||
Plots is a plotting API and toolset. My goals with the package are:
|
||||
|
||||
- **Powerful**. Do more with less. Complex visualizations become easy.
|
||||
- **Intuitive**. Start generating plots without reading volumes of documentation. Commands should "just work".
|
||||
- **Intuitive**. Stop reading so much documentation. Commands should "just work".
|
||||
- **Concise**. Less code means fewer mistakes and more efficient development/analysis.
|
||||
- **Flexible**. Produce your favorite plots from your favorite package, but quicker and simpler.
|
||||
- **Consistent**. Don't commit to one graphics package. Use the same code and access the strengths of all [backends](http://plots.readthedocs.io/en/latest/backends/).
|
||||
- **Lightweight**. Very few dependencies, since backends are loaded and initialized dynamically.
|
||||
- **Consistent**. Don't commit to one graphics package, use the same code everywhere.
|
||||
- **Lightweight**. Very few dependencies.
|
||||
- **Smart**. Attempts to figure out what you **want** it to do... not just what you **tell** it.
|
||||
|
||||
Use the [preprocessing pipeline](http://plots.readthedocs.io/en/latest/pipeline/) in Plots to fully describe your visualization before it calls the backend code. This maintains modularity and allows for efficient separation of front end code, algorithms, and backend graphics. New graphical backends can be added with minimal effort.
|
||||
|
||||
```julia
|
||||
using Plots
|
||||
pyplot(reuse=true)
|
||||
|
||||
@gif for i in linspace(0,2π,100)
|
||||
X = Y = linspace(-5,5,40)
|
||||
surface(X, Y, (x,y) -> sin(x+10sin(i))+cos(y))
|
||||
end
|
||||
```
|
||||
|
||||

|
||||
|
||||
View the [full documentation](http://plots.readthedocs.io).
|
||||
View the [full documentation](http://docs.juliaplots.org/latest).
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
julia 0.4
|
||||
julia 0.7
|
||||
|
||||
RecipesBase
|
||||
Colors
|
||||
RecipesBase 0.2.3
|
||||
PlotUtils 0.4.1
|
||||
PlotThemes 0.1.3
|
||||
Reexport
|
||||
Compat
|
||||
FixedSizeArrays
|
||||
StaticArrays 0.5
|
||||
FixedPointNumbers 0.3
|
||||
Measures
|
||||
Showoff
|
||||
StatsBase 0.14.0
|
||||
JSON
|
||||
NaNMath
|
||||
Requires
|
||||
Contour
|
||||
GR 0.31.0
|
||||
|
||||
+18
-14
@@ -1,14 +1,14 @@
|
||||
environment:
|
||||
matrix:
|
||||
- JULIAVERSION: "julialang/bin/winnt/x86/0.3/julia-0.3-latest-win32.exe"
|
||||
- JULIAVERSION: "julialang/bin/winnt/x64/0.3/julia-0.3-latest-win64.exe"
|
||||
- JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe"
|
||||
- JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe"
|
||||
# - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.7/julia-0.7-latest-win32.exe"
|
||||
# - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.7/julia-0.7-latest-win64.exe"
|
||||
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
|
||||
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /release-.*/
|
||||
# matrix:
|
||||
# allow_failures:
|
||||
# - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
|
||||
# - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
@@ -17,18 +17,22 @@ notifications:
|
||||
on_build_status_changed: false
|
||||
|
||||
install:
|
||||
- ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12"
|
||||
# If there's a newer build queued for the same PR, cancel this one
|
||||
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
throw "There are newer queued builds for this pull request, failing early." }
|
||||
# Download most recent Julia Windows binary
|
||||
- ps: (new-object net.webclient).DownloadFile(
|
||||
$("http://s3.amazonaws.com/"+$env:JULIAVERSION),
|
||||
"C:\projects\julia-binary.exe")
|
||||
- ps: (new-object net.webclient).DownloadFile($env:JULIA_URL, "C:\projects\julia-binary.exe")
|
||||
# Run installer silently, output to C:\projects\julia
|
||||
- C:\projects\julia-binary.exe /S /D=C:\projects\julia
|
||||
|
||||
build_script:
|
||||
# Need to convert from shallow to complete for Pkg.clone to work
|
||||
- IF EXIST .git\shallow (git fetch --unshallow)
|
||||
- C:\projects\julia\bin\julia -e "versioninfo();
|
||||
Pkg.clone(pwd(), \"Plots\"); Pkg.build(\"Plots\")"
|
||||
- C:\projects\julia\bin\julia -e "versioninfo(); Pkg.clone(pwd(), \"Plots\"); Pkg.build(\"Plots\")"
|
||||
|
||||
test_script:
|
||||
- C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"Plots\")"
|
||||
# - C:\projects\julia\bin\julia -e "Pkg.test(\"Plots\")"
|
||||
- C:\projects\julia\bin\julia -e "include(Pkg.dir(\"Plots\", \"test\", \"travis_commands.jl\"))"
|
||||
|
||||
Vendored
+1
-1
@@ -3,6 +3,6 @@
|
||||
|
||||
local_fn = joinpath(dirname(@__FILE__), "plotly-latest.min.js")
|
||||
if !isfile(local_fn)
|
||||
info("Cannot find deps/plotly-latest.min.js... downloading latest version.")
|
||||
@info("Cannot find deps/plotly-latest.min.js... downloading latest version.")
|
||||
download("https://cdn.plot.ly/plotly-latest.min.js", local_fn)
|
||||
end
|
||||
|
||||
+159
-105
@@ -1,44 +1,41 @@
|
||||
|
||||
__precompile__()
|
||||
|
||||
module Plots
|
||||
|
||||
using Compat
|
||||
using Reexport
|
||||
@reexport using Colors
|
||||
# using Requires
|
||||
using FixedSizeArrays
|
||||
|
||||
import StaticArrays
|
||||
using StaticArrays.FixedSizeArrays
|
||||
using Dates, Printf, Statistics, Base64, LinearAlgebra
|
||||
import SparseArrays: findnz
|
||||
|
||||
@reexport using RecipesBase
|
||||
import RecipesBase: plot, plot!, animate
|
||||
using Base.Meta
|
||||
@reexport using PlotUtils
|
||||
@reexport using PlotThemes
|
||||
import Showoff
|
||||
import StatsBase
|
||||
import JSON
|
||||
|
||||
using Requires
|
||||
|
||||
export
|
||||
AbstractPlot,
|
||||
Plot,
|
||||
Subplot,
|
||||
AbstractLayout,
|
||||
GridLayout,
|
||||
grid,
|
||||
EmptyLayout,
|
||||
bbox,
|
||||
plotarea,
|
||||
@layout,
|
||||
AVec,
|
||||
AMat,
|
||||
KW,
|
||||
|
||||
wrap,
|
||||
set_theme,
|
||||
add_theme,
|
||||
theme,
|
||||
|
||||
plot,
|
||||
plot!,
|
||||
attr!,
|
||||
|
||||
current,
|
||||
default,
|
||||
with,
|
||||
|
||||
@userplot,
|
||||
@shorthands,
|
||||
twinx,
|
||||
|
||||
pie,
|
||||
pie!,
|
||||
@@ -58,58 +55,49 @@ export
|
||||
yflip!,
|
||||
xaxis!,
|
||||
yaxis!,
|
||||
xgrid!,
|
||||
ygrid!,
|
||||
|
||||
xlims,
|
||||
ylims,
|
||||
zlims,
|
||||
|
||||
savefig,
|
||||
png,
|
||||
gui,
|
||||
inline,
|
||||
closeall,
|
||||
|
||||
backend,
|
||||
backends,
|
||||
backend_name,
|
||||
backend_object,
|
||||
add_backend,
|
||||
aliases,
|
||||
dataframes,
|
||||
|
||||
Shape,
|
||||
text,
|
||||
font,
|
||||
Axis,
|
||||
stroke,
|
||||
brush,
|
||||
Surface,
|
||||
OHLC,
|
||||
arrow,
|
||||
|
||||
colorscheme,
|
||||
ColorScheme,
|
||||
ColorGradient,
|
||||
ColorVector,
|
||||
ColorWrapper,
|
||||
ColorFunction,
|
||||
ColorZFunction,
|
||||
getColor,
|
||||
getColorZ,
|
||||
|
||||
debugplots,
|
||||
|
||||
supported_args,
|
||||
supported_types,
|
||||
supported_styles,
|
||||
supported_markers,
|
||||
is_subplot_supported,
|
||||
Segments,
|
||||
Formatted,
|
||||
|
||||
Animation,
|
||||
frame,
|
||||
gif,
|
||||
mov,
|
||||
mp4,
|
||||
animate,
|
||||
@animate,
|
||||
@gif,
|
||||
|
||||
PlotRecipe,
|
||||
spy,
|
||||
arcdiagram,
|
||||
chorddiagram,
|
||||
|
||||
test_examples,
|
||||
iter_segments,
|
||||
coords,
|
||||
|
||||
translate,
|
||||
translate!,
|
||||
@@ -119,63 +107,93 @@ export
|
||||
P2,
|
||||
P3,
|
||||
BezierCurve,
|
||||
curve_points,
|
||||
directed_curve
|
||||
|
||||
plotattr
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
import NaNMath # define functions that ignores NaNs. To overcome the destructive effects of https://github.com/JuliaLang/julia/pull/12563
|
||||
ignorenan_minimum(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.minimum(x)
|
||||
ignorenan_minimum(x) = Base.minimum(x)
|
||||
ignorenan_maximum(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.maximum(x)
|
||||
ignorenan_maximum(x) = Base.maximum(x)
|
||||
ignorenan_mean(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.mean(x)
|
||||
ignorenan_mean(x) = Base.mean(x)
|
||||
ignorenan_extrema(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.extrema(x)
|
||||
ignorenan_extrema(x) = Base.extrema(x)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# to cater for block matrices, Base.transpose is recursive.
|
||||
# This makes it impossible to create row vectors of String and Symbol with the transpose operator.
|
||||
# This solves this issue, internally in Plots at least.
|
||||
|
||||
|
||||
# commented out on the insistence of the METADATA maintainers
|
||||
|
||||
#Base.transpose(x::Symbol) = x
|
||||
#Base.transpose(x::String) = x
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
import Measures
|
||||
module PlotMeasures
|
||||
import Measures
|
||||
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h
|
||||
typealias BBox Measures.Absolute2DBox
|
||||
export BBox, BoundingBox, mm, cm, inch, pt, px, pct, w, h
|
||||
const BBox = Measures.Absolute2DBox
|
||||
|
||||
# allow pixels and percentages
|
||||
const px = AbsoluteLength(0.254)
|
||||
const pct = Length{:pct, Float64}(1.0)
|
||||
export BBox, BoundingBox, mm, cm, inch, px, pct, pt, w, h
|
||||
end
|
||||
|
||||
using .PlotMeasures
|
||||
import .PlotMeasures: Length, AbsoluteLength, Measure, width, height
|
||||
# ---------------------------------------------------------
|
||||
|
||||
include("types.jl")
|
||||
include("utils.jl")
|
||||
include("colors.jl")
|
||||
include("components.jl")
|
||||
include("axes.jl")
|
||||
include("backends.jl")
|
||||
include("args.jl")
|
||||
include("themes.jl")
|
||||
include("plot.jl")
|
||||
include("series_args.jl")
|
||||
include("series_new.jl")
|
||||
include("pipeline.jl")
|
||||
include("series.jl")
|
||||
include("layouts.jl")
|
||||
include("subplots.jl")
|
||||
include("recipes.jl")
|
||||
include("animation.jl")
|
||||
include("output.jl")
|
||||
include("examples.jl")
|
||||
include("arg_desc.jl")
|
||||
|
||||
include("plotattr.jl")
|
||||
include("backends.jl")
|
||||
include("output.jl")
|
||||
include("init.jl")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# define and export shorthand plotting method definitions
|
||||
macro shorthands(funcname::Symbol)
|
||||
funcname2 = Symbol(funcname, "!")
|
||||
esc(quote
|
||||
export $funcname, $funcname2
|
||||
$funcname(args...; kw...) = plot(args...; kw..., seriestype = $(quot(funcname)))
|
||||
$funcname2(args...; kw...) = plot!(args...; kw..., seriestype = $(quot(funcname)))
|
||||
end)
|
||||
end
|
||||
|
||||
@shorthands scatter
|
||||
@shorthands bar
|
||||
@shorthands barh
|
||||
@shorthands histogram
|
||||
@shorthands barhist
|
||||
@shorthands stephist
|
||||
@shorthands scatterhist
|
||||
@shorthands histogram2d
|
||||
@shorthands density
|
||||
@shorthands heatmap
|
||||
@shorthands plots_heatmap
|
||||
@shorthands hexbin
|
||||
@shorthands sticks
|
||||
@shorthands hline
|
||||
@shorthands vline
|
||||
@shorthands hspan
|
||||
@shorthands vspan
|
||||
@shorthands ohlc
|
||||
@shorthands contour
|
||||
@shorthands contourf
|
||||
@shorthands contour3d
|
||||
@shorthands surface
|
||||
@shorthands wireframe
|
||||
@@ -186,69 +204,105 @@ end
|
||||
@shorthands quiver
|
||||
@shorthands curves
|
||||
|
||||
"Plot a pie diagram"
|
||||
pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
|
||||
pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
|
||||
|
||||
"Plot with seriestype :path3d"
|
||||
plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d)
|
||||
plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d)
|
||||
|
||||
|
||||
"Add title to an existing plot"
|
||||
title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
|
||||
|
||||
"Add xlabel to an existing plot"
|
||||
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
|
||||
|
||||
"Add ylabel to an existing plot"
|
||||
ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...)
|
||||
xlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; xlims = lims, kw...)
|
||||
ylims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; ylims = lims, kw...)
|
||||
zlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; zlims = lims, kw...)
|
||||
|
||||
"Set xlims for an existing plot"
|
||||
xlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; xlims = lims, kw...)
|
||||
|
||||
"Set ylims for an existing plot"
|
||||
ylims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; ylims = lims, kw...)
|
||||
|
||||
"Set zlims for an existing plot"
|
||||
zlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; zlims = lims, kw...)
|
||||
|
||||
xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...)
|
||||
ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...)
|
||||
zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...)
|
||||
xticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; xticks = v, kw...)
|
||||
yticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; yticks = v, kw...)
|
||||
xticks!{T<:Real,S<:AbstractString}(
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; xticks = (ticks,labels), kw...)
|
||||
yticks!{T<:Real,S<:AbstractString}(
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; yticks = (ticks,labels), kw...)
|
||||
|
||||
|
||||
"Set xticks for an existing plot"
|
||||
xticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; xticks = v, kw...)
|
||||
|
||||
"Set yticks for an existing plot"
|
||||
yticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; yticks = v, kw...)
|
||||
|
||||
xticks!(
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; xticks = (ticks,labels), kw...)
|
||||
yticks!(
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; yticks = (ticks,labels), kw...)
|
||||
|
||||
"Add annotations to an existing plot"
|
||||
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
|
||||
annotate!{T<:Tuple}(anns::AVec{T}; kw...) = plot!(; annotation = anns, kw...)
|
||||
annotate!(anns::AVec{T}; kw...) where {T<:Tuple} = plot!(; annotation = anns, kw...)
|
||||
|
||||
"Flip the current plots' x axis"
|
||||
xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...)
|
||||
|
||||
"Flip the current plots' y axis"
|
||||
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
|
||||
|
||||
"Specify x axis attributes for an existing plot"
|
||||
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
|
||||
|
||||
"Specify x axis attributes for an existing plot"
|
||||
yaxis!(args...; kw...) = plot!(; yaxis = args, kw...)
|
||||
xgrid!(args...; kw...) = plot!(; xgrid = args, kw...)
|
||||
ygrid!(args...; kw...) = plot!(; ygrid = args, kw...)
|
||||
|
||||
title!(plt::Plot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)
|
||||
xlabel!(plt::Plot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...)
|
||||
ylabel!(plt::Plot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...)
|
||||
xlims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; xlims = lims, kw...)
|
||||
ylims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; ylims = lims, kw...)
|
||||
zlims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; zlims = lims, kw...)
|
||||
xlims!(plt::Plot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...)
|
||||
ylims!(plt::Plot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...)
|
||||
zlims!(plt::Plot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...)
|
||||
xticks!{T<:Real}(plt::Plot, ticks::AVec{T}; kw...) = plot!(plt; xticks = ticks, kw...)
|
||||
yticks!{T<:Real}(plt::Plot, ticks::AVec{T}; kw...) = plot!(plt; yticks = ticks, kw...)
|
||||
xticks!{T<:Real,S<:AbstractString}(plt::Plot,
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; xticks = (ticks,labels), kw...)
|
||||
yticks!{T<:Real,S<:AbstractString}(plt::Plot,
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; yticks = (ticks,labels), kw...)
|
||||
annotate!(plt::Plot, anns...; kw...) = plot!(plt; annotation = anns, kw...)
|
||||
annotate!{T<:Tuple}(plt::Plot, anns::AVec{T}; kw...) = plot!(plt; annotation = anns, kw...)
|
||||
xflip!(plt::Plot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...)
|
||||
yflip!(plt::Plot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...)
|
||||
xaxis!(plt::Plot, args...; kw...) = plot!(plt; xaxis = args, kw...)
|
||||
yaxis!(plt::Plot, args...; kw...) = plot!(plt; yaxis = args, kw...)
|
||||
|
||||
let PlotOrSubplot = Union{Plot, Subplot}
|
||||
global title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)
|
||||
global xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...)
|
||||
global ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...)
|
||||
global xlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; xlims = lims, kw...)
|
||||
global ylims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; ylims = lims, kw...)
|
||||
global zlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; zlims = lims, kw...)
|
||||
global xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...)
|
||||
global ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...)
|
||||
global zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...)
|
||||
global xticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) where {T<:Real} = plot!(plt; xticks = ticks, kw...)
|
||||
global yticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) where {T<:Real} = plot!(plt; yticks = ticks, kw...)
|
||||
global xticks!(plt::PlotOrSubplot,
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; xticks = (ticks,labels), kw...)
|
||||
global yticks!(plt::PlotOrSubplot,
|
||||
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; yticks = (ticks,labels), kw...)
|
||||
global xgrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xgrid = args, kw...)
|
||||
global ygrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; ygrid = args, kw...)
|
||||
global annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...)
|
||||
global annotate!(plt::PlotOrSubplot, anns::AVec{T}; kw...) where {T<:Tuple} = plot!(plt; annotation = anns, kw...)
|
||||
global xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...)
|
||||
global yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...)
|
||||
global xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...)
|
||||
global yaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; yaxis = args, kw...)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const CURRENT_BACKEND = CurrentBackend(:none)
|
||||
|
||||
function __init__()
|
||||
setup_ijulia()
|
||||
setup_atom()
|
||||
|
||||
if haskey(ENV, "PLOTS_DEFAULTS")
|
||||
for (k,v) in eval(parse(ENV["PLOTS_DEFAULTS"]))
|
||||
default(k, v)
|
||||
# for compatibility with Requires.jl:
|
||||
@init begin
|
||||
if isdefined(Main, :PLOTS_DEFAULTS)
|
||||
if haskey(Main.PLOTS_DEFAULTS, :theme)
|
||||
theme(Main.PLOTS_DEFAULTS[:theme])
|
||||
end
|
||||
for (k,v) in Main.PLOTS_DEFAULTS
|
||||
k == :theme || default(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+94
-46
@@ -1,62 +1,108 @@
|
||||
|
||||
immutable Animation
|
||||
dir::Compat.ASCIIString
|
||||
frames::Vector{Compat.ASCIIString}
|
||||
"Represents an animation object"
|
||||
struct Animation
|
||||
dir::String
|
||||
frames::Vector{String}
|
||||
end
|
||||
|
||||
function Animation()
|
||||
tmpdir = convert(Compat.ASCIIString, mktempdir())
|
||||
Animation(tmpdir, Compat.ASCIIString[])
|
||||
tmpdir = convert(String, mktempdir())
|
||||
Animation(tmpdir, String[])
|
||||
end
|
||||
|
||||
function frame{P<:AbstractPlot}(anim::Animation, plt::P=current())
|
||||
i = length(anim.frames) + 1
|
||||
filename = @sprintf("%06d.png", i)
|
||||
png(plt, joinpath(anim.dir, filename))
|
||||
push!(anim.frames, filename)
|
||||
"""
|
||||
frame(animation[, plot])
|
||||
|
||||
Add a plot (the current plot if not specified) to an existing animation
|
||||
"""
|
||||
function frame(anim::Animation, plt::P=current()) where P<:AbstractPlot
|
||||
i = length(anim.frames) + 1
|
||||
filename = @sprintf("%06d.png", i)
|
||||
png(plt, joinpath(anim.dir, filename))
|
||||
push!(anim.frames, filename)
|
||||
end
|
||||
|
||||
giffn() = (isijulia() ? "tmp.gif" : tempname()*".gif")
|
||||
movfn() = (isijulia() ? "tmp.mov" : tempname()*".mov")
|
||||
mp4fn() = (isijulia() ? "tmp.mp4" : tempname()*".mp4")
|
||||
|
||||
mutable struct FrameIterator
|
||||
itr
|
||||
every::Int
|
||||
kw
|
||||
end
|
||||
FrameIterator(itr; every=1, kw...) = FrameIterator(itr, every, kw)
|
||||
|
||||
"""
|
||||
Animate from an iterator which returns the plot args each iteration.
|
||||
"""
|
||||
function animate(fitr::FrameIterator, fn = giffn(); kw...)
|
||||
anim = Animation()
|
||||
for (i, plotargs) in enumerate(fitr.itr)
|
||||
if mod1(i, fitr.every) == 1
|
||||
plot(wraptuple(plotargs)...; fitr.kw...)
|
||||
frame(anim)
|
||||
end
|
||||
end
|
||||
gif(anim, fn; kw...)
|
||||
end
|
||||
|
||||
# most things will implement this
|
||||
function animate(obj, fn = giffn(); every=1, fps=20, loop=0, kw...)
|
||||
animate(FrameIterator(obj, every, kw), fn; fps=fps, loop=loop)
|
||||
end
|
||||
|
||||
# -----------------------------------------------
|
||||
|
||||
"Wraps the location of an animated gif so that it can be displayed"
|
||||
immutable AnimatedGif
|
||||
filename::Compat.ASCIIString
|
||||
struct AnimatedGif
|
||||
filename::String
|
||||
end
|
||||
|
||||
function gif(anim::Animation, fn = tempname()*".gif"; fps::Integer = 20)
|
||||
fn = abspath(fn)
|
||||
file_extension(fn) = Base.Filesystem.splitext(fn)[2][2:end]
|
||||
|
||||
try
|
||||
gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim.dir, fn; kw...)
|
||||
mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
|
||||
mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
|
||||
|
||||
# high quality
|
||||
speed = round(Int, 100 / fps)
|
||||
file = joinpath(Pkg.dir("ImageMagick"), "deps","deps.jl")
|
||||
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH")
|
||||
include(file)
|
||||
|
||||
function buildanimation(animdir::AbstractString, fn::AbstractString,
|
||||
is_animated_gif::Bool=true;
|
||||
fps::Integer = 20, loop::Integer = 0,
|
||||
variable_palette::Bool=false,
|
||||
show_msg::Bool=true)
|
||||
fn = abspath(fn)
|
||||
|
||||
if is_animated_gif
|
||||
if variable_palette
|
||||
# generate a colorpalette for each frame for highest quality, but larger filesize
|
||||
palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1"
|
||||
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
|
||||
else
|
||||
# generate a colorpalette first so ffmpeg does not have to guess it
|
||||
run(`ffmpeg -v 0 -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
|
||||
# then apply the palette to get better results
|
||||
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
|
||||
end
|
||||
else
|
||||
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
|
||||
end
|
||||
prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
|
||||
run(`$(joinpath(prefix, "convert")) -delay $speed -loop 0 $(joinpath(anim.dir, "*.png")) -alpha off $fn`)
|
||||
|
||||
catch err
|
||||
warn("""Tried to create gif using convert (ImageMagick), but got error: $err
|
||||
ImageMagick can be installed by executing `Pkg.add("ImageMagick")`
|
||||
Will try ffmpeg, but it's lower quality...)""")
|
||||
|
||||
# low quality
|
||||
run(`ffmpeg -v 0 -framerate $fps -i $(anim.dir)/%06d.png -y $fn`)
|
||||
# run(`ffmpeg -v warning -i "fps=$fps,scale=320:-1:flags=lanczos"`)
|
||||
end
|
||||
|
||||
info("Saved animation to ", fn)
|
||||
AnimatedGif(fn)
|
||||
show_msg && @info("Saved animation to ", fn)
|
||||
AnimatedGif(fn)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# write out html to view the gif... note the rand call which is a hack so the image doesn't get cached
|
||||
function Base.writemime(io::IO, ::MIME"text/html", agif::AnimatedGif)
|
||||
write(io, "<img src=\"$(relpath(agif.filename))?$(rand())>\" />")
|
||||
function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
|
||||
ext = file_extension(agif.filename)
|
||||
write(io, if ext == "gif"
|
||||
"<img src=\"$(relpath(agif.filename))?$(rand())>\" />"
|
||||
elseif ext in ("mov", "mp4")
|
||||
"<video controls><source src=\"$(relpath(agif.filename))?$(rand())>\" type=\"video/$ext\"></video>"
|
||||
else
|
||||
error("Cannot show animation with extension $ext: $agif")
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -70,6 +116,7 @@ function _animate(forloop::Expr, args...; callgif = false)
|
||||
# add the call to frame to the end of each iteration
|
||||
animsym = gensym("anim")
|
||||
countersym = gensym("counter")
|
||||
freqassert = :()
|
||||
block = forloop.args[2]
|
||||
|
||||
# create filter
|
||||
@@ -82,7 +129,7 @@ function _animate(forloop::Expr, args...; callgif = false)
|
||||
# filter every `freq` frames (starting with the first frame)
|
||||
@assert n == 2
|
||||
freq = args[2]
|
||||
@assert isa(freq, Integer) && freq > 0
|
||||
freqassert = :(@assert isa($freq, Integer) && $freq > 0)
|
||||
:(mod1($countersym, $freq) == 1)
|
||||
|
||||
elseif args[1] == :when
|
||||
@@ -95,15 +142,16 @@ function _animate(forloop::Expr, args...; callgif = false)
|
||||
end
|
||||
|
||||
push!(block.args, :(if $filterexpr; frame($animsym); end))
|
||||
push!(block.args, :($countersym += 1))
|
||||
push!(block.args, :(global $countersym += 1))
|
||||
|
||||
# add a final call to `gif(anim)`?
|
||||
retval = callgif ? :(gif($animsym)) : animsym
|
||||
|
||||
# full expression:
|
||||
esc(quote
|
||||
$freqassert # if filtering, check frequency is an Integer > 0
|
||||
$animsym = Animation() # init animation object
|
||||
$countersym = 1 # init iteration counter
|
||||
global $countersym = 1 # init iteration counter
|
||||
$forloop # for loop, saving a frame after each iteration
|
||||
$retval # return the animation object, or the gif
|
||||
end)
|
||||
@@ -122,7 +170,7 @@ Example:
|
||||
```
|
||||
"""
|
||||
macro gif(forloop::Expr, args...)
|
||||
_animate(forloop, args...; callgif = true)
|
||||
_animate(forloop, args...; callgif = true)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -131,13 +179,13 @@ Collect one frame per for-block iteration and return an `Animation` object.
|
||||
Example:
|
||||
|
||||
```
|
||||
p = plot(1)
|
||||
anim = @animate for x=0:0.1:5
|
||||
p = plot(1)
|
||||
anim = @animate for x=0:0.1:5
|
||||
push!(p, 1, sin(x))
|
||||
end
|
||||
gif(anim)
|
||||
end
|
||||
gif(anim)
|
||||
```
|
||||
"""
|
||||
macro animate(forloop::Expr, args...)
|
||||
_animate(forloop, args...)
|
||||
_animate(forloop, args...)
|
||||
end
|
||||
|
||||
+55
-11
@@ -19,9 +19,9 @@ const _arg_desc = KW(
|
||||
:markersize => "Number or AbstractVector. Size (radius pixels) of the markers.",
|
||||
:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)",
|
||||
:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)",
|
||||
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:seriescolor`.",
|
||||
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.",
|
||||
:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.",
|
||||
:bins => "Integer, NTuple{2,Integer}, AbstractVector. For histogram-types, defines the number of bins, or the edges, of the histogram.",
|
||||
:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `range(min(x), stop = extrema(x), length = 25)`",
|
||||
:smooth => "Bool. Add a regression line?",
|
||||
:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.",
|
||||
:x => "Various. Input data. First Dimension",
|
||||
@@ -29,6 +29,7 @@ const _arg_desc = KW(
|
||||
:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.",
|
||||
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
|
||||
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
|
||||
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
|
||||
:levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.",
|
||||
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
|
||||
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
|
||||
@@ -39,9 +40,10 @@ const _arg_desc = KW(
|
||||
:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.",
|
||||
:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.",
|
||||
:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.",
|
||||
:normalize => "Bool. Should normalize histogram types? Trying for area == 1.",
|
||||
:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).",
|
||||
:weights => "AbstractVector. Used in histogram types for weighted counts.",
|
||||
:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.",
|
||||
:contour_labels => "Bool. Show labels at the contour lines?",
|
||||
:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.",
|
||||
:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.",
|
||||
:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.",
|
||||
@@ -62,46 +64,88 @@ const _arg_desc = KW(
|
||||
:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).",
|
||||
:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.",
|
||||
:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots",
|
||||
:dpi => "Number. Dots Per Inch of output figures",
|
||||
:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.",
|
||||
:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.",
|
||||
:extra_kwargs => "KW (Dict{Symbol,Any}). Pass a map of extra keyword args which may be specific to a backend.",
|
||||
:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides",
|
||||
|
||||
# subplot args
|
||||
:title => "String. Subplot title.",
|
||||
:title_location => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
|
||||
:titlefont => "Font. Font of subplot title.",
|
||||
:titlefontfamily => "String or Symbol. Font family of subplot title.",
|
||||
:titlefontsize => "Integer. Font pointsize of subplot title.",
|
||||
:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center",
|
||||
:titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center",
|
||||
:titlefontrotation => "Real. Font rotation of subplot title",
|
||||
:titlefontcolor => "Color Type. Font color of subplot title",
|
||||
:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.",
|
||||
:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.",
|
||||
:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).",
|
||||
:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.",
|
||||
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.",
|
||||
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
|
||||
:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.",
|
||||
:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
|
||||
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)",
|
||||
:legendfontfamily => "String or Symbol. Font family of legend entries.",
|
||||
:legendfontsize => "Integer. Font pointsize of legend entries.",
|
||||
:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center",
|
||||
:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center",
|
||||
:legendfontrotation => "Real. Font rotation of legend entries",
|
||||
:legendfontcolor => "Color Type. Font color of legend entries",
|
||||
:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)",
|
||||
:clims => "`:auto` or NTuple{2,Number}. Fixes the limits of the colorbar.",
|
||||
:legendfont => "Font. Font of legend items.",
|
||||
:grid => "Bool. Show the grid lines?",
|
||||
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.",
|
||||
:projection => "Symbol or String. '3d' or 'polar'",
|
||||
:aspect_ratio => "Symbol (:equal) or Number (width to height ratio of plot area).",
|
||||
:aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `apect_ratio` x-units.",
|
||||
:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.",
|
||||
:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.",
|
||||
:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.",
|
||||
:right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.",
|
||||
:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.",
|
||||
:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.",
|
||||
:colorbar_title => "String. Title of colorbar.",
|
||||
:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)",
|
||||
:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots",
|
||||
|
||||
# axis args
|
||||
:guide => "String. Axis guide (label).",
|
||||
:lims => "NTuple{2,Number}. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example).",
|
||||
:lims => "NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0]",
|
||||
:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`",
|
||||
:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`",
|
||||
:rotation => "Number. Degrees rotation of tick labels.",
|
||||
:flip => "Bool. Should we flip (reverse) the axis?",
|
||||
:tickfont => "Font. Font of axis tick labels.",
|
||||
:guidefont => "Font. Font of axis guide (label).",
|
||||
:formatter => "Function, :scientific, or :auto. A method which converts a number to a string for tick labeling.",
|
||||
:tickfontfamily => "String or Symbol. Font family of tick labels.",
|
||||
:tickfontsize => "Integer. Font pointsize of tick labels.",
|
||||
:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center",
|
||||
:tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center",
|
||||
:tickfontrotation => "Real. Font rotation of tick labels",
|
||||
:tickfontcolor => "Color Type. Font color of tick labels",
|
||||
:guidefontfamily => "String or Symbol. Font family of axes guides.",
|
||||
:guidefontsize => "Integer. Font pointsize of axes guides.",
|
||||
:guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center",
|
||||
:guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center",
|
||||
:guidefontrotation => "Real. Font rotation of axes guides",
|
||||
:guidefontcolor => "Color Type. Font color of axes guides",
|
||||
:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.",
|
||||
:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).",
|
||||
:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.",
|
||||
:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).",
|
||||
|
||||
:mirror => "Bool. Switch the side of the tick labels (right or top).",
|
||||
:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`",
|
||||
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
|
||||
:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.",
|
||||
:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)",
|
||||
:gridlinewidth => "Number. Width of the grid lines (in pixels)",
|
||||
:foreground_color_minor_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of minor grid lines.",
|
||||
:minorgrid => "Bool. Adds minor grid lines and ticks to the plot. Set minorticks to change number of gridlines",
|
||||
:minorticks => "Integer. Intervals to divide the gap between major ticks into",
|
||||
:minorgridalpha => "Number in [0,1]. The alpha/opacity override for the minorgrid lines.",
|
||||
:minorgridstyle => "Symbol. Style of the minor grid lines. Choose from $(_allStyles)",
|
||||
:minorgridlinewidth => "Number. Width of the minor grid lines (in pixels)",
|
||||
:tick_direction => "Symbol. Direction of the ticks. `:in` or `:out`",
|
||||
:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`",
|
||||
:widen => "Bool. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Defaults to `true`.",
|
||||
)
|
||||
|
||||
+792
-194
File diff suppressed because it is too large
Load Diff
+446
-48
@@ -23,13 +23,13 @@ function Axis(sp::Subplot, letter::Symbol, args...; kw...)
|
||||
lk = Symbol(letter, k)
|
||||
lv = _axis_defaults_byletter[lk]
|
||||
d[k] = (lv == :match ? v : lv)
|
||||
end
|
||||
end
|
||||
|
||||
# merge!(d, _axis_defaults)
|
||||
d[:discrete_values] = []
|
||||
|
||||
# update the defaults
|
||||
update!(Axis(sp, d), args...; kw...)
|
||||
attr!(Axis([sp], d), args...; kw...)
|
||||
end
|
||||
|
||||
function get_axis(sp::Subplot, letter::Symbol)
|
||||
@@ -38,7 +38,7 @@ function get_axis(sp::Subplot, letter::Symbol)
|
||||
sp.attr[axissym]
|
||||
else
|
||||
sp.attr[axissym] = Axis(sp, letter)
|
||||
end
|
||||
end::Axis
|
||||
end
|
||||
|
||||
function process_axis_arg!(d::KW, arg, letter = "")
|
||||
@@ -70,17 +70,23 @@ function process_axis_arg!(d::KW, arg, letter = "")
|
||||
elseif arg == nothing
|
||||
d[Symbol(letter,:ticks)] = []
|
||||
|
||||
elseif T <: Bool || arg in _allShowaxisArgs
|
||||
d[Symbol(letter,:showaxis)] = showaxis(arg, letter)
|
||||
|
||||
elseif typeof(arg) <: Number
|
||||
d[Symbol(letter,:rotation)] = arg
|
||||
|
||||
else
|
||||
warn("Skipped $(letter)axis arg $arg")
|
||||
elseif typeof(arg) <: Function
|
||||
d[Symbol(letter,:formatter)] = arg
|
||||
|
||||
elseif !handleColors!(d, arg, Symbol(letter, :foreground_color_axis))
|
||||
@warn("Skipped $(letter)axis arg $arg")
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# update an Axis object with magic args and keywords
|
||||
function update!(axis::Axis, args...; kw...)
|
||||
function attr!(axis::Axis, args...; kw...)
|
||||
# first process args
|
||||
d = axis.d
|
||||
for arg in args
|
||||
@@ -115,37 +121,234 @@ Base.show(io::IO, axis::Axis) = dumpdict(axis.d, "Axis", true)
|
||||
# Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k)
|
||||
Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...)
|
||||
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k)
|
||||
Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
|
||||
ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
|
||||
|
||||
# get discrete ticks, or not
|
||||
function get_ticks(axis::Axis)
|
||||
ticks = axis[:ticks]
|
||||
dvals = axis[:discrete_values]
|
||||
if !isempty(dvals) && ticks == :auto
|
||||
axis[:continuous_values], dvals
|
||||
else
|
||||
ticks
|
||||
|
||||
const _scale_funcs = Dict{Symbol,Function}(
|
||||
:log10 => log10,
|
||||
:log2 => log2,
|
||||
:ln => log,
|
||||
)
|
||||
const _inv_scale_funcs = Dict{Symbol,Function}(
|
||||
:log10 => exp10,
|
||||
:log2 => exp2,
|
||||
:ln => exp,
|
||||
)
|
||||
|
||||
# const _label_func = Dict{Symbol,Function}(
|
||||
# :log10 => x -> "10^$x",
|
||||
# :log2 => x -> "2^$x",
|
||||
# :ln => x -> "e^$x",
|
||||
# )
|
||||
|
||||
const _label_func = Dict{Symbol,Function}(
|
||||
:log10 => x -> "10^$x",
|
||||
:log2 => x -> "2^$x",
|
||||
:ln => x -> "e^$x",
|
||||
)
|
||||
|
||||
|
||||
scalefunc(scale::Symbol) = x -> get(_scale_funcs, scale, identity)(Float64(x))
|
||||
invscalefunc(scale::Symbol) = x -> get(_inv_scale_funcs, scale, identity)(Float64(x))
|
||||
labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string)
|
||||
|
||||
function optimal_ticks_and_labels(axis::Axis, ticks = nothing)
|
||||
amin,amax = axis_limits(axis)
|
||||
|
||||
# scale the limits
|
||||
scale = axis[:scale]
|
||||
sf = scalefunc(scale)
|
||||
|
||||
# If the axis input was a Date or DateTime use a special logic to find
|
||||
# "round" Date(Time)s as ticks
|
||||
# This bypasses the rest of optimal_ticks_and_labels, because
|
||||
# optimize_datetime_ticks returns ticks AND labels: the label format (Date
|
||||
# or DateTime) is chosen based on the time span between amin and amax
|
||||
# rather than on the input format
|
||||
# TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime
|
||||
if ticks == nothing && scale == :identity
|
||||
if axis[:formatter] == dateformatter
|
||||
# optimize_datetime_ticks returns ticks and labels(!) based on
|
||||
# integers/floats corresponding to the DateTime type. Thus, the axes
|
||||
# limits, which resulted from converting the Date type to integers,
|
||||
# are converted to 'DateTime integers' (actually floats) before
|
||||
# being passed to optimize_datetime_ticks.
|
||||
# (convert(Int, convert(DateTime, convert(Date, i))) == 87600000*i)
|
||||
ticks, labels = optimize_datetime_ticks(864e5 * amin, 864e5 * amax;
|
||||
k_min = 2, k_max = 4)
|
||||
# Now the ticks are converted back to floats corresponding to Dates.
|
||||
return ticks / 864e5, labels
|
||||
elseif axis[:formatter] == datetimeformatter
|
||||
return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4)
|
||||
end
|
||||
end
|
||||
|
||||
# get a list of well-laid-out ticks
|
||||
if ticks == nothing
|
||||
scaled_ticks = optimize_ticks(
|
||||
sf(amin),
|
||||
sf(amax);
|
||||
k_min = 4, # minimum number of ticks
|
||||
k_max = 8, # maximum number of ticks
|
||||
)[1]
|
||||
elseif typeof(ticks) <: Int
|
||||
scaled_ticks, viewmin, viewmax = optimize_ticks(
|
||||
sf(amin),
|
||||
sf(amax);
|
||||
k_min = ticks, # minimum number of ticks
|
||||
k_max = ticks, # maximum number of ticks
|
||||
k_ideal = ticks,
|
||||
# `strict_span = false` rewards cases where the span of the
|
||||
# chosen ticks is not too much bigger than amin - amax:
|
||||
strict_span = false,
|
||||
)
|
||||
axis[:lims] = map(invscalefunc(scale), (viewmin, viewmax))
|
||||
else
|
||||
scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks)))
|
||||
end
|
||||
unscaled_ticks = map(invscalefunc(scale), scaled_ticks)
|
||||
|
||||
labels = if any(isfinite, unscaled_ticks)
|
||||
formatter = axis[:formatter]
|
||||
if formatter == :auto
|
||||
# the default behavior is to make strings of the scaled values and then apply the labelfunc
|
||||
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :auto))
|
||||
elseif formatter == :plain
|
||||
# Leave the numbers in plain format
|
||||
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :plain))
|
||||
elseif formatter == :scientific
|
||||
Showoff.showoff(unscaled_ticks, :scientific)
|
||||
else
|
||||
# there was an override for the formatter... use that on the unscaled ticks
|
||||
map(formatter, unscaled_ticks)
|
||||
# if the formatter left us with numbers, still apply the default formatter
|
||||
# However it leave us with the problem of unicode number decoding by the backend
|
||||
# if eltype(unscaled_ticks) <: Number
|
||||
# Showoff.showoff(unscaled_ticks, :auto)
|
||||
# end
|
||||
end
|
||||
else
|
||||
# no finite ticks to show...
|
||||
String[]
|
||||
end
|
||||
|
||||
# @show unscaled_ticks labels
|
||||
# labels = Showoff.showoff(unscaled_ticks, scale == :log10 ? :scientific : :auto)
|
||||
unscaled_ticks, labels
|
||||
end
|
||||
|
||||
# return (continuous_values, discrete_values) for the ticks on this axis
|
||||
function get_ticks(axis::Axis)
|
||||
ticks = _transform_ticks(axis[:ticks])
|
||||
ticks in (nothing, false) && return nothing
|
||||
|
||||
# treat :native ticks as :auto
|
||||
ticks = ticks == :native ? :auto : ticks
|
||||
|
||||
dvals = axis[:discrete_values]
|
||||
cv, dv = if typeof(ticks) <: Symbol
|
||||
if !isempty(dvals)
|
||||
# discrete ticks...
|
||||
n = length(dvals)
|
||||
rng = if ticks == :auto
|
||||
Int[round(Int,i) for i in range(1, stop=n, length=15)]
|
||||
else # if ticks == :all
|
||||
1:n
|
||||
end
|
||||
axis[:continuous_values][rng], dvals[rng]
|
||||
elseif ispolar(axis.sps[1]) && axis[:letter] == :x
|
||||
#force theta axis to be full circle
|
||||
(collect(0:pi/4:7pi/4), string.(0:45:315))
|
||||
else
|
||||
# compute optimal ticks and labels
|
||||
optimal_ticks_and_labels(axis)
|
||||
end
|
||||
elseif typeof(ticks) <: Union{AVec, Int}
|
||||
if !isempty(dvals) && typeof(ticks) <: Int
|
||||
rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)]
|
||||
axis[:continuous_values][rng], dvals[rng]
|
||||
else
|
||||
# override ticks, but get the labels
|
||||
optimal_ticks_and_labels(axis, ticks)
|
||||
end
|
||||
elseif typeof(ticks) <: NTuple{2, Any}
|
||||
# assuming we're passed (ticks, labels)
|
||||
ticks
|
||||
else
|
||||
error("Unknown ticks type in get_ticks: $(typeof(ticks))")
|
||||
end
|
||||
# @show ticks dvals cv dv
|
||||
|
||||
return cv, dv
|
||||
end
|
||||
|
||||
_transform_ticks(ticks) = ticks
|
||||
_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks)
|
||||
_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2])
|
||||
|
||||
function get_minor_ticks(axis,ticks)
|
||||
axis[:minorticks] in (nothing, false) && !axis[:minorgrid] && return nothing
|
||||
ticks = ticks[1]
|
||||
length(ticks) < 2 && return nothing
|
||||
|
||||
amin, amax = axis_limits(axis)
|
||||
#Add one phantom tick either side of the ticks to ensure minor ticks extend to the axis limits
|
||||
if length(ticks) > 2
|
||||
ratio = (ticks[3] - ticks[2])/(ticks[2] - ticks[1])
|
||||
elseif axis[:scale] == :none
|
||||
ratio = 1
|
||||
else
|
||||
return nothing
|
||||
end
|
||||
first_step = ticks[2] - ticks[1]
|
||||
last_step = ticks[end] - ticks[end-1]
|
||||
ticks = [ticks[1] - first_step/ratio; ticks; ticks[end] + last_step*ratio]
|
||||
|
||||
#Default to 5 intervals between major ticks
|
||||
n = typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 ? axis[:minorticks] : 5
|
||||
minorticks = typeof(ticks[1])[]
|
||||
for (i,hi) in enumerate(ticks[2:end])
|
||||
lo = ticks[i]
|
||||
append!(minorticks,collect(lo + (hi-lo)/n :(hi-lo)/n: hi - (hi-lo)/2n))
|
||||
end
|
||||
minorticks[amin .<= minorticks .<= amax]
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
function reset_extrema!(sp::Subplot)
|
||||
for asym in (:x,:y,:z)
|
||||
sp[Symbol(asym,:axis)][:extrema] = Extrema()
|
||||
end
|
||||
for series in sp.series_list
|
||||
expand_extrema!(sp, series.d)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function expand_extrema!(ex::Extrema, v::Number)
|
||||
ex.emin = min(v, ex.emin)
|
||||
ex.emax = max(v, ex.emax)
|
||||
ex.emin = isfinite(v) ? min(v, ex.emin) : ex.emin
|
||||
ex.emax = isfinite(v) ? max(v, ex.emax) : ex.emax
|
||||
ex
|
||||
end
|
||||
|
||||
function expand_extrema!(axis::Axis, v::Number)
|
||||
expand_extrema!(axis[:extrema], v)
|
||||
end
|
||||
function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX})
|
||||
|
||||
# these shouldn't impact the extrema
|
||||
expand_extrema!(axis::Axis, ::Nothing) = axis[:extrema]
|
||||
expand_extrema!(axis::Axis, ::Bool) = axis[:extrema]
|
||||
|
||||
|
||||
function expand_extrema!(axis::Axis, v::Tuple{MIN,MAX}) where {MIN<:Number,MAX<:Number}
|
||||
ex = axis[:extrema]
|
||||
ex.emin = min(v[1], ex.emin)
|
||||
ex.emax = max(v[2], ex.emax)
|
||||
ex.emin = isfinite(v[1]) ? min(v[1], ex.emin) : ex.emin
|
||||
ex.emax = isfinite(v[2]) ? max(v[2], ex.emax) : ex.emax
|
||||
ex
|
||||
end
|
||||
function expand_extrema!{N<:Number}(axis::Axis, v::AVec{N})
|
||||
function expand_extrema!(axis::Axis, v::AVec{N}) where N<:Number
|
||||
ex = axis[:extrema]
|
||||
for vi in v
|
||||
expand_extrema!(ex, vi)
|
||||
@@ -155,20 +358,36 @@ end
|
||||
|
||||
|
||||
function expand_extrema!(sp::Subplot, d::KW)
|
||||
vert = isvertical(d)
|
||||
|
||||
# first expand for the data
|
||||
for letter in (:x, :y, :z)
|
||||
data = d[letter]
|
||||
axis = sp.attr[Symbol(letter, "axis")]
|
||||
if eltype(data) <: Number
|
||||
expand_extrema!(axis, data)
|
||||
elseif isa(data, Surface) && eltype(data.surf) <: Number
|
||||
data = d[if vert
|
||||
letter
|
||||
else
|
||||
letter == :x ? :y : letter == :y ? :x : :z
|
||||
end]
|
||||
if letter != :z && d[:seriestype] == :straightline && any(series[:seriestype] != :straightline for series in series_list(sp)) && data[1] != data[2]
|
||||
data = [NaN]
|
||||
end
|
||||
axis = sp[Symbol(letter, "axis")]
|
||||
|
||||
if isa(data, Volume)
|
||||
expand_extrema!(sp[:xaxis], data.x_extents)
|
||||
expand_extrema!(sp[:yaxis], data.y_extents)
|
||||
expand_extrema!(sp[:zaxis], data.z_extents)
|
||||
elseif eltype(data) <: Number || (isa(data, Surface) && all(di -> isa(di, Number), data.surf))
|
||||
if !(eltype(data) <: Number)
|
||||
# huh... must have been a mis-typed surface? lets swap it out
|
||||
data = d[letter] = Surface(Matrix{Float64}(data.surf))
|
||||
end
|
||||
expand_extrema!(axis, data)
|
||||
elseif data != nothing
|
||||
# TODO: need more here... gotta track the discrete reference value
|
||||
# as well as any coord offset (think of boxplot shape coords... they all
|
||||
# correspond to the same x-value)
|
||||
# @show letter,eltype(data),typeof(data)
|
||||
d[letter], d[Symbol(letter,"_discrete_indices")] = discrete_value!(axis, data)
|
||||
expand_extrema!(axis, d[letter])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -179,12 +398,11 @@ function expand_extrema!(sp::Subplot, d::KW)
|
||||
# end
|
||||
|
||||
# expand for fillrange
|
||||
vert = isvertical(d)
|
||||
fr = d[:fillrange]
|
||||
if fr == nothing && d[:seriestype] == :bar
|
||||
fr = 0.0
|
||||
end
|
||||
if fr != nothing
|
||||
if fr != nothing && !all3D(d)
|
||||
axis = sp.attr[vert ? :yaxis : :xaxis]
|
||||
if typeof(fr) <: Tuple
|
||||
for fri in fr
|
||||
@@ -202,15 +420,22 @@ function expand_extrema!(sp::Subplot, d::KW)
|
||||
|
||||
bw = d[:bar_width]
|
||||
if bw == nothing
|
||||
bw = d[:bar_width] = mean(diff(data))
|
||||
bw = d[:bar_width] = _bar_width * ignorenan_minimum(filter(x->x>0,diff(sort(data))))
|
||||
end
|
||||
# @show data bw
|
||||
|
||||
axis = sp.attr[Symbol(dsym, :axis)]
|
||||
expand_extrema!(axis, maximum(data) + 0.5maximum(bw))
|
||||
expand_extrema!(axis, minimum(data) - 0.5minimum(bw))
|
||||
expand_extrema!(axis, ignorenan_maximum(data) + 0.5maximum(bw))
|
||||
expand_extrema!(axis, ignorenan_minimum(data) - 0.5minimum(bw))
|
||||
end
|
||||
|
||||
# expand for heatmaps
|
||||
if d[:seriestype] == :heatmap
|
||||
for letter in (:x, :y)
|
||||
data = d[letter]
|
||||
axis = sp[Symbol(letter, "axis")]
|
||||
scale = get(d, Symbol(letter, "scale"), :identity)
|
||||
expand_extrema!(axis, heatmap_edges(data, scale))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax)
|
||||
@@ -221,27 +446,38 @@ end
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# push the limits out slightly
|
||||
function widen(lmin, lmax)
|
||||
span = lmax - lmin
|
||||
# eps = max(1e-16, min(1e-2span, 1e-10))
|
||||
eps = max(1e-16, 0.03span)
|
||||
lmin-eps, lmax+eps
|
||||
function widen(lmin, lmax, scale = :identity)
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
span = f(lmax) - f(lmin)
|
||||
# eps = NaNMath.max(1e-16, min(1e-2span, 1e-10))
|
||||
eps = NaNMath.max(1e-16, 0.03span)
|
||||
invf(f(lmin)-eps), invf(f(lmax)+eps)
|
||||
end
|
||||
|
||||
# figure out if widening is a good idea. if there's a scale set it's too tricky,
|
||||
# so lazy out and don't widen
|
||||
# figure out if widening is a good idea.
|
||||
const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d)
|
||||
|
||||
function default_should_widen(axis::Axis)
|
||||
should_widen = false
|
||||
if axis[:scale] == :identity
|
||||
for series in series_list(axis.sp)
|
||||
if series.d[:seriestype] in (:scatter,) || series.d[:markershape] != :none
|
||||
should_widen = true
|
||||
if !is_2tuple(axis[:lims])
|
||||
for sp in axis.sps
|
||||
for series in series_list(sp)
|
||||
if series.d[:seriestype] in _widen_seriestypes
|
||||
should_widen = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
should_widen
|
||||
end
|
||||
|
||||
function round_limits(amin,amax)
|
||||
scale = 10^(1-round(log10(amax - amin)))
|
||||
amin = floor(amin*scale)/scale
|
||||
amax = ceil(amax*scale)/scale
|
||||
amin, amax
|
||||
end
|
||||
|
||||
# using the axis extrema and limit overrides, return the min/max value for this axis
|
||||
function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis))
|
||||
ex = axis[:extrema]
|
||||
@@ -258,8 +494,22 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)
|
||||
if amax <= amin && isfinite(amin)
|
||||
amax = amin + 1.0
|
||||
end
|
||||
if should_widen
|
||||
widen(amin, amax)
|
||||
if !isfinite(amin) && !isfinite(amax)
|
||||
amin, amax = 0.0, 1.0
|
||||
end
|
||||
if ispolar(axis.sps[1])
|
||||
if axis[:letter] == :x
|
||||
amin, amax = 0, 2pi
|
||||
elseif lims == :auto
|
||||
#widen max radius so ticks dont overlap with theta axis
|
||||
amin, amax + 0.1 * abs(amax - amin)
|
||||
else
|
||||
amin, amax
|
||||
end
|
||||
elseif should_widen && axis[:widen]
|
||||
widen(amin, amax, axis[:scale])
|
||||
elseif lims == :round
|
||||
round_limits(amin,amax)
|
||||
else
|
||||
amin, amax
|
||||
end
|
||||
@@ -275,7 +525,7 @@ function discrete_value!(axis::Axis, dv)
|
||||
# @show axis[:discrete_map], axis[:discrete_values], dv
|
||||
if cv_idx == -1
|
||||
ex = axis[:extrema]
|
||||
cv = max(0.5, ex.emax + 1.0)
|
||||
cv = NaNMath.max(0.5, ex.emax + 1.0)
|
||||
expand_extrema!(axis, cv)
|
||||
push!(axis[:discrete_values], dv)
|
||||
push!(axis[:continuous_values], cv)
|
||||
@@ -330,3 +580,151 @@ function pie_labels(sp::Subplot, series::Series)
|
||||
d[:x]
|
||||
end
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# compute the line segments which should be drawn for this axis
|
||||
function axis_drawing_info(sp::Subplot)
|
||||
xaxis, yaxis = sp[:xaxis], sp[:yaxis]
|
||||
xmin, xmax = axis_limits(xaxis)
|
||||
ymin, ymax = axis_limits(yaxis)
|
||||
xticks = get_ticks(xaxis)
|
||||
yticks = get_ticks(yaxis)
|
||||
xminorticks = get_minor_ticks(xaxis,xticks)
|
||||
yminorticks = get_minor_ticks(yaxis,yticks)
|
||||
xaxis_segs = Segments(2)
|
||||
yaxis_segs = Segments(2)
|
||||
xtick_segs = Segments(2)
|
||||
ytick_segs = Segments(2)
|
||||
xgrid_segs = Segments(2)
|
||||
ygrid_segs = Segments(2)
|
||||
xminorgrid_segs = Segments(2)
|
||||
yminorgrid_segs = Segments(2)
|
||||
xborder_segs = Segments(2)
|
||||
yborder_segs = Segments(2)
|
||||
|
||||
if sp[:framestyle] != :none
|
||||
# xaxis
|
||||
if xaxis[:showaxis]
|
||||
if sp[:framestyle] != :grid
|
||||
y1, y2 = if sp[:framestyle] in (:origin, :zerolines)
|
||||
0.0, 0.0
|
||||
else
|
||||
xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax)
|
||||
end
|
||||
push!(xaxis_segs, (xmin, y1), (xmax, y1))
|
||||
# don't show the 0 tick label for the origin framestyle
|
||||
if sp[:framestyle] == :origin && !(xticks in (nothing,false)) && length(xticks) > 1
|
||||
showticks = xticks[1] .!= 0
|
||||
xticks = (xticks[1][showticks], xticks[2][showticks])
|
||||
end
|
||||
end
|
||||
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine
|
||||
end
|
||||
if !(xaxis[:ticks] in (nothing, false))
|
||||
f = scalefunc(yaxis[:scale])
|
||||
invf = invscalefunc(yaxis[:scale])
|
||||
ticks_in = xaxis[:tick_direction] == :out ? -1 : 1
|
||||
t1 = invf(f(ymin) + 0.015 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
t2 = invf(f(ymax) - 0.015 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
t3 = invf(f(0) + 0.015 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
|
||||
for xtick in xticks[1]
|
||||
if xaxis[:showaxis]
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
(0, t3)
|
||||
else
|
||||
xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, t2) : (ymin, t1)
|
||||
end
|
||||
push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick
|
||||
end
|
||||
# sp[:draw_axes_border] && push!(xaxis_segs, (xtick, ymax), (xtick, t2)) # top tick
|
||||
xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid
|
||||
end
|
||||
end
|
||||
if !(xaxis[:minorticks] in (nothing, false)) || xaxis[:minorgrid]
|
||||
f = scalefunc(yaxis[:scale])
|
||||
invf = invscalefunc(yaxis[:scale])
|
||||
ticks_in = xaxis[:tick_direction] == :out ? -1 : 1
|
||||
t1 = invf(f(ymin) + 0.01 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
t2 = invf(f(ymax) - 0.01 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
t3 = invf(f(0) + 0.01 * (f(ymax) - f(ymin)) * ticks_in)
|
||||
|
||||
for xminortick in xminorticks
|
||||
if xaxis[:showaxis]
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
(0, t3)
|
||||
else
|
||||
xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, t2) : (ymin, t1)
|
||||
end
|
||||
push!(xtick_segs, (xminortick, tick_start), (xminortick, tick_stop)) # bottom tick
|
||||
end
|
||||
# sp[:draw_axes_border] && push!(xaxis_segs, (xtick, ymax), (xtick, t2)) # top tick
|
||||
xaxis[:minorgrid] && push!(xminorgrid_segs, (xminortick, ymin), (xminortick, ymax)) # vertical grid
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# yaxis
|
||||
if yaxis[:showaxis]
|
||||
if sp[:framestyle] != :grid
|
||||
x1, x2 = if sp[:framestyle] in (:origin, :zerolines)
|
||||
0.0, 0.0
|
||||
else
|
||||
xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax)
|
||||
end
|
||||
push!(yaxis_segs, (x1, ymin), (x1, ymax))
|
||||
# don't show the 0 tick label for the origin framestyle
|
||||
if sp[:framestyle] == :origin && !(yticks in (nothing,false)) && length(yticks) > 1
|
||||
showticks = yticks[1] .!= 0
|
||||
yticks = (yticks[1][showticks], yticks[2][showticks])
|
||||
end
|
||||
end
|
||||
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine
|
||||
end
|
||||
if !(yaxis[:ticks] in (nothing, false))
|
||||
f = scalefunc(xaxis[:scale])
|
||||
invf = invscalefunc(xaxis[:scale])
|
||||
ticks_in = yaxis[:tick_direction] == :out ? -1 : 1
|
||||
t1 = invf(f(xmin) + 0.015 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
t2 = invf(f(xmax) - 0.015 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
t3 = invf(f(0) + 0.015 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
|
||||
for ytick in yticks[1]
|
||||
if yaxis[:showaxis]
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
(0, t3)
|
||||
else
|
||||
xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, t2) : (xmin, t1)
|
||||
end
|
||||
push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick
|
||||
end
|
||||
# sp[:draw_axes_border] && push!(yaxis_segs, (xmax, ytick), (t2, ytick)) # right tick
|
||||
yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid
|
||||
end
|
||||
end
|
||||
if !(yaxis[:minorticks] in (nothing, false)) || yaxis[:minorgrid]
|
||||
f = scalefunc(xaxis[:scale])
|
||||
invf = invscalefunc(xaxis[:scale])
|
||||
ticks_in = yaxis[:tick_direction] == :out ? -1 : 1
|
||||
t1 = invf(f(xmin) + 0.01 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
t2 = invf(f(xmax) - 0.01 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
t3 = invf(f(0) + 0.01 * (f(xmax) - f(xmin)) * ticks_in)
|
||||
|
||||
for ytick in yminorticks
|
||||
if yaxis[:showaxis]
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
(0, t3)
|
||||
else
|
||||
xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, t2) : (xmin, t1)
|
||||
end
|
||||
push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick
|
||||
end
|
||||
# sp[:draw_axes_border] && push!(yaxis_segs, (xmax, ytick), (t2, ytick)) # right tick
|
||||
yaxis[:minorgrid] && push!(yminorgrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs
|
||||
end
|
||||
|
||||
+273
-82
@@ -1,49 +1,53 @@
|
||||
using Pkg
|
||||
|
||||
immutable NoBackend <: AbstractBackend end
|
||||
struct NoBackend <: AbstractBackend end
|
||||
|
||||
const _backendType = Dict{Symbol, DataType}(:none => NoBackend)
|
||||
const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none)
|
||||
const _backends = Symbol[]
|
||||
const _initialized_backends = Set{Symbol}()
|
||||
const _default_backends = (:none, :gr, :plotly)
|
||||
const _backendPackage = Dict{Symbol, Symbol}()
|
||||
|
||||
"Returns a list of supported backends"
|
||||
backends() = _backends
|
||||
|
||||
"Returns the name of the current backend"
|
||||
backend_name() = CURRENT_BACKEND.sym
|
||||
_backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")
|
||||
backend_package(pkg::Symbol) = pkg in _default_backends ? :Plots : Symbol("Plots", _backendPackage[pkg])
|
||||
backend_package_name(sym::Symbol) = sym in _default_backends ? :Plots : _backendPackage[sym]
|
||||
|
||||
macro init_backend(s)
|
||||
str = lowercase(string(s))
|
||||
package_str = string(s)
|
||||
str = lowercase(package_str)
|
||||
sym = Symbol(str)
|
||||
T = Symbol(string(s) * "Backend")
|
||||
esc(quote
|
||||
immutable $T <: AbstractBackend end
|
||||
struct $T <: AbstractBackend end
|
||||
export $sym
|
||||
$sym(; kw...) = (default(; kw...); backend(Symbol($str)))
|
||||
backend_name(::$T) = Symbol($str)
|
||||
backend_package_name(pkg::$T) = backend_package_name(Symbol($str))
|
||||
push!(_backends, Symbol($str))
|
||||
_backendType[Symbol($str)] = $T
|
||||
_backendSymbol[$T] = Symbol($str)
|
||||
include("backends/" * $str * ".jl")
|
||||
_backendPackage[Symbol($str)] = Symbol($package_str)
|
||||
# include("backends/" * $str * ".jl")
|
||||
end)
|
||||
end
|
||||
|
||||
@init_backend Immerse
|
||||
@init_backend Gadfly
|
||||
@init_backend PyPlot
|
||||
@init_backend Qwt
|
||||
@init_backend UnicodePlots
|
||||
@init_backend Winston
|
||||
@init_backend Bokeh
|
||||
@init_backend Plotly
|
||||
@init_backend PlotlyJS
|
||||
@init_backend GR
|
||||
@init_backend GLVisualize
|
||||
@init_backend PGFPlots
|
||||
|
||||
include("backends/web.jl")
|
||||
# include("backends/web.jl")
|
||||
# include("backends/supported.jl")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
function add_backend(pkg::Symbol)
|
||||
@info("To do a standard install of $pkg, copy and run this:\n\n")
|
||||
println(add_backend_string(_backend_instance(pkg)))
|
||||
println()
|
||||
end
|
||||
|
||||
# don't do anything as a default
|
||||
_create_backend_figure(plt::Plot) = nothing
|
||||
_prepare_plot_object(plt::Plot) = nothing
|
||||
@@ -54,16 +58,69 @@ _series_updated(plt::Plot, series::Series) = nothing
|
||||
|
||||
_before_layout_calcs(plt::Plot) = nothing
|
||||
|
||||
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt
|
||||
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt
|
||||
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt
|
||||
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt
|
||||
|
||||
"Returns the (width,height) of a text label."
|
||||
function text_size(lablen::Int, sz::Number, rot::Number = 0)
|
||||
# we need to compute the size of the ticks generically
|
||||
# this means computing the bounding box and then getting the width/height
|
||||
# note:
|
||||
ptsz = sz * pt
|
||||
width = 0.8lablen * ptsz
|
||||
|
||||
# now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
|
||||
height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz
|
||||
width = abs(sind(rot+90)) * width + abs(cosd(rot+90)) * ptsz
|
||||
width, height
|
||||
end
|
||||
text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot)
|
||||
|
||||
# account for the size/length/rotation of tick labels
|
||||
function tick_padding(axis::Axis)
|
||||
ticks = get_ticks(axis)
|
||||
if ticks == nothing
|
||||
0mm
|
||||
else
|
||||
vals, labs = ticks
|
||||
isempty(labs) && return 0mm
|
||||
# ptsz = axis[:tickfont].pointsize * pt
|
||||
longest_label = maximum(length(lab) for lab in labs)
|
||||
|
||||
# generalize by "rotating" y labels
|
||||
rot = axis[:rotation] + (axis[:letter] == :y ? 90 : 0)
|
||||
|
||||
# # we need to compute the size of the ticks generically
|
||||
# # this means computing the bounding box and then getting the width/height
|
||||
# labelwidth = 0.8longest_label * ptsz
|
||||
#
|
||||
#
|
||||
# # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
|
||||
# hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm
|
||||
# hgt
|
||||
|
||||
# get the height of the rotated label
|
||||
text_size(longest_label, axis[:tickfontsize], rot)[2]
|
||||
end
|
||||
end
|
||||
|
||||
# Set the (left, top, right, bottom) minimum padding around the plot area
|
||||
# to fit ticks, tick labels, guides, colorbars, etc.
|
||||
function _update_min_padding!(sp::Subplot)
|
||||
leftpad = 10mm + sp[:left_margin] + guide_padding(sp[:yaxis])
|
||||
toppad = 2mm + sp[:top_margin] + title_padding(sp)
|
||||
rightpad = 3mm + sp[:right_margin]
|
||||
bottompad = 5mm + sp[:bottom_margin] + guide_padding(sp[:xaxis])
|
||||
# TODO: something different when `is3d(sp) == true`
|
||||
leftpad = tick_padding(sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
|
||||
toppad = sp[:top_margin] + title_padding(sp)
|
||||
rightpad = sp[:right_margin]
|
||||
bottompad = tick_padding(sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis])
|
||||
|
||||
# switch them?
|
||||
if sp[:xaxis][:mirror]
|
||||
bottompad, toppad = toppad, bottompad
|
||||
end
|
||||
if sp[:yaxis][:mirror]
|
||||
leftpad, rightpad = rightpad, leftpad
|
||||
end
|
||||
|
||||
# @show (leftpad, toppad, rightpad, bottompad)
|
||||
sp.minpad = (leftpad, toppad, rightpad, bottompad)
|
||||
end
|
||||
@@ -73,7 +130,7 @@ _update_plot_object(plt::Plot) = nothing
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
type CurrentBackend
|
||||
mutable struct CurrentBackend
|
||||
sym::Symbol
|
||||
pkg::AbstractBackend
|
||||
end
|
||||
@@ -84,31 +141,30 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
|
||||
function pickDefaultBackend()
|
||||
env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "")
|
||||
if env_default != ""
|
||||
try
|
||||
Pkg.installed(env_default) # this will error if not installed
|
||||
sym = Symbol(lowercase(env_default))
|
||||
if haskey(_backendType, sym)
|
||||
sym = Symbol(lowercase(env_default))
|
||||
if sym in _backends
|
||||
if sym in _initialized_backends
|
||||
return backend(sym)
|
||||
else
|
||||
warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t",
|
||||
join(sort(_backends), "\n\t"))
|
||||
@warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.")
|
||||
end
|
||||
catch
|
||||
warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not installed.")
|
||||
else
|
||||
@warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t",
|
||||
join(sort(_backends), "\n\t"))
|
||||
end
|
||||
end
|
||||
|
||||
# the ordering/inclusion of this package list is my semi-arbitrary guess at
|
||||
# which one someone will want to use if they have the package installed...accounting for
|
||||
# features, speed, and robustness
|
||||
for pkgstr in ("PyPlot", "GR", "PlotlyJS", "Immerse", "Gadfly", "UnicodePlots")
|
||||
if Pkg.installed(pkgstr) != nothing
|
||||
return backend(Symbol(lowercase(pkgstr)))
|
||||
end
|
||||
end
|
||||
# for pkgstr in ("GR", "PyPlot", "PlotlyJS", "PGFPlots", "UnicodePlots", "InspectDR", "GLVisualize")
|
||||
# if pkgstr in keys(Pkg.installed())
|
||||
# return backend(Symbol(lowercase(pkgstr)))
|
||||
# end
|
||||
# end
|
||||
|
||||
# the default if nothing else is installed
|
||||
backend(:plotly)
|
||||
backend(:gr)
|
||||
end
|
||||
|
||||
|
||||
@@ -124,23 +180,6 @@ function backend()
|
||||
pickDefaultBackend()
|
||||
end
|
||||
|
||||
sym = CURRENT_BACKEND.sym
|
||||
if !(sym in _initialized_backends)
|
||||
|
||||
# initialize
|
||||
println("[Plots.jl] Initializing backend: ", sym)
|
||||
|
||||
inst = _backend_instance(sym)
|
||||
try
|
||||
_initialize_backend(inst)
|
||||
catch err
|
||||
warn("Couldn't initialize $sym. (might need to install it?)")
|
||||
rethrow(err)
|
||||
end
|
||||
|
||||
push!(_initialized_backends, sym)
|
||||
|
||||
end
|
||||
CURRENT_BACKEND.pkg
|
||||
end
|
||||
|
||||
@@ -148,42 +187,41 @@ end
|
||||
Set the plot backend.
|
||||
"""
|
||||
function backend(pkg::AbstractBackend)
|
||||
CURRENT_BACKEND.sym = backend_name(pkg)
|
||||
warn_on_deprecated_backend(CURRENT_BACKEND.sym)
|
||||
CURRENT_BACKEND.pkg = pkg
|
||||
sym = backend_name(pkg)
|
||||
if sym in _initialized_backends
|
||||
CURRENT_BACKEND.sym = backend_name(pkg)
|
||||
CURRENT_BACKEND.pkg = pkg
|
||||
else
|
||||
# try
|
||||
_initialize_backend(pkg)
|
||||
push!(_initialized_backends, sym)
|
||||
CURRENT_BACKEND.sym = backend_name(pkg)
|
||||
CURRENT_BACKEND.pkg = pkg
|
||||
# catch
|
||||
# add_backend(sym)
|
||||
# end
|
||||
end
|
||||
backend()
|
||||
end
|
||||
|
||||
function backend(modname::Symbol)
|
||||
warn_on_deprecated_backend(modname)
|
||||
CURRENT_BACKEND.sym = modname
|
||||
CURRENT_BACKEND.pkg = _backend_instance(modname)
|
||||
function backend(sym::Symbol)
|
||||
if sym in _backends
|
||||
backend(_backend_instance(sym))
|
||||
else
|
||||
@warn("`:$sym` is not a supported backend.")
|
||||
end
|
||||
backend()
|
||||
end
|
||||
|
||||
const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse]
|
||||
|
||||
function warn_on_deprecated_backend(bsym::Symbol)
|
||||
if bsym in _deprecated_backends
|
||||
warn("Backend $bsym has been deprecated. It may not work as originally intended.")
|
||||
@warn("Backend $bsym has been deprecated. It may not work as originally intended.")
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
supported_types(::AbstractBackend) = []
|
||||
supported_styles(::AbstractBackend) = [:solid]
|
||||
supported_markers(::AbstractBackend) = [:none]
|
||||
supported_scales(::AbstractBackend) = [:identity]
|
||||
is_subplot_supported(::AbstractBackend) = false
|
||||
is_string_supported(::AbstractBackend) = false
|
||||
nativeImagesSupported(b::AbstractBackend) = :image in supported_types(b)
|
||||
|
||||
supported_types() = supported_types(backend())
|
||||
supported_styles() = supported_styles(backend())
|
||||
supported_markers() = supported_markers(backend())
|
||||
supported_scales() = supported_scales(backend())
|
||||
is_subplot_supported() = is_subplot_supported(backend())
|
||||
is_string_supported() = is_string_supported(backend())
|
||||
nativeImagesSupported() = nativeImagesSupported(backend())
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -213,7 +251,7 @@ const _base_supported_args = [
|
||||
:subplot_index,
|
||||
:discrete_values,
|
||||
:projection,
|
||||
|
||||
|
||||
]
|
||||
|
||||
function merge_with_base_supported(v::AVec)
|
||||
@@ -225,5 +263,158 @@ function merge_with_base_supported(v::AVec)
|
||||
end
|
||||
end
|
||||
end
|
||||
v
|
||||
Set(v)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# @init_backend Immerse
|
||||
# @init_backend Gadfly
|
||||
@init_backend PyPlot
|
||||
# @init_backend Qwt
|
||||
@init_backend UnicodePlots
|
||||
# @init_backend Winston
|
||||
# @init_backend Bokeh
|
||||
@init_backend Plotly
|
||||
@init_backend PlotlyJS
|
||||
@init_backend GR
|
||||
@init_backend GLVisualize
|
||||
@init_backend PGFPlots
|
||||
@init_backend InspectDR
|
||||
@init_backend HDF5
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# create the various `is_xxx_supported` and `supported_xxxs` methods
|
||||
# by default they pass through to checking membership in `_gr_xxx`
|
||||
for s in (:attr, :seriestype, :marker, :style, :scale)
|
||||
f = Symbol("is_", s, "_supported")
|
||||
f2 = Symbol("supported_", s, "s")
|
||||
@eval begin
|
||||
$f(::AbstractBackend, $s) = false
|
||||
$f(bend::AbstractBackend, $s::AbstractVector) = all(v -> $f(bend, v), $s)
|
||||
$f($s) = $f(backend(), $s)
|
||||
$f2() = $f2(backend())
|
||||
end
|
||||
|
||||
for bend in backends()
|
||||
bend_type = typeof(_backend_instance(bend))
|
||||
v = Symbol("_", bend, "_", s)
|
||||
@eval begin
|
||||
$f(::$bend_type, $s::Symbol) = $s in $v
|
||||
$f2(::$bend_type) = $v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# is_subplot_supported(::AbstractBackend) = false
|
||||
# is_subplot_supported() = is_subplot_supported(backend())
|
||||
|
||||
|
||||
################################################################################
|
||||
# initialize the backends
|
||||
|
||||
function _initialize_backend(pkg::AbstractBackend)
|
||||
sym = backend_package_name(pkg)
|
||||
@eval Main begin
|
||||
import $sym
|
||||
export $sym
|
||||
end
|
||||
end
|
||||
|
||||
function add_backend_string(pkg::AbstractBackend)
|
||||
sym = backend_package_name(pkg)
|
||||
"""
|
||||
using Pkg
|
||||
Pkg.add("$sym")
|
||||
"""
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# glvisualize
|
||||
|
||||
function _initialize_backend(::GLVisualizeBackend; kw...)
|
||||
@eval Main begin
|
||||
import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour
|
||||
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle, Point, Vec
|
||||
import FileIO, Images
|
||||
export GLVisualize
|
||||
import Reactive: Signal
|
||||
import GLAbstraction: Style
|
||||
import GLVisualize: visualize
|
||||
import Plots.GL
|
||||
import UnicodeFun
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# hdf5
|
||||
|
||||
function _initialize_backend(::HDF5Backend)
|
||||
@eval Main begin
|
||||
import HDF5
|
||||
export HDF5
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# PGFPLOTS
|
||||
|
||||
function add_backend_string(::PGFPlotsBackend)
|
||||
"""
|
||||
using Pkg
|
||||
Pkg.add("PGFPlots")
|
||||
Pkg.build("PGFPlots")
|
||||
"""
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# plotlyjs
|
||||
|
||||
function add_backend_string(::PlotlyJSBackend)
|
||||
"""
|
||||
using Pkg
|
||||
Pkg.add("PlotlyJS")
|
||||
Pkg.add("Rsvg")
|
||||
import Blink
|
||||
Blink.AtomShell.install()
|
||||
"""
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# pyplot
|
||||
|
||||
function _initialize_backend(::PyPlotBackend)
|
||||
@eval Main begin
|
||||
import PyPlot, PyCall
|
||||
import LaTeXStrings: latexstring
|
||||
|
||||
export PyPlot
|
||||
|
||||
# we don't want every command to update the figure
|
||||
PyPlot.ioff()
|
||||
end
|
||||
end
|
||||
|
||||
function add_backend_string(::PyPlotBackend)
|
||||
"""
|
||||
using Pkg
|
||||
Pkg.add("PyPlot")
|
||||
Pkg.add("PyCall")
|
||||
Pkg.add("LaTeXStrings")
|
||||
withenv("PYTHON" => "") do
|
||||
Pkg.build("PyCall")
|
||||
Pkg.build("PyPlot")
|
||||
end
|
||||
"""
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# unicodeplots
|
||||
function add_backend_string(::UnicodePlotsBackend)
|
||||
"""
|
||||
using Pkg
|
||||
Pkg.add("UnicodePlots")
|
||||
Pkg.build("UnicodePlots")
|
||||
"""
|
||||
end
|
||||
|
||||
+1499
-77
File diff suppressed because it is too large
Load Diff
+824
-317
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,649 @@
|
||||
#HDF5 Plots: Save/replay plots to/from HDF5
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#==Usage
|
||||
===============================================================================
|
||||
Write to .hdf5 file using:
|
||||
p = plot(...)
|
||||
Plots.hdf5plot_write(p, "plotsave.hdf5")
|
||||
|
||||
Read from .hdf5 file using:
|
||||
pyplot() #Must first select backend
|
||||
pread = Plots.hdf5plot_read("plotsave.hdf5")
|
||||
display(pread)
|
||||
==#
|
||||
|
||||
|
||||
#==TODO
|
||||
===============================================================================
|
||||
1. Support more features
|
||||
- SeriesAnnotations & GridLayout known to be missing.
|
||||
3. Improve error handling.
|
||||
- Will likely crash if file format is off.
|
||||
2. Save data in a folder parallel to "plot".
|
||||
- Will make it easier for users to locate data.
|
||||
- Use HDF5 reference to link data?
|
||||
3. Develop an actual versioned file format.
|
||||
- Should have some form of backward compatibility.
|
||||
- Should be reliable for archival purposes.
|
||||
==#
|
||||
|
||||
import FixedPointNumbers: N0f8 #In core Julia
|
||||
|
||||
#Dispatch types:
|
||||
struct HDF5PlotNative; end #Indentifies a data element that can natively be handled by HDF5
|
||||
struct HDF5CTuple; end #Identifies a "complex" tuple structure
|
||||
|
||||
mutable struct HDF5Plot_PlotRef
|
||||
ref::Union{Plot, Nothing}
|
||||
end
|
||||
|
||||
|
||||
#==Useful constants
|
||||
===============================================================================#
|
||||
const _hdf5_nullable{T} = Union{T, Nothing}
|
||||
const _hdf5_plotroot = "plot"
|
||||
const _hdf5_dataroot = "data" #TODO: Eventually move data to different root (easier to locate)?
|
||||
const _hdf5plot_datatypeid = "TYPE" #Attribute identifying type
|
||||
const _hdf5plot_countid = "COUNT" #Attribute for storing count
|
||||
|
||||
#Dict has problems using "Types" as keys. Initialize in "_initialize_backend":
|
||||
const HDF5PLOT_MAP_STR2TELEM = Dict{String, Type}()
|
||||
const HDF5PLOT_MAP_TELEM2STR = Dict{Type, String}()
|
||||
|
||||
#Don't really like this global variable... Very hacky
|
||||
const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing)
|
||||
|
||||
#Simple sub-structures that can just be written out using _hdf5plot_gwritefields:
|
||||
const HDF5PLOT_SIMPLESUBSTRUCT = Union{Font, BoundingBox,
|
||||
GridLayout, RootLayout, ColorGradient, SeriesAnnotations, PlotText
|
||||
}
|
||||
|
||||
|
||||
#==
|
||||
===============================================================================#
|
||||
|
||||
const _hdf5_attr = merge_with_base_supported([
|
||||
:annotations,
|
||||
:background_color_legend, :background_color_inside, :background_color_outside,
|
||||
:foreground_color_grid, :foreground_color_legend, :foreground_color_title,
|
||||
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
|
||||
:label,
|
||||
:linecolor, :linestyle, :linewidth, :linealpha,
|
||||
:markershape, :markercolor, :markersize, :markeralpha,
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins, :bar_width, :bar_edges, :bar_position,
|
||||
:title, :title_location, :titlefont,
|
||||
:window_title,
|
||||
:guide, :lims, :ticks, :scale, :flip, :rotation,
|
||||
:tickfont, :guidefont, :legendfont,
|
||||
:grid, :legend, :colorbar,
|
||||
:marker_z, :line_z, :fill_z,
|
||||
:levels,
|
||||
:ribbon, :quiver, :arrow,
|
||||
:orientation,
|
||||
:overwrite_figure,
|
||||
:polar,
|
||||
:normalize, :weights,
|
||||
:contours, :aspect_ratio,
|
||||
:match_dimensions,
|
||||
:clims,
|
||||
:inset_subplots,
|
||||
:dpi,
|
||||
:colorbar_title,
|
||||
])
|
||||
const _hdf5_seriestype = [
|
||||
:path, :steppre, :steppost, :shape, :straightline,
|
||||
:scatter, :hexbin, #:histogram2d, :histogram,
|
||||
# :bar,
|
||||
:heatmap, :pie, :image,
|
||||
:contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
|
||||
]
|
||||
const _hdf5_style = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
const _hdf5_marker = vcat(_allMarkers, :pixel)
|
||||
const _hdf5_scale = [:identity, :ln, :log2, :log10]
|
||||
is_marker_supported(::HDF5Backend, shape::Shape) = true
|
||||
|
||||
if length(HDF5PLOT_MAP_TELEM2STR) < 1
|
||||
#Possible element types of high-level data types:
|
||||
telem2str = Dict{String, Type}(
|
||||
"NATIVE" => HDF5PlotNative,
|
||||
"VOID" => Nothing,
|
||||
"BOOL" => Bool,
|
||||
"SYMBOL" => Symbol,
|
||||
"TUPLE" => Tuple,
|
||||
"CTUPLE" => HDF5CTuple, #Tuple of complex structures
|
||||
"RGBA" => ARGB{N0f8},
|
||||
"EXTREMA" => Extrema,
|
||||
"LENGTH" => Length,
|
||||
"ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR
|
||||
|
||||
#Sub-structure types:
|
||||
"FONT" => Font,
|
||||
"BOUNDINGBOX" => BoundingBox,
|
||||
"GRIDLAYOUT" => GridLayout,
|
||||
"ROOTLAYOUT" => RootLayout,
|
||||
"SERIESANNOTATIONS" => SeriesAnnotations,
|
||||
# "PLOTTEXT" => PlotText,
|
||||
"COLORGRADIENT" => ColorGradient,
|
||||
"AXIS" => Axis,
|
||||
"SURFACE" => Surface,
|
||||
"SUBPLOT" => Subplot,
|
||||
"NULLABLE" => _hdf5_nullable,
|
||||
)
|
||||
merge!(HDF5PLOT_MAP_STR2TELEM, telem2str)
|
||||
merge!(HDF5PLOT_MAP_TELEM2STR, Dict{Type, String}(v=>k for (k,v) in HDF5PLOT_MAP_STR2TELEM))
|
||||
end
|
||||
|
||||
|
||||
#==Helper functions
|
||||
===============================================================================#
|
||||
|
||||
_hdf5_plotelempath(subpath::String) = "$_hdf5_plotroot/$subpath"
|
||||
_hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
|
||||
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
|
||||
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
|
||||
|
||||
function _hdf5_merge!(dest::Dict, src::Dict)
|
||||
for (k, v) in src
|
||||
if isa(v, Axis)
|
||||
_hdf5_merge!(dest[k].d, v.d)
|
||||
else
|
||||
dest[k] = v
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
#==
|
||||
===============================================================================#
|
||||
|
||||
|
||||
# Create the window/figure for this backend.
|
||||
function _create_backend_figure(plt::Plot{HDF5Backend})
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# # this is called early in the pipeline, use it to make the plot current or something
|
||||
# function _prepare_plot_object(plt::Plot{HDF5Backend})
|
||||
# end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Set up the subplot within the backend object.
|
||||
function _initialize_subplot(plt::Plot{HDF5Backend}, sp::Subplot{HDF5Backend})
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Add one series to the underlying backend object.
|
||||
# Called once per series
|
||||
# NOTE: Seems to be called when user calls plot()... even if backend
|
||||
# plot, sp.o has not yet been constructed...
|
||||
function _series_added(plt::Plot{HDF5Backend}, series::Series)
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# When series data is added/changed, this callback can do dynamic updates to the backend object.
|
||||
# note: if the backend rebuilds the plot from scratch on display, then you might not do anything here.
|
||||
function _series_updated(plt::Plot{HDF5Backend}, series::Series)
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# called just before updating layout bounding boxes... in case you need to prep
|
||||
# for the calcs
|
||||
function _before_layout_calcs(plt::Plot{HDF5Backend})
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Set the (left, top, right, bottom) minimum padding around the plot area
|
||||
# to fit ticks, tick labels, guides, colorbars, etc.
|
||||
function _update_min_padding!(sp::Subplot{HDF5Backend})
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations])
|
||||
function _update_plot_object(plt::Plot{HDF5Backend})
|
||||
#Do nothing
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Display/show the plot (open a GUI window, or browser page, for example).
|
||||
function _display(plt::Plot{HDF5Backend})
|
||||
msg = "HDF5 interface does not support `display()` function."
|
||||
msg *= "\nUse `Plots.hdf5plot_write(::String)` method to write to .HDF5 \"plot\" file instead."
|
||||
@warn(msg)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
#==HDF5 write functions
|
||||
===============================================================================#
|
||||
|
||||
function _hdf5plot_writetype(grp, k::String, tstr::Array{String})
|
||||
d = HDF5.d_open(grp, k)
|
||||
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_writetype(grp, k::String, T::Type)
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[T]
|
||||
d = HDF5.d_open(grp, k)
|
||||
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_overwritetype(grp, k::String, T::Type)
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[T]
|
||||
d = HDF5.d_open(grp, k)
|
||||
HDF5.a_delete(d, _hdf5plot_datatypeid)
|
||||
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_writetype(grp, T::Type) #Write directly to group
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[T]
|
||||
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_overwritetype(grp, T::Type) #Write directly to group
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[T]
|
||||
HDF5.a_delete(grp, _hdf5plot_datatypeid)
|
||||
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_writetype(grp, ::Type{Array{T}}) where T<:Any
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[Array] #ANY
|
||||
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_writetype(grp, ::Type{T}) where T<:BoundingBox
|
||||
tstr = HDF5PLOT_MAP_TELEM2STR[BoundingBox]
|
||||
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
|
||||
end
|
||||
function _hdf5plot_writecount(grp, n::Int) #Write directly to group
|
||||
HDF5.a_write(grp, _hdf5plot_countid, n)
|
||||
end
|
||||
function _hdf5plot_gwritefields(grp, k::String, v)
|
||||
grp = HDF5.g_create(grp, k)
|
||||
for _k in fieldnames(typeof(v))
|
||||
_v = getfield(v, _k)
|
||||
kstr = string(_k)
|
||||
_hdf5plot_gwrite(grp, kstr, _v)
|
||||
end
|
||||
_hdf5plot_writetype(grp, typeof(v))
|
||||
return
|
||||
end
|
||||
|
||||
# Write data
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_gwrite(grp, k::String, v) #Default
|
||||
grp[k] = v
|
||||
_hdf5plot_writetype(grp, k, HDF5PlotNative)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Array{T}) where T<:Number #Default for arrays
|
||||
grp[k] = v
|
||||
_hdf5plot_writetype(grp, k, HDF5PlotNative)
|
||||
end
|
||||
#=
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Array{Any})
|
||||
# @show grp, k
|
||||
@warn("Cannot write Array: $k=$v")
|
||||
end
|
||||
=#
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Nothing)
|
||||
grp[k] = 0
|
||||
_hdf5plot_writetype(grp, k, Nothing)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Bool)
|
||||
grp[k] = Int(v)
|
||||
_hdf5plot_writetype(grp, k, Bool)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Symbol)
|
||||
grp[k] = string(v)
|
||||
_hdf5plot_writetype(grp, k, Symbol)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Tuple)
|
||||
varr = [v...]
|
||||
elt = eltype(varr)
|
||||
# if isleaftype(elt)
|
||||
|
||||
_hdf5plot_gwrite(grp, k, varr)
|
||||
if elt <: Number
|
||||
#We just wrote a simple dataset
|
||||
_hdf5plot_overwritetype(grp, k, Tuple)
|
||||
else #Used a more complex scheme (using subgroups):
|
||||
_hdf5plot_overwritetype(grp[k], HDF5CTuple)
|
||||
end
|
||||
#NOTE: _hdf5plot_overwritetype overwrites "Array" type with "Tuple".
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, d::Dict)
|
||||
# @warn("Cannot write dict: $k=$d")
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::AbstractRange)
|
||||
_hdf5plot_gwrite(grp, k, collect(v)) #For now
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::ARGB{N0f8})
|
||||
grp[k] = [v.r.i, v.g.i, v.b.i, v.alpha.i]
|
||||
_hdf5plot_writetype(grp, k, ARGB{N0f8})
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Colorant)
|
||||
_hdf5plot_gwrite(grp, k, ARGB{N0f8}(v))
|
||||
end
|
||||
#Custom vector (when not using simple numeric type):
|
||||
function _hdf5plot_gwritearray(grp, k::String, v::Array{T}) where T
|
||||
if "annotations" == k;
|
||||
return #Hack. Does not yet support annotations.
|
||||
end
|
||||
|
||||
vgrp = HDF5.g_create(grp, k)
|
||||
_hdf5plot_writetype(vgrp, Array) #ANY
|
||||
sz = size(v)
|
||||
lidx = LinearIndices(sz)
|
||||
|
||||
for iter in eachindex(v)
|
||||
coord = lidx[iter]
|
||||
elem = v[iter]
|
||||
idxstr = join(coord, "_")
|
||||
_hdf5plot_gwrite(vgrp, "v$idxstr", v[iter])
|
||||
end
|
||||
|
||||
_hdf5plot_gwrite(vgrp, "dim", [sz...])
|
||||
return
|
||||
end
|
||||
_hdf5plot_gwrite(grp, k::String, v::Array) =
|
||||
_hdf5plot_gwritearray(grp, k, v)
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Extrema)
|
||||
grp[k] = [v.emin, v.emax]
|
||||
_hdf5plot_writetype(grp, k, Extrema)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Length{T}) where T
|
||||
grp[k] = v.value
|
||||
_hdf5plot_writetype(grp, k, [HDF5PLOT_MAP_TELEM2STR[Length], string(T)])
|
||||
end
|
||||
|
||||
# Write more complex structures:
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Plot)
|
||||
#Don't write plot references
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::HDF5PLOT_SIMPLESUBSTRUCT)
|
||||
_hdf5plot_gwritefields(grp, k, v)
|
||||
return
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Axis)
|
||||
grp = HDF5.g_create(grp, k)
|
||||
for (_k, _v) in v.d
|
||||
kstr = string(_k)
|
||||
_hdf5plot_gwrite(grp, kstr, _v)
|
||||
end
|
||||
_hdf5plot_writetype(grp, Axis)
|
||||
return
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Surface)
|
||||
grp = HDF5.g_create(grp, k)
|
||||
_hdf5plot_gwrite(grp, "data2d", v.surf)
|
||||
_hdf5plot_writetype(grp, Surface)
|
||||
end
|
||||
# #TODO: "Properly" support Nullable using _hdf5plot_writetype?
|
||||
# function _hdf5plot_gwrite(grp, k::String, v::_hdf5_nullable)
|
||||
# if isnull(v)
|
||||
# _hdf5plot_gwrite(grp, k, nothing)
|
||||
# else
|
||||
# _hdf5plot_gwrite(grp, k, v.value)
|
||||
# end
|
||||
# return
|
||||
# end
|
||||
|
||||
function _hdf5plot_gwrite(grp, k::String, v::SeriesAnnotations)
|
||||
#Currently no support for SeriesAnnotations
|
||||
return
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Subplot)
|
||||
grp = HDF5.g_create(grp, k)
|
||||
_hdf5plot_gwrite(grp, "index", v[:subplot_index])
|
||||
_hdf5plot_writetype(grp, Subplot)
|
||||
return
|
||||
end
|
||||
function _hdf5plot_write(grp, d::Dict)
|
||||
for (k, v) in d
|
||||
kstr = string(k)
|
||||
_hdf5plot_gwrite(grp, kstr, v)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Write main plot structures:
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_write(sp::Subplot{HDF5Backend}, subpath::String, f)
|
||||
f = f::HDF5.HDF5File #Assert
|
||||
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/attr"))
|
||||
_hdf5plot_write(grp, sp.attr)
|
||||
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list"))
|
||||
_hdf5plot_writecount(grp, length(sp.series_list))
|
||||
for (i, series) in enumerate(sp.series_list)
|
||||
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
|
||||
_hdf5plot_write(grp, series.d)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function _hdf5plot_write(plt::Plot{HDF5Backend}, f)
|
||||
f = f::HDF5.HDF5File #Assert
|
||||
|
||||
grp = HDF5.g_create(f, _hdf5_plotelempath("attr"))
|
||||
_hdf5plot_write(grp, plt.attr)
|
||||
|
||||
grp = HDF5.g_create(f, _hdf5_plotelempath("subplots"))
|
||||
_hdf5plot_writecount(grp, length(plt.subplots))
|
||||
|
||||
for (i, sp) in enumerate(plt.subplots)
|
||||
_hdf5plot_write(sp, "subplots/subplot$i", f)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString)
|
||||
HDF5.h5open(path, "w") do file
|
||||
_hdf5plot_write(plt, file)
|
||||
end
|
||||
end
|
||||
hdf5plot_write(path::AbstractString) = hdf5plot_write(current(), path)
|
||||
|
||||
|
||||
#==HDF5 playback (read) functions
|
||||
===============================================================================#
|
||||
|
||||
function _hdf5plot_readcount(grp) #Read directly from group
|
||||
return HDF5.a_read(grp, _hdf5plot_countid)
|
||||
end
|
||||
|
||||
_hdf5plot_convert(T::Type{HDF5PlotNative}, v) = v
|
||||
_hdf5plot_convert(T::Type{Nothing}, v) = nothing
|
||||
_hdf5plot_convert(T::Type{Bool}, v) = (v!=0)
|
||||
_hdf5plot_convert(T::Type{Symbol}, v) = Symbol(v)
|
||||
_hdf5plot_convert(T::Type{Tuple}, v) = tuple(v...) #With Vector{T<:Number}
|
||||
function _hdf5plot_convert(T::Type{ARGB{N0f8}}, v)
|
||||
r, g, b, a = reinterpret(N0f8, v)
|
||||
return Colors.ARGB{N0f8}(r, g, b, a)
|
||||
end
|
||||
_hdf5plot_convert(T::Type{Extrema}, v) = Extrema(v[1], v[2])
|
||||
|
||||
# Read data structures:
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_read(grp, k::String, T::Type, dtid)
|
||||
v = HDF5.d_read(grp, k)
|
||||
return _hdf5plot_convert(T, v)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Length}, dtid::Vector)
|
||||
v = HDF5.d_read(grp, k)
|
||||
TU = Symbol(dtid[2])
|
||||
T = typeof(v)
|
||||
return Length{TU,T}(v)
|
||||
end
|
||||
|
||||
# Read more complex data structures:
|
||||
# ----------------------------------------------------------------
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Font}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
family = _hdf5plot_read(grp, "family")
|
||||
pointsize = _hdf5plot_read(grp, "pointsize")
|
||||
halign = _hdf5plot_read(grp, "halign")
|
||||
valign = _hdf5plot_read(grp, "valign")
|
||||
rotation = _hdf5plot_read(grp, "rotation")
|
||||
color = _hdf5plot_read(grp, "color")
|
||||
return Font(family, pointsize, halign, valign, rotation, color)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Array}, dtid) #ANY
|
||||
grp = HDF5.g_open(grp, k)
|
||||
sz = _hdf5plot_read(grp, "dim")
|
||||
if [0] == sz; return []; end
|
||||
sz = tuple(sz...)
|
||||
result = Array{Any}(undef, sz)
|
||||
lidx = LinearIndices(sz)
|
||||
|
||||
for iter in eachindex(result)
|
||||
coord = lidx[iter]
|
||||
idxstr = join(coord, "_")
|
||||
result[iter] = _hdf5plot_read(grp, "v$idxstr")
|
||||
end
|
||||
|
||||
#Hack: Implicitly make Julia detect element type.
|
||||
# (Should probably write it explicitly to file)
|
||||
result = [result[iter] for iter in eachindex(result)] #Potentially make more specific
|
||||
return reshape(result, sz)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple}, dtid)
|
||||
v = _hdf5plot_read(grp, k, Array, dtid)
|
||||
return tuple(v...)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
colors = _hdf5plot_read(grp, "colors")
|
||||
values = _hdf5plot_read(grp, "values")
|
||||
return ColorGradient(colors, values)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{BoundingBox}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
x0 = _hdf5plot_read(grp, "x0")
|
||||
a = _hdf5plot_read(grp, "a")
|
||||
return BoundingBox(x0, a)
|
||||
end
|
||||
_hdf5plot_read(grp, k::String, T::Type{RootLayout}, dtid) = RootLayout()
|
||||
function _hdf5plot_read(grp, k::String, T::Type{GridLayout}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
# parent = _hdf5plot_read(grp, "parent")
|
||||
parent = RootLayout()
|
||||
minpad = _hdf5plot_read(grp, "minpad")
|
||||
bbox = _hdf5plot_read(grp, "bbox")
|
||||
grid = _hdf5plot_read(grp, "grid")
|
||||
widths = _hdf5plot_read(grp, "widths")
|
||||
heights = _hdf5plot_read(grp, "heights")
|
||||
attr = KW() #TODO support attr: _hdf5plot_read(grp, "attr")
|
||||
|
||||
return GridLayout(parent, minpad, bbox, grid, widths, heights, attr)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Axis}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
return Axis([], kwlist)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Surface}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
data2d = _hdf5plot_read(grp, "data2d")
|
||||
return Surface(data2d)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Subplot}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
idx = _hdf5plot_read(grp, "index")
|
||||
return HDF5PLOT_PLOTREF.ref.subplots[idx]
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String)
|
||||
dtid = HDF5.a_read(grp[k], _hdf5plot_datatypeid)
|
||||
T = _hdf5_map_str2telem(dtid) #expect exception
|
||||
return _hdf5plot_read(grp, k, T, dtid)
|
||||
end
|
||||
|
||||
#Read in values in group to populate d:
|
||||
function _hdf5plot_read(grp, d::Dict)
|
||||
gnames = names(grp)
|
||||
for k in gnames
|
||||
try
|
||||
v = _hdf5plot_read(grp, k)
|
||||
d[Symbol(k)] = v
|
||||
catch e
|
||||
@show e
|
||||
@show grp
|
||||
@warn("Could not read field $k")
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Read main plot structures:
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_read(sp::Subplot, subpath::String, f)
|
||||
f = f::HDF5.HDF5File #Assert
|
||||
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr"))
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
_hdf5_merge!(sp.attr, kwlist)
|
||||
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list"))
|
||||
nseries = _hdf5plot_readcount(grp)
|
||||
|
||||
for i in 1:nseries
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
plot!(sp, kwlist[:x], kwlist[:y]) #Add data & create data structures
|
||||
_hdf5_merge!(sp.series_list[end].d, kwlist)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function _hdf5plot_read(plt::Plot, f)
|
||||
f = f::HDF5.HDF5File #Assert
|
||||
#Assumpltion: subplots are already allocated (plt.subplots)
|
||||
|
||||
HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout"
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("attr"))
|
||||
_hdf5plot_read(grp, plt.attr)
|
||||
|
||||
for (i, sp) in enumerate(plt.subplots)
|
||||
_hdf5plot_read(sp, "subplots/subplot$i", f)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function hdf5plot_read(path::AbstractString)
|
||||
plt = nothing
|
||||
HDF5.h5open(path, "r") do file
|
||||
grp = HDF5.g_open(file, _hdf5_plotelempath("subplots"))
|
||||
n = _hdf5plot_readcount(grp)
|
||||
plt = plot(layout=n) #Get reference to a new plot
|
||||
_hdf5plot_read(plt, file)
|
||||
end
|
||||
return plt
|
||||
end
|
||||
|
||||
#Last line
|
||||
@@ -0,0 +1,525 @@
|
||||
|
||||
# https://github.com/ma-laforge/InspectDR.jl
|
||||
|
||||
#=TODO:
|
||||
Tweak scale factor for width & other sizes
|
||||
|
||||
Not supported by InspectDR:
|
||||
:foreground_color_grid
|
||||
:foreground_color_border
|
||||
:polar,
|
||||
|
||||
Add in functionality to Plots.jl:
|
||||
:aspect_ratio,
|
||||
=#
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
#TODO: remove features
|
||||
const _inspectdr_attr = merge_with_base_supported([
|
||||
:annotations,
|
||||
:background_color_legend, :background_color_inside, :background_color_outside,
|
||||
# :foreground_color_grid,
|
||||
:foreground_color_legend, :foreground_color_title,
|
||||
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
|
||||
:label,
|
||||
:linecolor, :linestyle, :linewidth, :linealpha,
|
||||
:markershape, :markercolor, :markersize, :markeralpha,
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:markerstrokestyle, #Causes warning not to have it... what is this?
|
||||
:fillcolor, :fillalpha, #:fillrange,
|
||||
# :bins, :bar_width, :bar_edges, :bar_position,
|
||||
:title, :title_location,
|
||||
:window_title,
|
||||
:guide, :lims, :scale, #:ticks, :flip, :rotation,
|
||||
:titlefontfamily, :titlefontsize, :titlefontcolor,
|
||||
:legendfontfamily, :legendfontsize, :legendfontcolor,
|
||||
:tickfontfamily, :tickfontsize, :tickfontcolor,
|
||||
:guidefontfamily, :guidefontsize, :guidefontcolor,
|
||||
:grid, :legend, #:colorbar,
|
||||
# :marker_z,
|
||||
# :line_z,
|
||||
# :levels,
|
||||
# :ribbon, :quiver, :arrow,
|
||||
# :orientation,
|
||||
:overwrite_figure,
|
||||
:polar,
|
||||
# :normalize, :weights,
|
||||
# :contours, :aspect_ratio,
|
||||
:match_dimensions,
|
||||
# :clims,
|
||||
# :inset_subplots,
|
||||
:dpi,
|
||||
# :colorbar_title,
|
||||
])
|
||||
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
const _inspectdr_seriestype = [
|
||||
:path, :scatter, :shape, :straightline, #, :steppre, :steppost
|
||||
]
|
||||
#see: _allMarkers, _shape_keys
|
||||
const _inspectdr_marker = Symbol[
|
||||
:none, :auto,
|
||||
:circle, :rect, :diamond,
|
||||
:cross, :xcross,
|
||||
:utriangle, :dtriangle, :rtriangle, :ltriangle,
|
||||
:pentagon, :hexagon, :heptagon, :octagon,
|
||||
:star4, :star5, :star6, :star7, :star8,
|
||||
:vline, :hline, :+, :x,
|
||||
]
|
||||
|
||||
const _inspectdr_scale = [:identity, :ln, :log2, :log10]
|
||||
|
||||
is_marker_supported(::InspectDRBackend, shape::Shape) = true
|
||||
|
||||
_inspectdr_to_pixels(bb::BoundingBox) =
|
||||
InspectDR.BoundingBox(to_pixels(left(bb)), to_pixels(right(bb)), to_pixels(top(bb)), to_pixels(bottom(bb)))
|
||||
|
||||
#Do we avoid Map to avoid possible pre-comile issues?
|
||||
function _inspectdr_mapglyph(s::Symbol)
|
||||
s == :rect && return :square
|
||||
return s
|
||||
end
|
||||
|
||||
function _inspectdr_mapglyph(s::Shape)
|
||||
x, y = coords(s)
|
||||
return InspectDR.GlyphPolyline(x, y)
|
||||
end
|
||||
|
||||
# py_marker(markers::AVec) = map(py_marker, markers)
|
||||
function _inspectdr_mapglyph(markers::AVec)
|
||||
@warn("Vectors of markers are currently unsupported in InspectDR.")
|
||||
_inspectdr_mapglyph(markers[1])
|
||||
end
|
||||
|
||||
_inspectdr_mapglyphsize(v::Real) = v
|
||||
function _inspectdr_mapglyphsize(v::Vector)
|
||||
@warn("Vectors of marker sizes are currently unsupported in InspectDR.")
|
||||
_inspectdr_mapglyphsize(v[1])
|
||||
end
|
||||
|
||||
_inspectdr_mapcolor(v::Colorant) = v
|
||||
function _inspectdr_mapcolor(g::PlotUtils.ColorGradient)
|
||||
@warn("Color gradients are currently unsupported in InspectDR.")
|
||||
#Pick middle color:
|
||||
_inspectdr_mapcolor(g.colors[div(1+end,2)])
|
||||
end
|
||||
function _inspectdr_mapcolor(v::AVec)
|
||||
@warn("Vectors of colors are currently unsupported in InspectDR.")
|
||||
#Pick middle color:
|
||||
_inspectdr_mapcolor(v[div(1+end,2)])
|
||||
end
|
||||
|
||||
#Hack: suggested point size does not seem adequate relative to plot size, for some reason.
|
||||
_inspectdr_mapptsize(v) = 1.5*v
|
||||
|
||||
function _inspectdr_add_annotations(plot, x, y, val)
|
||||
#What kind of annotation is this?
|
||||
end
|
||||
|
||||
#plot::InspectDR.Plot2D
|
||||
function _inspectdr_add_annotations(plot, x, y, val::PlotText)
|
||||
vmap = Dict{Symbol, Symbol}(:top=>:t, :bottom=>:b) #:vcenter
|
||||
hmap = Dict{Symbol, Symbol}(:left=>:l, :right=>:r) #:hcenter
|
||||
align = Symbol(get(vmap, val.font.valign, :c), get(hmap, val.font.halign, :c))
|
||||
fnt = InspectDR.Font(val.font.family, val.font.pointsize,
|
||||
color =_inspectdr_mapcolor(val.font.color)
|
||||
)
|
||||
ann = InspectDR.atext(val.str, x=x, y=y,
|
||||
font=fnt, angle=val.font.rotation, align=align
|
||||
)
|
||||
InspectDR.add(plot, ann)
|
||||
return
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _inspectdr_getscale(s::Symbol, yaxis::Bool)
|
||||
#TODO: Support :asinh, :sqrt
|
||||
kwargs = yaxis ? (:tgtmajor=>8, :tgtminor=>2) : () #More grid lines on y-axis
|
||||
if :log2 == s
|
||||
return InspectDR.AxisScale(:log2; kwargs...)
|
||||
elseif :log10 == s
|
||||
return InspectDR.AxisScale(:log10; kwargs...)
|
||||
elseif :ln == s
|
||||
return InspectDR.AxisScale(:ln; kwargs...)
|
||||
else #identity
|
||||
return InspectDR.AxisScale(:lin; kwargs...)
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
#Glyph used when plotting "Shape"s:
|
||||
INSPECTDR_GLYPH_SHAPE = InspectDR.GlyphPolyline(
|
||||
2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y
|
||||
)
|
||||
|
||||
mutable struct InspecDRPlotRef
|
||||
mplot::Union{Nothing, InspectDR.Multiplot}
|
||||
gui::Union{Nothing, InspectDR.GtkPlot}
|
||||
end
|
||||
|
||||
_inspectdr_getmplot(::Any) = nothing
|
||||
_inspectdr_getmplot(r::InspecDRPlotRef) = r.mplot
|
||||
|
||||
_inspectdr_getgui(::Any) = nothing
|
||||
_inspectdr_getgui(gplot::InspectDR.GtkPlot) = (gplot.destroyed ? nothing : gplot)
|
||||
_inspectdr_getgui(r::InspecDRPlotRef) = _inspectdr_getgui(r.gui)
|
||||
push!(_initialized_backends, :inspectdr)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Create the window/figure for this backend.
|
||||
function _create_backend_figure(plt::Plot{InspectDRBackend})
|
||||
mplot = _inspectdr_getmplot(plt.o)
|
||||
gplot = _inspectdr_getgui(plt.o)
|
||||
|
||||
#:overwrite_figure: want to reuse current figure
|
||||
if plt[:overwrite_figure] && mplot != nothing
|
||||
mplot.subplots = [] #Reset
|
||||
if gplot != nothing #Ensure still references current plot
|
||||
gplot.src = mplot
|
||||
end
|
||||
else #want new one:
|
||||
mplot = InspectDR.Multiplot()
|
||||
gplot = nothing #Will be created later
|
||||
end
|
||||
|
||||
#break link with old subplots
|
||||
for sp in plt.subplots
|
||||
sp.o = nothing
|
||||
end
|
||||
|
||||
return InspecDRPlotRef(mplot, gplot)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# # this is called early in the pipeline, use it to make the plot current or something
|
||||
# function _prepare_plot_object(plt::Plot{InspectDRBackend})
|
||||
# end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Set up the subplot within the backend object.
|
||||
function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend})
|
||||
plot = sp.o
|
||||
#Don't do anything without a "subplot" object: Will process later.
|
||||
if nothing == plot; return; end
|
||||
plot.data = []
|
||||
plot.userannot = [] #Clear old markers/text annotation/polyline "annotation"
|
||||
return plot
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Add one series to the underlying backend object.
|
||||
# Called once per series
|
||||
# NOTE: Seems to be called when user calls plot()... even if backend
|
||||
# plot, sp.o has not yet been constructed...
|
||||
function _series_added(plt::Plot{InspectDRBackend}, series::Series)
|
||||
st = series[:seriestype]
|
||||
sp = series[:subplot]
|
||||
plot = sp.o
|
||||
|
||||
#Don't do anything without a "subplot" object: Will process later.
|
||||
if nothing == plot; return; end
|
||||
|
||||
_vectorize(v) = isa(v, Vector) ? v : collect(v) #InspectDR only supports vectors
|
||||
x, y = if st == :straightline
|
||||
straightline_data(series)
|
||||
else
|
||||
_vectorize(series[:x]), _vectorize(series[:y])
|
||||
end
|
||||
|
||||
#No support for polar grid... but can still perform polar transformation:
|
||||
if ispolar(sp)
|
||||
Θ = x; r = y
|
||||
x = r.*cos.(Θ); y = r.*sin.(Θ)
|
||||
end
|
||||
|
||||
# doesn't handle mismatched x/y - wrap data (pyplot behaviour):
|
||||
nx = length(x); ny = length(y)
|
||||
if nx < ny
|
||||
series[:x] = Float64[x[mod1(i,nx)] for i=1:ny]
|
||||
elseif ny > nx
|
||||
series[:y] = Float64[y[mod1(i,ny)] for i=1:nx]
|
||||
end
|
||||
|
||||
#= TODO: Eventually support
|
||||
series[:fillcolor] #I think this is fill under line
|
||||
zorder = series[:series_plotindex]
|
||||
|
||||
For st in :shape:
|
||||
zorder = series[:series_plotindex],
|
||||
=#
|
||||
|
||||
if st in (:shape,)
|
||||
x, y = shape_data(series)
|
||||
nmax = 0
|
||||
for (i,rng) in enumerate(iter_segments(x, y))
|
||||
nmax = i
|
||||
if length(rng) > 1
|
||||
linewidth = series[:linewidth]
|
||||
linecolor = _inspectdr_mapcolor(_cycle(series[:linecolor], i))
|
||||
fillcolor = _inspectdr_mapcolor(_cycle(series[:fillcolor], i))
|
||||
line = InspectDR.line(
|
||||
style=:solid, width=linewidth, color=linecolor
|
||||
)
|
||||
apline = InspectDR.PolylineAnnotation(
|
||||
x[rng], y[rng], line=line, fillcolor=fillcolor
|
||||
)
|
||||
InspectDR.add(plot, apline)
|
||||
end
|
||||
end
|
||||
|
||||
i = (nmax >= 2 ? div(nmax, 2) : nmax) #Must pick one set of colors for legend
|
||||
if i > 1 #Add dummy waveform for legend entry:
|
||||
linewidth = series[:linewidth]
|
||||
linecolor = _inspectdr_mapcolor(_cycle(series[:linecolor], i))
|
||||
fillcolor = _inspectdr_mapcolor(_cycle(series[:fillcolor], i))
|
||||
wfrm = InspectDR.add(plot, Float64[], Float64[], id=series[:label])
|
||||
wfrm.line = InspectDR.line(
|
||||
style=:none, width=linewidth, #linewidth affects glyph
|
||||
)
|
||||
wfrm.glyph = InspectDR.glyph(
|
||||
shape = INSPECTDR_GLYPH_SHAPE, size = 8,
|
||||
color = linecolor, fillcolor = fillcolor
|
||||
)
|
||||
end
|
||||
elseif st in (:path, :scatter, :straightline) #, :steppre, :steppost)
|
||||
#NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think).
|
||||
linewidth = series[:linewidth]
|
||||
#More efficient & allows some support for markerstrokewidth:
|
||||
_style = (0==linewidth ? :none : series[:linestyle])
|
||||
wfrm = InspectDR.add(plot, x, y, id=series[:label])
|
||||
wfrm.line = InspectDR.line(
|
||||
style = _style,
|
||||
width = series[:linewidth],
|
||||
color = series[:linecolor],
|
||||
)
|
||||
#InspectDR does not control markerstrokewidth independently.
|
||||
if :none == _style
|
||||
#Use this property only if no line is displayed:
|
||||
wfrm.line.width = series[:markerstrokewidth]
|
||||
end
|
||||
wfrm.glyph = InspectDR.glyph(
|
||||
shape = _inspectdr_mapglyph(series[:markershape]),
|
||||
size = _inspectdr_mapglyphsize(series[:markersize]),
|
||||
color = _inspectdr_mapcolor(series[:markerstrokecolor]),
|
||||
fillcolor = _inspectdr_mapcolor(series[:markercolor]),
|
||||
)
|
||||
end
|
||||
|
||||
# this is all we need to add the series_annotations text
|
||||
anns = series[:series_annotations]
|
||||
for (xi,yi,str,fnt) in EachAnn(anns, x, y)
|
||||
_inspectdr_add_annotations(plot, xi, yi, PlotText(str, fnt))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# When series data is added/changed, this callback can do dynamic updates to the backend object.
|
||||
# note: if the backend rebuilds the plot from scratch on display, then you might not do anything here.
|
||||
function _series_updated(plt::Plot{InspectDRBackend}, series::Series)
|
||||
#Nothing to do
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend})
|
||||
plot = sp.o
|
||||
strip = plot.strips[1] #Only 1 strip supported with Plots.jl
|
||||
|
||||
xaxis = sp[:xaxis]; yaxis = sp[:yaxis]
|
||||
xgrid_show = xaxis[:grid]
|
||||
ygrid_show = yaxis[:grid]
|
||||
|
||||
strip.grid = InspectDR.GridRect(
|
||||
vmajor=xgrid_show, # vminor=xgrid_show,
|
||||
hmajor=ygrid_show, # hminor=ygrid_show,
|
||||
)
|
||||
|
||||
plot.xscale = _inspectdr_getscale(xaxis[:scale], false)
|
||||
strip.yscale = _inspectdr_getscale(yaxis[:scale], true)
|
||||
xmin, xmax = axis_limits(xaxis)
|
||||
ymin, ymax = axis_limits(yaxis)
|
||||
if ispolar(sp)
|
||||
#Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax)
|
||||
rmax = NaNMath.max(abs(ymin), abs(ymax))
|
||||
xmin, xmax = -rmax, rmax
|
||||
ymin, ymax = -rmax, rmax
|
||||
end
|
||||
plot.xext = InspectDR.PExtents1D() #reset
|
||||
strip.yext = InspectDR.PExtents1D() #reset
|
||||
plot.xext_full = InspectDR.PExtents1D(xmin, xmax)
|
||||
strip.yext_full = InspectDR.PExtents1D(ymin, ymax)
|
||||
a = plot.annotation
|
||||
a.title = sp[:title]
|
||||
a.xlabel = xaxis[:guide]; a.ylabels = [yaxis[:guide]]
|
||||
|
||||
l = plot.layout
|
||||
l[:frame_canvas].fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot])
|
||||
l[:frame_data].fillcolor = _inspectdr_mapcolor(sp[:background_color_inside])
|
||||
l[:frame_data].line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis])
|
||||
l[:font_title] = InspectDR.Font(sp[:titlefontfamily],
|
||||
_inspectdr_mapptsize(sp[:titlefontsize]),
|
||||
color = _inspectdr_mapcolor(sp[:titlefontcolor])
|
||||
)
|
||||
#Cannot independently control fonts of axes with InspectDR:
|
||||
l[:font_axislabel] = InspectDR.Font(xaxis[:guidefontfamily],
|
||||
_inspectdr_mapptsize(xaxis[:guidefontsize]),
|
||||
color = _inspectdr_mapcolor(xaxis[:guidefontcolor])
|
||||
)
|
||||
l[:font_ticklabel] = InspectDR.Font(xaxis[:tickfontfamily],
|
||||
_inspectdr_mapptsize(xaxis[:tickfontsize]),
|
||||
color = _inspectdr_mapcolor(xaxis[:tickfontcolor])
|
||||
)
|
||||
l[:enable_legend] = (sp[:legend] != :none)
|
||||
#l[:halloc_legend] = 150 #TODO: compute???
|
||||
l[:font_legend] = InspectDR.Font(sp[:legendfontfamily],
|
||||
_inspectdr_mapptsize(sp[:legendfontsize]),
|
||||
color = _inspectdr_mapcolor(sp[:legendfontcolor])
|
||||
)
|
||||
l[:frame_legend].fillcolor = _inspectdr_mapcolor(sp[:background_color_legend])
|
||||
end
|
||||
|
||||
# called just before updating layout bounding boxes... in case you need to prep
|
||||
# for the calcs
|
||||
function _before_layout_calcs(plt::Plot{InspectDRBackend})
|
||||
mplot = _inspectdr_getmplot(plt.o)
|
||||
if nothing == mplot; return; end
|
||||
|
||||
mplot.title = plt[:plot_title]
|
||||
if "" == mplot.title
|
||||
#Don't use window_title... probably not what you want.
|
||||
#mplot.title = plt[:window_title]
|
||||
end
|
||||
mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside])
|
||||
|
||||
resize!(mplot.subplots, length(plt.subplots))
|
||||
nsubplots = length(plt.subplots)
|
||||
for (i, sp) in enumerate(plt.subplots)
|
||||
if !isassigned(mplot.subplots, i)
|
||||
mplot.subplots[i] = InspectDR.Plot2D()
|
||||
end
|
||||
sp.o = mplot.subplots[i]
|
||||
plot = sp.o
|
||||
_initialize_subplot(plt, sp)
|
||||
_inspectdr_setupsubplot(sp)
|
||||
|
||||
# add the annotations
|
||||
for ann in sp[:annotations]
|
||||
_inspectdr_add_annotations(plot, ann...)
|
||||
end
|
||||
end
|
||||
|
||||
#Do not yet support absolute plot positionning.
|
||||
#Just try to make things look more-or less ok:
|
||||
if nsubplots <= 1
|
||||
mplot.layout[:ncolumns] = 1
|
||||
elseif nsubplots <= 4
|
||||
mplot.layout[:ncolumns] = 2
|
||||
elseif nsubplots <= 6
|
||||
mplot.layout[:ncolumns] = 3
|
||||
elseif nsubplots <= 12
|
||||
mplot.layout[:ncolumns] = 4
|
||||
else
|
||||
mplot.layout[:ncolumns] = 5
|
||||
end
|
||||
|
||||
for series in plt.series_list
|
||||
_series_added(plt, series)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Set the (left, top, right, bottom) minimum padding around the plot area
|
||||
# to fit ticks, tick labels, guides, colorbars, etc.
|
||||
function _update_min_padding!(sp::Subplot{InspectDRBackend})
|
||||
plot = sp.o
|
||||
if !isa(plot, InspectDR.Plot2D); return sp.minpad; end
|
||||
#Computing plotbounds with 0-BoundingBox returns required padding:
|
||||
bb = InspectDR.plotbounds(plot.layout.values, InspectDR.BoundingBox(0,0,0,0))
|
||||
#NOTE: plotbounds always pads for titles, legends, etc. even if not in use.
|
||||
#TODO: possibly zero-out items not in use??
|
||||
|
||||
# add in the user-specified margin to InspectDR padding:
|
||||
leftpad = abs(bb.xmin)*px + sp[:left_margin]
|
||||
toppad = abs(bb.ymin)*px + sp[:top_margin]
|
||||
rightpad = abs(bb.xmax)*px + sp[:right_margin]
|
||||
bottompad = abs(bb.ymax)*px + sp[:bottom_margin]
|
||||
sp.minpad = (leftpad, toppad, rightpad, bottompad)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations])
|
||||
function _update_plot_object(plt::Plot{InspectDRBackend})
|
||||
mplot = _inspectdr_getmplot(plt.o)
|
||||
if nothing == mplot; return; end
|
||||
|
||||
for (i, sp) in enumerate(plt.subplots)
|
||||
graphbb = _inspectdr_to_pixels(plotarea(sp))
|
||||
plot = mplot.subplots[i]
|
||||
plot.plotbb = InspectDR.plotbounds(plot.layout.values, graphbb)
|
||||
end
|
||||
|
||||
gplot = _inspectdr_getgui(plt.o)
|
||||
if nothing == gplot; return; end
|
||||
|
||||
gplot.src = mplot #Ensure still references current plot
|
||||
InspectDR.refresh(gplot)
|
||||
return
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
const _inspectdr_mimeformats_dpi = Dict(
|
||||
"image/png" => "png"
|
||||
)
|
||||
const _inspectdr_mimeformats_nodpi = Dict(
|
||||
"image/svg+xml" => "svg",
|
||||
"application/eps" => "eps",
|
||||
"image/eps" => "eps",
|
||||
# "application/postscript" => "ps", #TODO: support once Cairo supports PSSurface
|
||||
"application/pdf" => "pdf"
|
||||
)
|
||||
_inspectdr_show(io::IO, mime::MIME, ::Nothing, w, h) =
|
||||
throw(ErrorException("Cannot show(::IO, ...) plot - not yet generated"))
|
||||
function _inspectdr_show(io::IO, mime::MIME, mplot, w, h)
|
||||
InspectDR._show(io, mime, mplot, Float64(w), Float64(h))
|
||||
end
|
||||
|
||||
for (mime, fmt) in _inspectdr_mimeformats_dpi
|
||||
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
|
||||
dpi = plt[:dpi]#TODO: support
|
||||
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
|
||||
end
|
||||
end
|
||||
for (mime, fmt) in _inspectdr_mimeformats_nodpi
|
||||
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
|
||||
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# Display/show the plot (open a GUI window, or browser page, for example).
|
||||
function _display(plt::Plot{InspectDRBackend})
|
||||
mplot = _inspectdr_getmplot(plt.o)
|
||||
if nothing == mplot; return; end
|
||||
gplot = _inspectdr_getgui(plt.o)
|
||||
|
||||
if nothing == gplot
|
||||
gplot = display(InspectDR.GtkDisplay(), mplot)
|
||||
else
|
||||
#redundant... Plots.jl will call _update_plot_object:
|
||||
#InspectDR.refresh(gplot)
|
||||
end
|
||||
plt.o = InspecDRPlotRef(mplot, gplot)
|
||||
return gplot
|
||||
end
|
||||
+383
-89
@@ -2,13 +2,14 @@
|
||||
|
||||
# significant contributions by: @pkofod
|
||||
|
||||
supported_args(::PGFPlotsBackend) = merge_with_base_supported([
|
||||
# :annotations,
|
||||
# :background_color_legend,
|
||||
const _pgfplots_attr = merge_with_base_supported([
|
||||
:annotations,
|
||||
:background_color_legend,
|
||||
:background_color_inside,
|
||||
# :background_color_outside,
|
||||
# :foreground_color_legend, :foreground_color_grid, :foreground_color_axis,
|
||||
# :foreground_color_text, :foreground_color_border,
|
||||
# :foreground_color_legend,
|
||||
:foreground_color_grid, :foreground_color_axis,
|
||||
:foreground_color_text, :foreground_color_border,
|
||||
:label,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor, :linestyle, :linewidth, :linealpha,
|
||||
@@ -22,32 +23,24 @@ supported_args(::PGFPlotsBackend) = merge_with_base_supported([
|
||||
:guide, :lims, :ticks, :scale, :flip, :rotation,
|
||||
:tickfont, :guidefont, :legendfont,
|
||||
:grid, :legend,
|
||||
# :colorbar,
|
||||
# :marker_z, :levels,
|
||||
:colorbar,
|
||||
:fill_z, :line_z, :marker_z, :levels,
|
||||
# :ribbon, :quiver, :arrow,
|
||||
# :orientation,
|
||||
# :overwrite_figure,
|
||||
# :polar,
|
||||
:polar,
|
||||
# :normalize, :weights, :contours,
|
||||
:aspect_ratio,
|
||||
# :match_dimensions,
|
||||
:tick_direction,
|
||||
:framestyle,
|
||||
:camera,
|
||||
:contour_labels,
|
||||
])
|
||||
supported_types(::PGFPlotsBackend) = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour]
|
||||
supported_styles(::PGFPlotsBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supported_markers(::PGFPlotsBackend) = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
|
||||
supported_scales(::PGFPlotsBackend) = [:identity, :ln, :log2, :log10]
|
||||
is_subplot_supported(::PGFPlotsBackend) = false
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _initialize_backend(::PGFPlotsBackend; kw...)
|
||||
@eval begin
|
||||
import PGFPlots
|
||||
export PGFPlots
|
||||
end
|
||||
end
|
||||
const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour, :shape, :straightline,]
|
||||
const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon, :hline] #vcat(_allMarkers, Shape)
|
||||
const _pgfplots_scale = [:identity, :ln, :log2, :log10]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
@@ -64,6 +57,8 @@ const _pgfplots_markers = KW(
|
||||
:none => "none",
|
||||
:cross => "+",
|
||||
:xcross => "x",
|
||||
:+ => "+",
|
||||
:x => "x",
|
||||
:utriangle => "triangle*",
|
||||
:dtriangle => "triangle*",
|
||||
:circle => "*",
|
||||
@@ -72,6 +67,7 @@ const _pgfplots_markers = KW(
|
||||
:star6 => "asterisk",
|
||||
:diamond => "diamond*",
|
||||
:pentagon => "pentagon*",
|
||||
:hline => "-"
|
||||
)
|
||||
|
||||
const _pgfplots_legend_pos = KW(
|
||||
@@ -79,6 +75,7 @@ const _pgfplots_legend_pos = KW(
|
||||
:bottomright => "south east",
|
||||
:topright => "north east",
|
||||
:topleft => "north west",
|
||||
:outertopright => "outer north east",
|
||||
)
|
||||
|
||||
|
||||
@@ -91,71 +88,131 @@ const _pgf_series_extrastyle = KW(
|
||||
:xsticks => "xcomb",
|
||||
)
|
||||
|
||||
# PGFPlots uses the anchors to define orientations for example to align left
|
||||
# one needs to use the right edge as anchor
|
||||
const _pgf_annotation_halign = KW(
|
||||
:center => "",
|
||||
:left => "right",
|
||||
:right => "left"
|
||||
)
|
||||
|
||||
const _pgf_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
|
||||
const _pgf_framestyle_defaults = Dict(:semi => :box)
|
||||
function pgf_framestyle(style::Symbol)
|
||||
if style in _pgf_framestyles
|
||||
return style
|
||||
else
|
||||
default_style = get(_pgf_framestyle_defaults, style, :axes)
|
||||
@warn("Framestyle :$style is not (yet) supported by the PGFPlots backend. :$default_style was cosen instead.")
|
||||
default_style
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
# takes in color,alpha, and returns color and alpha appropriate for pgf style
|
||||
function pgf_color(c, a = nothing)
|
||||
c = getColor(c)
|
||||
function pgf_color(c::Colorant)
|
||||
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", red(c), green(c), blue(c))
|
||||
a = float(a == nothing ? alpha(c) : a)
|
||||
cstr, a
|
||||
cstr, alpha(c)
|
||||
end
|
||||
|
||||
function pgf_fillstyle(d::KW)
|
||||
cstr,a = pgf_color(d[:fillcolor], d[:fillalpha])
|
||||
function pgf_color(grad::ColorGradient)
|
||||
# Can't handle ColorGradient here, fallback to defaults.
|
||||
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", 0.0, 0.60560316,0.97868012)
|
||||
cstr, 1
|
||||
end
|
||||
|
||||
# Generates a colormap for pgfplots based on a ColorGradient
|
||||
function pgf_colormap(grad::ColorGradient)
|
||||
join(map(grad.colors) do c
|
||||
@sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c),blue(c))
|
||||
end,", ")
|
||||
end
|
||||
|
||||
pgf_thickness_scaling(plt::Plot) = plt[:thickness_scaling]
|
||||
pgf_thickness_scaling(sp::Subplot) = pgf_thickness_scaling(sp.plt)
|
||||
pgf_thickness_scaling(series) = pgf_thickness_scaling(series[:subplot])
|
||||
|
||||
function pgf_fillstyle(d, i = 1)
|
||||
cstr,a = pgf_color(get_fillcolor(d, i))
|
||||
fa = get_fillalpha(d, i)
|
||||
if fa != nothing
|
||||
a = fa
|
||||
end
|
||||
"fill = $cstr, fill opacity=$a"
|
||||
end
|
||||
|
||||
function pgf_linestyle(d::KW)
|
||||
cstr,a = pgf_color(d[:linecolor], d[:linealpha])
|
||||
function pgf_linestyle(linewidth::Real, color, α = 1, linestyle = "solid")
|
||||
cstr, a = pgf_color(plot_color(color, α))
|
||||
"""
|
||||
color = $cstr,
|
||||
draw opacity=$a,
|
||||
line width=$(d[:linewidth]),
|
||||
$(get(_pgfplots_linestyles, d[:linestyle], "solid"))"""
|
||||
draw opacity = $a,
|
||||
line width = $linewidth,
|
||||
$(get(_pgfplots_linestyles, linestyle, "solid"))"""
|
||||
end
|
||||
|
||||
function pgf_marker(d::KW)
|
||||
shape = d[:markershape]
|
||||
cstr, a = pgf_color(d[:markercolor], d[:markeralpha])
|
||||
cstr_stroke, a_stroke = pgf_color(d[:markerstrokecolor], d[:markerstrokealpha])
|
||||
function pgf_linestyle(d, i = 1)
|
||||
lw = pgf_thickness_scaling(d) * get_linewidth(d, i)
|
||||
lc = get_linecolor(d, i)
|
||||
la = get_linealpha(d, i)
|
||||
ls = get_linestyle(d, i)
|
||||
return pgf_linestyle(lw, lc, la, ls)
|
||||
end
|
||||
|
||||
function pgf_font(fontsize, thickness_scaling = 1, font = "\\selectfont")
|
||||
fs = fontsize * thickness_scaling
|
||||
return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}")
|
||||
end
|
||||
|
||||
function pgf_marker(d, i = 1)
|
||||
shape = _cycle(d[:markershape], i)
|
||||
cstr, a = pgf_color(plot_color(get_markercolor(d, i), get_markeralpha(d, i)))
|
||||
cstr_stroke, a_stroke = pgf_color(plot_color(get_markerstrokecolor(d, i), get_markerstrokealpha(d, i)))
|
||||
"""
|
||||
mark = $(get(_pgfplots_markers, shape, "*")),
|
||||
mark size = $(0.5 * d[:markersize]),
|
||||
mark size = $(pgf_thickness_scaling(d) * 0.5 * _cycle(d[:markersize], i)),
|
||||
mark options = {
|
||||
color = $cstr_stroke, draw opacity = $a_stroke,
|
||||
fill = $cstr, fill opacity = $a,
|
||||
line width = $(d[:markerstrokewidth]),
|
||||
line width = $(pgf_thickness_scaling(d) * _cycle(d[:markerstrokewidth], i)),
|
||||
rotate = $(shape == :dtriangle ? 180 : 0),
|
||||
$(get(_pgfplots_linestyles, d[:markerstrokestyle], "solid"))
|
||||
$(get(_pgfplots_linestyles, _cycle(d[:markerstrokestyle], i), "solid"))
|
||||
}"""
|
||||
end
|
||||
|
||||
function pgf_add_annotation!(o, x, y, val, thickness_scaling = 1)
|
||||
# Construct the style string.
|
||||
# Currently supports color and orientation
|
||||
cstr,a = pgf_color(val.font.color)
|
||||
push!(o, PGFPlots.Plots.Node(val.str, # Annotation Text
|
||||
x, y,
|
||||
style="""
|
||||
$(get(_pgf_annotation_halign,val.font.halign,"")),
|
||||
color=$cstr, draw opacity=$(convert(Float16,a)),
|
||||
rotate=$(val.font.rotation),
|
||||
font=$(pgf_font(val.font.pointsize, thickness_scaling))
|
||||
"""))
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function pgf_series(sp::Subplot, series::Series)
|
||||
d = series.d
|
||||
st = d[:seriestype]
|
||||
style = []
|
||||
kw = KW()
|
||||
|
||||
push!(style, pgf_linestyle(d))
|
||||
push!(style, pgf_marker(d))
|
||||
|
||||
if d[:fillrange] != nothing
|
||||
push!(style, pgf_fillstyle(d))
|
||||
end
|
||||
|
||||
# add to legend?
|
||||
if sp[:legend] != :none && should_add_to_legend(series)
|
||||
kw[:legendentry] = d[:label]
|
||||
end
|
||||
series_collection = PGFPlots.Plot[]
|
||||
|
||||
# function args
|
||||
args = if st == :contour
|
||||
args = if st == :contour
|
||||
d[:z].surf, d[:x], d[:y]
|
||||
elseif is3d(st)
|
||||
d[:x], d[:y], d[:z]
|
||||
elseif st == :straightline
|
||||
straightline_data(series)
|
||||
elseif st == :shape
|
||||
shape_data(series)
|
||||
elseif ispolar(sp)
|
||||
theta, r = filter_radial_data(d[:x], d[:y], axis_limits(sp[:yaxis]))
|
||||
rad2deg.(theta), r
|
||||
else
|
||||
d[:x], d[:y]
|
||||
end
|
||||
@@ -166,34 +223,131 @@ function pgf_series(sp::Subplot, series::Series)
|
||||
else
|
||||
a
|
||||
end, args)
|
||||
# for (i,a) in enumerate(args)
|
||||
# if typeof(a) <: AbstractVector && typeof(a) != Vector
|
||||
# args[i] = collect(a)
|
||||
# end
|
||||
# end
|
||||
|
||||
# include additional style, then add to the kw
|
||||
if st in (:contour, :histogram2d)
|
||||
style = []
|
||||
kw = KW()
|
||||
push!(style, pgf_linestyle(d))
|
||||
push!(style, pgf_marker(d))
|
||||
push!(style, "forget plot")
|
||||
|
||||
kw[:style] = join(style, ',')
|
||||
func = if st == :histogram2d
|
||||
PGFPlots.Histogram2
|
||||
else
|
||||
kw[:labels] = series[:contour_labels]
|
||||
kw[:levels] = series[:levels]
|
||||
PGFPlots.Contour
|
||||
end
|
||||
push!(series_collection, func(args...; kw...))
|
||||
|
||||
else
|
||||
# series segments
|
||||
segments = iter_segments(series)
|
||||
for (i, rng) in enumerate(segments)
|
||||
style = []
|
||||
kw = KW()
|
||||
push!(style, pgf_linestyle(d, i))
|
||||
push!(style, pgf_marker(d, i))
|
||||
|
||||
if st == :shape
|
||||
push!(style, pgf_fillstyle(d, i))
|
||||
end
|
||||
|
||||
# add to legend?
|
||||
if i == 1 && sp[:legend] != :none && should_add_to_legend(series)
|
||||
if d[:fillrange] != nothing
|
||||
push!(style, "forget plot")
|
||||
push!(series_collection, pgf_fill_legend_hack(d, args))
|
||||
else
|
||||
kw[:legendentry] = d[:label]
|
||||
if st == :shape # || d[:fillrange] != nothing
|
||||
push!(style, "area legend")
|
||||
end
|
||||
end
|
||||
else
|
||||
push!(style, "forget plot")
|
||||
end
|
||||
|
||||
seg_args = (arg[rng] for arg in args)
|
||||
|
||||
# include additional style, then add to the kw
|
||||
if haskey(_pgf_series_extrastyle, st)
|
||||
push!(style, _pgf_series_extrastyle[st])
|
||||
end
|
||||
kw[:style] = join(style, ',')
|
||||
|
||||
# add fillrange
|
||||
if series[:fillrange] != nothing && st != :shape
|
||||
push!(series_collection, pgf_fillrange_series(series, i, _cycle(series[:fillrange], rng), seg_args...))
|
||||
end
|
||||
|
||||
# build/return the series object
|
||||
func = if st == :path3d
|
||||
PGFPlots.Linear3
|
||||
elseif st == :scatter
|
||||
PGFPlots.Scatter
|
||||
else
|
||||
PGFPlots.Linear
|
||||
end
|
||||
push!(series_collection, func(seg_args...; kw...))
|
||||
end
|
||||
end
|
||||
series_collection
|
||||
end
|
||||
|
||||
function pgf_fillrange_series(series, i, fillrange, args...)
|
||||
st = series[:seriestype]
|
||||
style = []
|
||||
kw = KW()
|
||||
push!(style, "line width = 0")
|
||||
push!(style, "draw opacity = 0")
|
||||
push!(style, pgf_fillstyle(series, i))
|
||||
push!(style, pgf_marker(series, i))
|
||||
push!(style, "forget plot")
|
||||
if haskey(_pgf_series_extrastyle, st)
|
||||
push!(style, _pgf_series_extrastyle[st])
|
||||
end
|
||||
kw[:style] = join(style, ',')
|
||||
func = is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
|
||||
return func(pgf_fillrange_args(fillrange, args...)...; kw...)
|
||||
end
|
||||
|
||||
# build/return the series object
|
||||
function pgf_fillrange_args(fillrange, x, y)
|
||||
n = length(x)
|
||||
x_fill = [x; x[n:-1:1]; x[1]]
|
||||
y_fill = [y; _cycle(fillrange, n:-1:1); y[1]]
|
||||
return x_fill, y_fill
|
||||
end
|
||||
|
||||
function pgf_fillrange_args(fillrange, x, y, z)
|
||||
n = length(x)
|
||||
x_fill = [x; x[n:-1:1]; x[1]]
|
||||
y_fill = [y; y[n:-1:1]; x[1]]
|
||||
z_fill = [z; _cycle(fillrange, n:-1:1); z[1]]
|
||||
return x_fill, y_fill, z_fill
|
||||
end
|
||||
|
||||
function pgf_fill_legend_hack(d, args)
|
||||
style = []
|
||||
kw = KW()
|
||||
push!(style, pgf_linestyle(d, 1))
|
||||
push!(style, pgf_marker(d, 1))
|
||||
push!(style, pgf_fillstyle(d, 1))
|
||||
push!(style, "area legend")
|
||||
kw[:legendentry] = d[:label]
|
||||
kw[:style] = join(style, ',')
|
||||
st = d[:seriestype]
|
||||
func = if st == :path3d
|
||||
PGFPlots.Linear3
|
||||
elseif st == :scatter
|
||||
PGFPlots.Scatter
|
||||
elseif st == :histogram2d
|
||||
PGFPlots.Histogram2
|
||||
elseif st == :contour
|
||||
PGFPlots.Contour
|
||||
else
|
||||
PGFPlots.Linear
|
||||
end
|
||||
func(args...; kw...)
|
||||
return func(([arg[1]] for arg in args)...; kw...)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function pgf_axis(sp::Subplot, letter)
|
||||
@@ -201,9 +355,19 @@ function pgf_axis(sp::Subplot, letter)
|
||||
style = []
|
||||
kw = KW()
|
||||
|
||||
# turn off scaled ticks
|
||||
push!(style, "scaled $(letter) ticks = false")
|
||||
|
||||
# set to supported framestyle
|
||||
framestyle = pgf_framestyle(sp[:framestyle])
|
||||
|
||||
# axis guide
|
||||
kw[Symbol(letter,:label)] = axis[:guide]
|
||||
|
||||
# Add label font
|
||||
cstr, α = pgf_color(plot_color(axis[:guidefontcolor]))
|
||||
push!(style, string(letter, "label style = {font = ", pgf_font(axis[:guidefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:guidefontrotation], "}"))
|
||||
|
||||
# flip/reverse?
|
||||
axis[:flip] && push!(style, "$letter dir=reverse")
|
||||
|
||||
@@ -214,14 +378,78 @@ function pgf_axis(sp::Subplot, letter)
|
||||
scale == :ln || push!(style, "log basis $letter=$(scale == :log2 ? 2 : 10)")
|
||||
end
|
||||
|
||||
# ticks on or off
|
||||
if axis[:ticks] in (nothing, false, :none) || framestyle == :none
|
||||
push!(style, "$(letter)majorticks=false")
|
||||
end
|
||||
|
||||
# grid on or off
|
||||
if axis[:grid] && framestyle != :none
|
||||
push!(style, "$(letter)majorgrids = true")
|
||||
else
|
||||
push!(style, "$(letter)majorgrids = false")
|
||||
end
|
||||
|
||||
# limits
|
||||
# TODO: support zlims
|
||||
if letter != :z
|
||||
lims = axis_limits(axis)
|
||||
lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(axis)) : axis_limits(axis)
|
||||
kw[Symbol(letter,:min)] = lims[1]
|
||||
kw[Symbol(letter,:max)] = lims[2]
|
||||
end
|
||||
|
||||
if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
|
||||
ticks = get_ticks(axis)
|
||||
#pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
|
||||
tick_values = ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1]
|
||||
push!(style, string(letter, "tick = {", join(tick_values,","), "}"))
|
||||
if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto
|
||||
# wrap the power part of label with }
|
||||
tick_labels = Vector{String}(length(ticks[2]))
|
||||
for (i, label) in enumerate(ticks[2])
|
||||
base, power = split(label, "^")
|
||||
power = string("{", power, "}")
|
||||
tick_labels[i] = string(base, "^", power)
|
||||
end
|
||||
push!(style, string(letter, "ticklabels = {\$", join(tick_labels,"\$,\$"), "\$}"))
|
||||
elseif axis[:showaxis]
|
||||
tick_labels = ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2]
|
||||
if axis[:formatter] in (:scientific, :auto)
|
||||
tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$")
|
||||
tick_labels = replace.(tick_labels, "×", "\\times")
|
||||
end
|
||||
push!(style, string(letter, "ticklabels = {", join(tick_labels,","), "}"))
|
||||
else
|
||||
push!(style, string(letter, "ticklabels = {}"))
|
||||
end
|
||||
push!(style, string(letter, "tick align = ", (axis[:tick_direction] == :out ? "outside" : "inside")))
|
||||
cstr, α = pgf_color(plot_color(axis[:tickfontcolor]))
|
||||
push!(style, string(letter, "ticklabel style = {font = ", pgf_font(axis[:tickfontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:tickfontrotation], "}"))
|
||||
push!(style, string(letter, " grid style = {", pgf_linestyle(pgf_thickness_scaling(sp) * axis[:gridlinewidth], axis[:foreground_color_grid], axis[:gridalpha], axis[:gridstyle]), "}"))
|
||||
end
|
||||
|
||||
# framestyle
|
||||
if framestyle in (:axes, :origin)
|
||||
axispos = framestyle == :axes ? "left" : "middle"
|
||||
# the * after lines disables the arrows at the axes
|
||||
push!(style, string("axis lines* = ", axispos))
|
||||
end
|
||||
|
||||
if framestyle == :zerolines
|
||||
push!(style, string("extra ", letter, " ticks = 0"))
|
||||
push!(style, string("extra ", letter, " tick labels = "))
|
||||
push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}}"))
|
||||
end
|
||||
|
||||
if !axis[:showaxis]
|
||||
push!(style, "separate axis lines")
|
||||
end
|
||||
if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none)
|
||||
push!(style, string(letter, " axis line style = {draw opacity = 0}"))
|
||||
else
|
||||
push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}"))
|
||||
end
|
||||
|
||||
# return the style list and KW args
|
||||
style, kw
|
||||
end
|
||||
@@ -229,10 +457,14 @@ end
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
function _make_pgf_plot!(plt::Plot)
|
||||
function _update_plot_object(plt::Plot{PGFPlotsBackend})
|
||||
plt.o = PGFPlots.Axis[]
|
||||
# Obtain the total height of the plot by extracting the maximal bottom
|
||||
# coordinate from the bounding box.
|
||||
total_height = bottom(bbox(plt.layout))
|
||||
|
||||
for sp in plt.subplots
|
||||
# first build the PGFPlots.Axis object
|
||||
# first build the PGFPlots.Axis object
|
||||
style = ["unbounded coords=jump"]
|
||||
kw = KW()
|
||||
|
||||
@@ -240,26 +472,30 @@ function _make_pgf_plot!(plt::Plot)
|
||||
for letter in (:x, :y, :z)
|
||||
if letter != :z || is3d(sp)
|
||||
axisstyle, axiskw = pgf_axis(sp, letter)
|
||||
append!(style, axisstyle)
|
||||
merge!(kw, axiskw)
|
||||
end
|
||||
end
|
||||
|
||||
# bounding box values are in mm
|
||||
# note: bb origin is top-left, pgf is bottom-left
|
||||
# A round on 2 decimal places should be enough precision for 300 dpi
|
||||
# plots.
|
||||
bb = bbox(sp)
|
||||
push!(style, """
|
||||
xshift = $(left(bb).value)mm,
|
||||
yshift = $((height(bb) - (bottom(bb))).value)mm,
|
||||
width = $(width(bb).value)mm,
|
||||
height = $(height(bb).value)mm,
|
||||
yshift = $(round((total_height - (bottom(bb))).value, digits=2))mm,
|
||||
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])}
|
||||
""")
|
||||
kw[:width] = "$(width(bb).value)mm"
|
||||
kw[:height] = "$(height(bb).value)mm"
|
||||
|
||||
if sp[:title] != ""
|
||||
push!(style, "title = $(sp[:title])")
|
||||
kw[:title] = "$(sp[:title])"
|
||||
cstr, α = pgf_color(plot_color(sp[:titlefontcolor]))
|
||||
push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}"))
|
||||
end
|
||||
|
||||
sp[:grid] && push!(style, "grid = major")
|
||||
if sp[:aspect_ratio] in (1, :equal)
|
||||
kw[:axisEqual] = "true"
|
||||
end
|
||||
@@ -268,28 +504,81 @@ function _make_pgf_plot!(plt::Plot)
|
||||
if haskey(_pgfplots_legend_pos, legpos)
|
||||
kw[:legendPos] = _pgfplots_legend_pos[legpos]
|
||||
end
|
||||
cstr, a = pgf_color(plot_color(sp[:background_color_legend]))
|
||||
push!(style, string("legend style = {", pgf_linestyle(pgf_thickness_scaling(sp), sp[:foreground_color_legend], 1.0, "solid"), ",", "fill = $cstr,", "font = ", pgf_font(sp[:legendfontsize], pgf_thickness_scaling(sp)), "}"))
|
||||
|
||||
o = PGFPlots.Axis(; style = style, kw...)
|
||||
if any(s[:seriestype] == :contour for s in series_list(sp))
|
||||
kw[:view] = "{0}{90}"
|
||||
kw[:colorbar] = !(sp[:colorbar] in (:none, :off, :hide, false))
|
||||
elseif is3d(sp)
|
||||
azim, elev = sp[:camera]
|
||||
kw[:view] = "{$(azim)}{$(elev)}"
|
||||
end
|
||||
|
||||
axisf = PGFPlots.Axis
|
||||
if sp[:projection] == :polar
|
||||
axisf = PGFPlots.PolarAxis
|
||||
#make radial axis vertical
|
||||
kw[:xmin] = 90
|
||||
kw[:xmax] = 450
|
||||
end
|
||||
|
||||
# Search series for any gradient. In case one series uses a gradient set
|
||||
# the colorbar and colomap.
|
||||
# The reasoning behind doing this on the axis level is that pgfplots
|
||||
# colorbar seems to only works on axis level and needs the proper colormap for
|
||||
# correctly displaying it.
|
||||
# It's also possible to assign the colormap to the series itself but
|
||||
# then the colormap needs to be added twice, once for the axis and once for the
|
||||
# series.
|
||||
# As it is likely that all series within the same axis use the same
|
||||
# colormap this should not cause any problem.
|
||||
for series in series_list(sp)
|
||||
for col in (:markercolor, :fillcolor, :linecolor)
|
||||
if typeof(series.d[col]) == ColorGradient
|
||||
push!(style,"colormap={plots}{$(pgf_colormap(series.d[col]))}")
|
||||
|
||||
if sp[:colorbar] == :none
|
||||
kw[:colorbar] = "false"
|
||||
else
|
||||
kw[:colorbar] = "true"
|
||||
end
|
||||
# goto is needed to break out of col and series for
|
||||
@goto colorbar_end
|
||||
end
|
||||
end
|
||||
end
|
||||
@label colorbar_end
|
||||
|
||||
o = axisf(; style = join(style, ","), kw...)
|
||||
|
||||
# add the series object to the PGFPlots.Axis
|
||||
for series in series_list(sp)
|
||||
push!(o, pgf_series(sp, series))
|
||||
push!.(o, pgf_series(sp, series))
|
||||
|
||||
# add series annotations
|
||||
anns = series[:series_annotations]
|
||||
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y])
|
||||
pgf_add_annotation!(o, xi, yi, PlotText(str, fnt), pgf_thickness_scaling(series))
|
||||
end
|
||||
end
|
||||
|
||||
# add the annotations
|
||||
for ann in sp[:annotations]
|
||||
pgf_add_annotation!(o, locate_annotation(sp, ann...)..., pgf_thickness_scaling(sp))
|
||||
end
|
||||
|
||||
|
||||
# add the PGFPlots.Axis to the list
|
||||
push!(plt.o, o)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _writemime(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend})
|
||||
_make_pgf_plot!(plt)
|
||||
writemime(io, mime, plt.o)
|
||||
function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend})
|
||||
show(io, mime, plt.o)
|
||||
end
|
||||
|
||||
function _writemime(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBackend})
|
||||
_make_pgf_plot!(plt)
|
||||
|
||||
function _show(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBackend})
|
||||
# prepare the object
|
||||
pgfplt = PGFPlots.plot(plt.o)
|
||||
|
||||
@@ -298,15 +587,20 @@ function _writemime(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBacke
|
||||
PGFPlots.save(PGFPlots.PDF(fn), pgfplt)
|
||||
|
||||
# read it into io
|
||||
write(io, readall(open(fn)))
|
||||
write(io, read(open(fn), String))
|
||||
|
||||
# cleanup
|
||||
PGFPlots.cleanup(plt.o)
|
||||
end
|
||||
|
||||
function _show(io::IO, mime::MIME"application/x-tex", plt::Plot{PGFPlotsBackend})
|
||||
fn = tempname()*".tex"
|
||||
PGFPlots.save(fn, backend_object(plt), include_preamble=false)
|
||||
write(io, read(open(fn), String))
|
||||
end
|
||||
|
||||
function _display(plt::Plot{PGFPlotsBackend})
|
||||
# prepare the object
|
||||
_make_pgf_plot!(plt)
|
||||
pgfplt = PGFPlots.plot(plt.o)
|
||||
|
||||
# save an svg
|
||||
|
||||
+592
-228
File diff suppressed because it is too large
Load Diff
+59
-32
@@ -1,49 +1,45 @@
|
||||
|
||||
# https://github.com/spencerlyon2/PlotlyJS.jl
|
||||
|
||||
supported_args(::PlotlyJSBackend) = supported_args(PlotlyBackend())
|
||||
supported_types(::PlotlyJSBackend) = supported_types(PlotlyBackend())
|
||||
supported_styles(::PlotlyJSBackend) = supported_styles(PlotlyBackend())
|
||||
supported_markers(::PlotlyJSBackend) = supported_markers(PlotlyBackend())
|
||||
is_subplot_supported(::PlotlyJSBackend) = true
|
||||
is_string_supported(::PlotlyJSBackend) = true
|
||||
const _plotlyjs_attr = _plotly_attr
|
||||
const _plotlyjs_seriestype = _plotly_seriestype
|
||||
const _plotlyjs_style = _plotly_style
|
||||
const _plotlyjs_marker = _plotly_marker
|
||||
const _plotlyjs_scale = _plotly_scale
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::PlotlyJSBackend; kw...)
|
||||
@eval begin
|
||||
import PlotlyJS
|
||||
export PlotlyJS
|
||||
end
|
||||
|
||||
# # override IJulia inline display
|
||||
# if isijulia()
|
||||
# IJulia.display_dict(plt::AbstractPlot{PlotlyJSBackend}) = IJulia.display_dict(plt.o)
|
||||
# end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _create_backend_figure(plt::Plot{PlotlyJSBackend})
|
||||
PlotlyJS.plot()
|
||||
if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot)
|
||||
PlotlyJS.SyncPlot(PlotlyJS.Plot(), current().o.view)
|
||||
else
|
||||
PlotlyJS.plot()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _series_added(plt::Plot{PlotlyJSBackend}, series::Series)
|
||||
syncplot = plt.o
|
||||
pdict = plotly_series(plt, series)
|
||||
typ = pop!(pdict, :type)
|
||||
gt = PlotlyJS.GenericTrace(typ; pdict...)
|
||||
PlotlyJS.addtraces!(syncplot, gt)
|
||||
pdicts = plotly_series(plt, series)
|
||||
for pdict in pdicts
|
||||
typ = pop!(pdict, :type)
|
||||
gt = PlotlyJS.GenericTrace(typ; pdict...)
|
||||
PlotlyJS.addtraces!(syncplot, gt)
|
||||
end
|
||||
end
|
||||
|
||||
function _series_updated(plt::Plot{PlotlyJSBackend}, series::Series)
|
||||
xsym, ysym = (ispolar(series) ? (:t,:r) : (:x,:y))
|
||||
kw = KW(xsym => (series.d[:x],), ysym => (series.d[:y],))
|
||||
z = series[:z]
|
||||
if z != nothing
|
||||
kw[:z] = (isa(z,Surface) ? transpose_z(series, series[:z].surf, false) : z,)
|
||||
end
|
||||
PlotlyJS.restyle!(
|
||||
plt.o,
|
||||
findfirst(plt.series_list, series),
|
||||
KW(xsym => (series.d[:x],), ysym => (series.d[:y],))
|
||||
findfirst(isequal(series), plt.series_list),
|
||||
kw
|
||||
)
|
||||
end
|
||||
|
||||
@@ -60,16 +56,47 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _writemime(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend})
|
||||
writemime(io, MIME("text/html"), plt.o)
|
||||
function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend})
|
||||
if isijulia() && !_use_remote[]
|
||||
write(io, PlotlyJS.html_body(PlotlyJS.JupyterPlot(plt.o)))
|
||||
else
|
||||
show(io, MIME("text/html"), plt.o)
|
||||
end
|
||||
end
|
||||
|
||||
function _writemime(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend})
|
||||
tmpfn = tempname() * ".png"
|
||||
function plotlyjs_save_hack(io::IO, plt::Plot{PlotlyJSBackend}, ext::String)
|
||||
tmpfn = tempname() * "." * ext
|
||||
PlotlyJS.savefig(plt.o, tmpfn)
|
||||
write(io, read(open(tmpfn)))
|
||||
end
|
||||
_show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "svg")
|
||||
_show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "png")
|
||||
_show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "pdf")
|
||||
_show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "eps")
|
||||
|
||||
function write_temp_html(plt::Plot{PlotlyJSBackend})
|
||||
filename = string(tempname(), ".html")
|
||||
savefig(plt, filename)
|
||||
filename
|
||||
end
|
||||
|
||||
function _display(plt::Plot{PlotlyJSBackend})
|
||||
display(plt.o)
|
||||
if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes")
|
||||
display(plt.o)
|
||||
else
|
||||
standalone_html_window(plt)
|
||||
end
|
||||
end
|
||||
|
||||
@require WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" begin
|
||||
function WebIO.render(plt::Plot{PlotlyJSBackend})
|
||||
prepare_output(plt)
|
||||
WebIO.render(plt.o)
|
||||
end
|
||||
end
|
||||
|
||||
function closeall(::PlotlyJSBackend)
|
||||
if !isplotnull() && isa(current().o, PlotlyJS.SyncPlot)
|
||||
close(current().o)
|
||||
end
|
||||
end
|
||||
|
||||
+606
-545
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,9 @@
|
||||
|
||||
# [ADD BACKEND WEBSITE]
|
||||
|
||||
function _initialize_backend(::[PkgName]Backend; kw...)
|
||||
@eval begin
|
||||
import [PkgName]
|
||||
export [PkgName]
|
||||
# todo: other initialization that needs to be eval-ed
|
||||
end
|
||||
# todo: other initialization
|
||||
end
|
||||
import [PkgName]
|
||||
export [PkgName]
|
||||
push!(_initialized_backends, [pgkname]::Symbol)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -67,7 +62,7 @@ end
|
||||
# "image/png" => "png",
|
||||
# "application/postscript" => "ps",
|
||||
# "image/svg+xml" => "svg"
|
||||
function _writemime(io::IO, ::MIME"image/png", plt::Plot{[PkgName]Backend})
|
||||
function _show(io::IO, ::MIME"image/png", plt::Plot{[PkgName]Backend})
|
||||
end
|
||||
|
||||
# Display/show the plot (open a GUI window, or browser page, for example).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# https://github.com/Evizero/UnicodePlots.jl
|
||||
|
||||
supported_args(::UnicodePlotsBackend) = merge_with_base_supported([
|
||||
const _unicodeplots_attr = merge_with_base_supported([
|
||||
:label,
|
||||
:legend,
|
||||
:seriescolor,
|
||||
@@ -12,33 +12,38 @@ supported_args(::UnicodePlotsBackend) = merge_with_base_supported([
|
||||
:title,
|
||||
:guide, :lims,
|
||||
])
|
||||
supported_types(::UnicodePlotsBackend) = [
|
||||
:path, :scatter,
|
||||
:histogram2d
|
||||
const _unicodeplots_seriestype = [
|
||||
:path, :scatter, :straightline,
|
||||
# :bar,
|
||||
:shape,
|
||||
:histogram2d,
|
||||
:spy
|
||||
]
|
||||
supported_styles(::UnicodePlotsBackend) = [:auto, :solid]
|
||||
supported_markers(::UnicodePlotsBackend) = [:none, :auto, :circle]
|
||||
supported_scales(::UnicodePlotsBackend) = [:identity]
|
||||
is_subplot_supported(::UnicodePlotsBackend) = true
|
||||
const _unicodeplots_style = [:auto, :solid]
|
||||
const _unicodeplots_marker = [:none, :auto, :circle]
|
||||
const _unicodeplots_scale = [:identity]
|
||||
|
||||
|
||||
# don't warn on unsupported... there's just too many warnings!!
|
||||
warnOnUnsupported_args(pkg::UnicodePlotsBackend, d::KW) = nothing
|
||||
warnOnUnsupported_args(::UnicodePlotsBackend, d::KW) = nothing
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::UnicodePlotsBackend; kw...)
|
||||
@eval begin
|
||||
import UnicodePlots
|
||||
export UnicodePlots
|
||||
end
|
||||
end
|
||||
const _canvas_type = Ref(:auto)
|
||||
|
||||
# -------------------------------
|
||||
function _canvas_map()
|
||||
KW(
|
||||
:braille => UnicodePlots.BrailleCanvas,
|
||||
:ascii => UnicodePlots.AsciiCanvas,
|
||||
:block => UnicodePlots.BlockCanvas,
|
||||
:dot => UnicodePlots.DotCanvas,
|
||||
:density => UnicodePlots.DensityCanvas,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
# do all the magic here... build it all at once, since we need to know about all the series at the very beginning
|
||||
function rebuildUnicodePlot!(plt::Plot)
|
||||
function rebuildUnicodePlot!(plt::Plot, width, height)
|
||||
plt.o = []
|
||||
|
||||
for sp in plt.subplots
|
||||
@@ -57,8 +62,33 @@ function rebuildUnicodePlot!(plt::Plot)
|
||||
y = Float64[ylim[1]]
|
||||
|
||||
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
|
||||
width, height = plt[:size]
|
||||
canvas_type = isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas
|
||||
ct = _canvas_type[]
|
||||
canvas_type = if ct == :auto
|
||||
isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas
|
||||
else
|
||||
_canvas_map()[ct]
|
||||
end
|
||||
|
||||
# special handling for spy
|
||||
if length(sp.series_list) == 1
|
||||
series = sp.series_list[1]
|
||||
if series[:seriestype] == :spy
|
||||
push!(plt.o, UnicodePlots.spy(
|
||||
series[:z].surf,
|
||||
width = width,
|
||||
height = height,
|
||||
title = sp[:title],
|
||||
canvas = canvas_type
|
||||
))
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
# # make it a bar canvas if plotting bar
|
||||
# if any(series -> series[:seriestype] == :bar, series_list(sp))
|
||||
# canvas_type = UnicodePlots.BarplotGraphics
|
||||
# end
|
||||
|
||||
o = UnicodePlots.Plot(x, y, canvas_type;
|
||||
width = width,
|
||||
height = height,
|
||||
@@ -92,28 +122,39 @@ function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
|
||||
return
|
||||
end
|
||||
|
||||
if st == :path
|
||||
if st in (:path, :straightline)
|
||||
func = UnicodePlots.lineplot!
|
||||
elseif st == :scatter || d[:markershape] != :none
|
||||
func = UnicodePlots.scatterplot!
|
||||
# elseif st == :bar
|
||||
# func = UnicodePlots.barplot!
|
||||
elseif st == :shape
|
||||
func = UnicodePlots.lineplot!
|
||||
else
|
||||
error("Linestyle $st not supported by UnicodePlots")
|
||||
end
|
||||
|
||||
# get the series data and label
|
||||
x, y = [collect(float(d[s])) for s in (:x, :y)]
|
||||
x, y = if st == :straightline
|
||||
straightline_data(d)
|
||||
elseif st == :shape
|
||||
shape_data(series)
|
||||
else
|
||||
[collect(float(d[s])) for s in (:x, :y)]
|
||||
end
|
||||
label = addlegend ? d[:label] : ""
|
||||
|
||||
# if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide
|
||||
color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto
|
||||
|
||||
# add the series
|
||||
x, y = Plots.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
|
||||
func(o, x, y; color = color, name = label)
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# since this is such a hack, it's only callable using `png`... should error during normal `writemime`
|
||||
# since this is such a hack, it's only callable using `png`... should error during normal `show`
|
||||
function png(plt::AbstractPlot{UnicodePlotsBackend}, fn::AbstractString)
|
||||
fn = addExtension(fn, "png")
|
||||
|
||||
@@ -122,7 +163,7 @@ function png(plt::AbstractPlot{UnicodePlotsBackend}, fn::AbstractString)
|
||||
gui(plt)
|
||||
|
||||
# @osx_only begin
|
||||
@compat @static if is_apple()
|
||||
@static if Sys.isapple()
|
||||
# BEGIN HACK
|
||||
|
||||
# wait while the plot gets drawn
|
||||
@@ -143,21 +184,21 @@ end
|
||||
|
||||
# we don't do very much for subplots... just stack them vertically
|
||||
|
||||
function _update_plot_object(plt::Plot{UnicodePlotsBackend})
|
||||
w, h = plt[:size]
|
||||
plt.attr[:size] = div(w, 10), div(h, 20)
|
||||
plt.attr[:color_palette] = [RGB(0,0,0)]
|
||||
rebuildUnicodePlot!(plt)
|
||||
function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
|
||||
w, h = plt[:size]
|
||||
plt.attr[:color_palette] = [RGB(0,0,0)]
|
||||
rebuildUnicodePlot!(plt, div(w, 10), div(h, 20))
|
||||
end
|
||||
|
||||
function _writemime(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
|
||||
map(show, plt.o)
|
||||
nothing
|
||||
function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
|
||||
unicodeplots_rebuild(plt)
|
||||
foreach(x -> show(io, x), plt.o)
|
||||
nothing
|
||||
end
|
||||
|
||||
|
||||
function _display(plt::Plot{UnicodePlotsBackend})
|
||||
map(show, plt.o)
|
||||
nothing
|
||||
unicodeplots_rebuild(plt)
|
||||
map(show, plt.o)
|
||||
nothing
|
||||
end
|
||||
|
||||
|
||||
+7
-7
@@ -3,13 +3,13 @@
|
||||
|
||||
# CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl
|
||||
|
||||
|
||||
function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr, :window_title, "Plots.jl"))
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>$title</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
$(html_head(plt))
|
||||
</head>
|
||||
<body>
|
||||
@@ -20,16 +20,16 @@ function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr
|
||||
end
|
||||
|
||||
function open_browser_window(filename::AbstractString)
|
||||
@compat @static if is_apple()
|
||||
@static if Sys.isapple()
|
||||
return run(`open $(filename)`)
|
||||
end
|
||||
@compat @static if is_linux()
|
||||
@static if Sys.islinux() || Sys.isbsd() # Sys.isbsd() addition is as yet untested, but based on suggestion in https://github.com/JuliaPlots/Plots.jl/issues/681
|
||||
return run(`xdg-open $(filename)`)
|
||||
end
|
||||
@compat @static if is_windows()
|
||||
return run(`$(ENV["COMSPEC"]) /c start $(filename)`)
|
||||
@static if Sys.iswindows()
|
||||
return run(`$(ENV["COMSPEC"]) /c start "" "$(filename)"`)
|
||||
end
|
||||
warn("Unknown OS... cannot open browser window.")
|
||||
@warn("Unknown OS... cannot open browser window.")
|
||||
end
|
||||
|
||||
function write_temp_html(plt::AbstractPlot)
|
||||
@@ -51,7 +51,7 @@ function html_to_png(html_fn, png_fn, w, h)
|
||||
run(`wkhtmltoimage -f png -q --width $w --height $h --disable-smart-width $html_fn $png_fn`)
|
||||
end
|
||||
|
||||
function writemime_png_from_html(io::IO, plt::AbstractPlot)
|
||||
function show_png_from_html(io::IO, plt::AbstractPlot)
|
||||
# write html to a temporary file
|
||||
html_fn = write_temp_html(plt)
|
||||
|
||||
|
||||
+349
-101
@@ -1,43 +1,54 @@
|
||||
|
||||
|
||||
typealias P2 FixedSizeArrays.Vec{2,Float64}
|
||||
typealias P3 FixedSizeArrays.Vec{3,Float64}
|
||||
const P2 = FixedSizeArrays.Vec{2,Float64}
|
||||
const P3 = FixedSizeArrays.Vec{3,Float64}
|
||||
|
||||
nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b))
|
||||
nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b))
|
||||
nanpush!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); push!(a, b))
|
||||
nanappend!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); append!(a, b))
|
||||
compute_angle(v::P2) = (angle = atan2(v[2], v[1]); angle < 0 ? 2π - angle : angle)
|
||||
compute_angle(v::P2) = (angle = atan(v[2], v[1]); angle < 0 ? 2π - angle : angle)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
||||
immutable Shape
|
||||
struct Shape
|
||||
x::Vector{Float64}
|
||||
y::Vector{Float64}
|
||||
function Shape(x::AVec, y::AVec)
|
||||
if x[1] != x[end] || y[1] != y[end]
|
||||
new(vcat(x, x[1]), vcat(y, y[1]))
|
||||
else
|
||||
new(x, y)
|
||||
end
|
||||
end
|
||||
# function Shape(x::AVec, y::AVec)
|
||||
# # if x[1] != x[end] || y[1] != y[end]
|
||||
# # new(vcat(x, x[1]), vcat(y, y[1]))
|
||||
# # else
|
||||
# new(x, y)
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
"""
|
||||
Shape(x, y)
|
||||
Shape(vertices)
|
||||
|
||||
Construct a polygon to be plotted
|
||||
"""
|
||||
Shape(verts::AVec) = Shape(unzip(verts)...)
|
||||
Shape(s::Shape) = deepcopy(s)
|
||||
|
||||
get_xs(shape::Shape) = shape.x
|
||||
get_ys(shape::Shape) = shape.y
|
||||
vertices(shape::Shape) = collect(zip(shape.x, shape.y))
|
||||
|
||||
#deprecated
|
||||
@deprecate shape_coords coords
|
||||
|
||||
function shape_coords(shape::Shape)
|
||||
"return the vertex points from a Shape or Segments object"
|
||||
function coords(shape::Shape)
|
||||
shape.x, shape.y
|
||||
end
|
||||
|
||||
function shape_coords(shapes::AVec{Shape})
|
||||
function coords(shapes::AVec{Shape})
|
||||
length(shapes) == 0 && return zeros(0), zeros(0)
|
||||
xs = map(get_xs, shapes)
|
||||
ys = map(get_ys, shapes)
|
||||
x, y = map(copy, shape_coords(shapes[1]))
|
||||
x, y = map(copy, coords(shapes[1]))
|
||||
for shape in shapes[2:end]
|
||||
nanappend!(x, shape.x)
|
||||
nanappend!(y, shape.y)
|
||||
@@ -47,7 +58,7 @@ end
|
||||
|
||||
"get an array of tuples of points on a circle with radius `r`"
|
||||
function partialcircle(start_θ, end_θ, n = 20, r=1)
|
||||
@compat(Tuple{Float64,Float64})[(r*cos(u),r*sin(u)) for u in linspace(start_θ, end_θ, n)]
|
||||
Tuple{Float64,Float64}[(r*cos(u),r*sin(u)) for u in range(start_θ, stop=end_θ, length=n)]
|
||||
end
|
||||
|
||||
"interleave 2 vectors into each other (like a zipper's teeth)"
|
||||
@@ -57,7 +68,8 @@ function weave(x,y; ordering = Vector[x,y])
|
||||
while !done
|
||||
for o in ordering
|
||||
try
|
||||
push!(ret, shift!(o))
|
||||
push!(ret, popfirst!(o))
|
||||
catch
|
||||
end
|
||||
end
|
||||
done = isempty(x) && isempty(y)
|
||||
@@ -72,13 +84,13 @@ function makestar(n; offset = -0.5, radius = 1.0)
|
||||
z2 = z1 + π / (n)
|
||||
outercircle = partialcircle(z1, z1 + 2π, n+1, radius)
|
||||
innercircle = partialcircle(z2, z2 + 2π, n+1, 0.4radius)
|
||||
Shape(weave(outercircle, innercircle)[1:end-2])
|
||||
Shape(weave(outercircle, innercircle))
|
||||
end
|
||||
|
||||
"create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle"
|
||||
function makeshape(n; offset = -0.5, radius = 1.0)
|
||||
z = offset * π
|
||||
Shape(partialcircle(z, z + 2π, n+1, radius)[1:end-1])
|
||||
Shape(partialcircle(z, z + 2π, n+1, radius))
|
||||
end
|
||||
|
||||
|
||||
@@ -88,7 +100,7 @@ function makecross(; offset = -0.5, radius = 1.0)
|
||||
outercircle = partialcircle(z1, z1 + 2π, 9, radius)
|
||||
innercircle = partialcircle(z2, z2 + 2π, 5, 0.5radius)
|
||||
Shape(weave(outercircle, innercircle,
|
||||
ordering=Vector[outercircle,innercircle,outercircle])[1:end-2])
|
||||
ordering=Vector[outercircle,innercircle,outercircle]))
|
||||
end
|
||||
|
||||
|
||||
@@ -110,6 +122,8 @@ const _shape_keys = Symbol[
|
||||
:xcross,
|
||||
:utriangle,
|
||||
:dtriangle,
|
||||
:rtriangle,
|
||||
:ltriangle,
|
||||
:pentagon,
|
||||
:heptagon,
|
||||
:octagon,
|
||||
@@ -119,14 +133,18 @@ const _shape_keys = Symbol[
|
||||
:star8,
|
||||
:vline,
|
||||
:hline,
|
||||
:+,
|
||||
:x,
|
||||
]
|
||||
|
||||
const _shapes = KW(
|
||||
:circle => makeshape(20),
|
||||
:rect => makeshape(4, offset=-0.25),
|
||||
:diamond => makeshape(4),
|
||||
:utriangle => makeshape(3),
|
||||
:dtriangle => makeshape(3, offset=0.5),
|
||||
:utriangle => makeshape(3, offset=0.5),
|
||||
:dtriangle => makeshape(3, offset=-0.5),
|
||||
:rtriangle => makeshape(3, offset=0.0),
|
||||
:ltriangle => makeshape(3, offset=1.0),
|
||||
:pentagon => makeshape(5),
|
||||
:hexagon => makeshape(6),
|
||||
:heptagon => makeshape(7),
|
||||
@@ -141,12 +159,15 @@ for n in [4,5,6,7,8]
|
||||
_shapes[Symbol("star$n")] = makestar(n)
|
||||
end
|
||||
|
||||
Shape(k::Symbol) = deepcopy(_shapes[k])
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
# uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
|
||||
"return the centroid of a Shape"
|
||||
function center(shape::Shape)
|
||||
x, y = shape_coords(shape)
|
||||
x, y = coords(shape)
|
||||
n = length(x)
|
||||
A, Cx, Cy = 0.0, 0.0, 0.0
|
||||
for i=1:n
|
||||
@@ -163,8 +184,8 @@ function center(shape::Shape)
|
||||
Cx / 6A, Cy / 6A
|
||||
end
|
||||
|
||||
function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
sx, sy = shape_coords(shape)
|
||||
function scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
sx, sy = coords(shape)
|
||||
cx, cy = c
|
||||
for i=1:length(sx)
|
||||
sx[i] = (sx[i] - cx) * x + cx
|
||||
@@ -173,13 +194,14 @@ function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
shape
|
||||
end
|
||||
|
||||
function Base.scale(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
function scale(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
shapecopy = deepcopy(shape)
|
||||
scale!(shape, x, y, c)
|
||||
scale!(shapecopy, x, y, c)
|
||||
end
|
||||
|
||||
"translate a Shape in space"
|
||||
function translate!(shape::Shape, x::Real, y::Real = x)
|
||||
sx, sy = shape_coords(shape)
|
||||
sx, sy = coords(shape)
|
||||
for i=1:length(sx)
|
||||
sx[i] += x
|
||||
sy[i] += y
|
||||
@@ -189,7 +211,7 @@ end
|
||||
|
||||
function translate(shape::Shape, x::Real, y::Real = x)
|
||||
shapecopy = deepcopy(shape)
|
||||
translate!(shape, x, y)
|
||||
translate!(shapecopy, x, y)
|
||||
end
|
||||
|
||||
function rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real)
|
||||
@@ -206,15 +228,17 @@ function rotate(x::Real, y::Real, θ::Real, c = center(shape))
|
||||
end
|
||||
|
||||
function rotate!(shape::Shape, Θ::Real, c = center(shape))
|
||||
x, y = shape_coords(shape)
|
||||
x, y = coords(shape)
|
||||
cx, cy = c
|
||||
for i=1:length(x)
|
||||
x[i] = rotate_x(x[i], y[i], Θ, cx, cy)
|
||||
y[i] = rotate_y(x[i], y[i], Θ, cx, cy)
|
||||
xi = rotate_x(x[i], y[i], Θ, cx, cy)
|
||||
yi = rotate_y(x[i], y[i], Θ, cx, cy)
|
||||
x[i], y[i] = xi, yi
|
||||
end
|
||||
shape
|
||||
end
|
||||
|
||||
"rotate an object in space"
|
||||
function rotate(shape::Shape, Θ::Real, c = center(shape))
|
||||
shapecopy = deepcopy(shape)
|
||||
rotate!(shapecopy, Θ, c)
|
||||
@@ -223,7 +247,7 @@ end
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
immutable Font
|
||||
mutable struct Font
|
||||
family::AbstractString
|
||||
pointsize::Int
|
||||
halign::Symbol
|
||||
@@ -236,7 +260,7 @@ end
|
||||
function font(args...)
|
||||
|
||||
# defaults
|
||||
family = "Helvetica"
|
||||
family = "sans-serif"
|
||||
pointsize = 14
|
||||
halign = :hcenter
|
||||
valign = :vcenter
|
||||
@@ -273,47 +297,86 @@ function font(args...)
|
||||
elseif typeof(arg) <: Real
|
||||
rotation = convert(Float64, arg)
|
||||
else
|
||||
warn("Unused font arg: $arg ($(typeof(arg)))")
|
||||
@warn("Unused font arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
Font(family, pointsize, halign, valign, rotation, color)
|
||||
end
|
||||
|
||||
function scalefontsize(k::Symbol, factor::Number)
|
||||
f = default(k)
|
||||
f = round(Int, factor * f)
|
||||
default(k, f)
|
||||
end
|
||||
|
||||
"""
|
||||
scalefontsizes(factor::Number)
|
||||
|
||||
Scales all **current** font sizes by `factor`. For example `scalefontsizes(1.1)` increases all current font sizes by 10%. To reset to initial sizes, use `scalefontsizes()`
|
||||
"""
|
||||
function scalefontsizes(factor::Number)
|
||||
for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize)
|
||||
scalefontsize(k, factor)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
scalefontsizes()
|
||||
|
||||
Resets font sizes to initial default values.
|
||||
"""
|
||||
function scalefontsizes()
|
||||
for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize)
|
||||
f = default(k)
|
||||
if k in keys(_initial_fontsizes)
|
||||
factor = f / _initial_fontsizes[k]
|
||||
scalefontsize(k, 1.0/factor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"Wrap a string with font info"
|
||||
immutable PlotText
|
||||
struct PlotText
|
||||
str::AbstractString
|
||||
font::Font
|
||||
end
|
||||
PlotText(str) = PlotText(string(str), font())
|
||||
|
||||
"""
|
||||
text(string, args...)
|
||||
|
||||
Create a PlotText object wrapping a string with font info, for plot annotations
|
||||
"""
|
||||
text(t::PlotText) = t
|
||||
text(t::PlotText, font::Font) = PlotText(t.str, font)
|
||||
text(str::AbstractString, f::Font) = PlotText(str, f)
|
||||
function text(str, args...)
|
||||
PlotText(string(str), font(args...))
|
||||
end
|
||||
|
||||
|
||||
annotations(::Void) = []
|
||||
annotations(anns::AVec) = anns
|
||||
annotations(anns) = Any[anns]
|
||||
|
||||
Base.length(t::PlotText) = length(t.str)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
immutable Stroke
|
||||
struct Stroke
|
||||
width
|
||||
color
|
||||
alpha
|
||||
style
|
||||
end
|
||||
|
||||
"""
|
||||
stroke(args...; alpha = nothing)
|
||||
|
||||
Define the properties of the stroke used in plotting lines
|
||||
"""
|
||||
function stroke(args...; alpha = nothing)
|
||||
width = nothing
|
||||
color = nothing
|
||||
style = nothing
|
||||
width = 1
|
||||
color = :black
|
||||
style = :solid
|
||||
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
@@ -326,13 +389,14 @@ function stroke(args...; alpha = nothing)
|
||||
elseif T <: Symbol || T <: AbstractString
|
||||
try
|
||||
color = parse(Colorant, string(arg))
|
||||
catch
|
||||
end
|
||||
elseif allAlphas(arg)
|
||||
alpha = arg
|
||||
elseif allReals(arg)
|
||||
width = arg
|
||||
else
|
||||
warn("Unused stroke arg: $arg ($(typeof(arg)))")
|
||||
@warn("Unused stroke arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -340,15 +404,15 @@ function stroke(args...; alpha = nothing)
|
||||
end
|
||||
|
||||
|
||||
immutable Brush
|
||||
struct Brush
|
||||
size # fillrange, markersize, or any other sizey attribute
|
||||
color
|
||||
alpha
|
||||
end
|
||||
|
||||
function brush(args...; alpha = nothing)
|
||||
size = nothing
|
||||
color = nothing
|
||||
size = 1
|
||||
color = :black
|
||||
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
@@ -358,13 +422,14 @@ function brush(args...; alpha = nothing)
|
||||
elseif T <: Symbol || T <: AbstractString
|
||||
try
|
||||
color = parse(Colorant, string(arg))
|
||||
catch
|
||||
end
|
||||
elseif allAlphas(arg)
|
||||
alpha = arg
|
||||
elseif allReals(arg)
|
||||
size = arg
|
||||
else
|
||||
warn("Unused brush arg: $arg ($(typeof(arg)))")
|
||||
@warn("Unused brush arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -373,22 +438,177 @@ end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
mutable struct SeriesAnnotations
|
||||
strs::AbstractVector # the labels/names
|
||||
font::Font
|
||||
baseshape::Union{Shape, AbstractVector{Shape}, Nothing}
|
||||
scalefactor::Tuple
|
||||
end
|
||||
function series_annotations(strs::AbstractVector, args...)
|
||||
fnt = font()
|
||||
shp = nothing
|
||||
scalefactor = (1,1)
|
||||
for arg in args
|
||||
if isa(arg, Shape) || (isa(arg, AbstractVector) && eltype(arg) == Shape)
|
||||
shp = arg
|
||||
elseif isa(arg, Font)
|
||||
fnt = arg
|
||||
elseif isa(arg, Symbol) && haskey(_shapes, arg)
|
||||
shp = _shapes[arg]
|
||||
elseif isa(arg, Number)
|
||||
scalefactor = (arg,arg)
|
||||
elseif is_2tuple(arg)
|
||||
scalefactor = arg
|
||||
else
|
||||
@warn("Unused SeriesAnnotations arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
# if scalefactor != 1
|
||||
# for s in get(shp)
|
||||
# scale!(s, scalefactor, scalefactor, (0,0))
|
||||
# end
|
||||
# end
|
||||
SeriesAnnotations(strs, fnt, shp, scalefactor)
|
||||
end
|
||||
series_annotations(anns::SeriesAnnotations) = anns
|
||||
series_annotations(::Nothing) = nothing
|
||||
|
||||
function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
|
||||
anns = series[:series_annotations]
|
||||
# msw,msh = anns.scalefactor
|
||||
# ms = series[:markersize]
|
||||
# msw,msh = if isa(ms, AbstractVector)
|
||||
# 1,1
|
||||
# elseif is_2tuple(ms)
|
||||
# ms
|
||||
# else
|
||||
# ms,ms
|
||||
# end
|
||||
|
||||
# @show msw msh
|
||||
if anns != nothing && anns.baseshape != nothing
|
||||
# we use baseshape to overwrite the markershape attribute
|
||||
# with a list of custom shapes for each
|
||||
msw,msh = anns.scalefactor
|
||||
msize = Float64[]
|
||||
shapes = Vector{Shape}(length(anns.strs))
|
||||
for i in eachindex(anns.strs)
|
||||
str = _cycle(anns.strs,i)
|
||||
|
||||
# get the width and height of the string (in mm)
|
||||
sw, sh = text_size(str, anns.font.pointsize)
|
||||
|
||||
# how much to scale the base shape?
|
||||
# note: it's a rough assumption that the shape fills the unit box [-1,-1,1,1],
|
||||
# so we scale the length-2 shape by 1/2 the total length
|
||||
scalar = (backend() == PyPlotBackend() ? 1.7 : 1.0)
|
||||
xscale = 0.5to_pixels(sw) * scalar
|
||||
yscale = 0.5to_pixels(sh) * scalar
|
||||
|
||||
# we save the size of the larger direction to the markersize list,
|
||||
# and then re-scale a copy of baseshape to match the w/h ratio
|
||||
maxscale = max(xscale, yscale)
|
||||
push!(msize, maxscale)
|
||||
baseshape = _cycle(get(anns.baseshape),i)
|
||||
shapes[i] = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0))
|
||||
end
|
||||
series[:markershape] = shapes
|
||||
series[:markersize] = msize
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
mutable struct EachAnn
|
||||
anns
|
||||
x
|
||||
y
|
||||
end
|
||||
|
||||
function Base.iterate(ea::EachAnn, i = 1)
|
||||
if ea.anns == nothing || isempty(ea.anns.strs) || i > length(ea.y)
|
||||
return nothing
|
||||
end
|
||||
|
||||
tmp = _cycle(ea.anns.strs,i)
|
||||
str,fnt = if isa(tmp, PlotText)
|
||||
tmp.str, tmp.font
|
||||
else
|
||||
tmp, ea.anns.font
|
||||
end
|
||||
((_cycle(ea.x,i), _cycle(ea.y,i), str, fnt), i+1)
|
||||
end
|
||||
|
||||
annotations(::Nothing) = []
|
||||
annotations(anns::AVec) = anns
|
||||
annotations(anns) = Any[anns]
|
||||
annotations(sa::SeriesAnnotations) = sa
|
||||
|
||||
# Expand arrays of coordinates, positions and labels into induvidual annotations
|
||||
# and make sure labels are of type PlotText
|
||||
function process_annotation(sp::Subplot, xs, ys, labs, font = font())
|
||||
anns = []
|
||||
labs = makevec(labs)
|
||||
for i in 1:max(length(xs), length(ys), length(labs))
|
||||
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
|
||||
if lab == :auto
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
|
||||
else
|
||||
push!(anns, (x, y, isa(lab, PlotText) ? lab : text(lab, font)))
|
||||
end
|
||||
end
|
||||
anns
|
||||
end
|
||||
function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol}, labs, font = font())
|
||||
anns = []
|
||||
positions, labs = makevec(positions), makevec(labs)
|
||||
for i in 1:max(length(positions), length(labs))
|
||||
pos, lab = _cycle(positions, i), _cycle(labs, i)
|
||||
pos = get(_positionAliases, pos, pos)
|
||||
if lab == :auto
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
push!(anns, (pos, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
|
||||
else
|
||||
push!(anns, (pos, isa(lab, PlotText) ? lab : text(lab, font)))
|
||||
end
|
||||
end
|
||||
anns
|
||||
end
|
||||
|
||||
# Give each annotation coordinates based on specified position
|
||||
function locate_annotation(sp::Subplot, pos::Symbol, lab::PlotText)
|
||||
position_multiplier = Dict{Symbol, Tuple{Float64,Float64}}(
|
||||
:topleft => (0.1, 0.9),
|
||||
:topcenter => (0.5, 0.9),
|
||||
:topright => (0.9, 0.9),
|
||||
:bottomleft => (0.1, 0.1),
|
||||
:bottomcenter => (0.5, 0.1),
|
||||
:bottomright => (0.9, 0.1),
|
||||
)
|
||||
xmin, xmax = ignorenan_extrema(sp[:xaxis])
|
||||
ymin, ymax = ignorenan_extrema(sp[:yaxis])
|
||||
x, y = (xmin, ymin).+ position_multiplier[pos].* (xmax - xmin, ymax - ymin)
|
||||
(x, y, lab)
|
||||
end
|
||||
locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label)
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"type which represents z-values for colors and sizes (and anything else that might come up)"
|
||||
immutable ZValues
|
||||
struct ZValues
|
||||
values::Vector{Float64}
|
||||
zrange::Tuple{Float64,Float64}
|
||||
end
|
||||
|
||||
function zvalues{T<:Real}(values::AVec{T}, zrange::Tuple{T,T} = (minimum(values), maximum(values)))
|
||||
function zvalues(values::AVec{T}, zrange::Tuple{T,T} = (ignorenan_minimum(values), ignorenan_maximum(values))) where T<:Real
|
||||
ZValues(collect(float(values)), map(Float64, zrange))
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
abstract AbstractSurface
|
||||
abstract type AbstractSurface end
|
||||
|
||||
"represents a contour or surface mesh"
|
||||
immutable Surface{M<:AMat} <: AbstractSurface
|
||||
struct Surface{M<:AMat} <: AbstractSurface
|
||||
surf::M
|
||||
end
|
||||
|
||||
@@ -400,7 +620,7 @@ for f in (:length, :size)
|
||||
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
|
||||
end
|
||||
Base.copy(surf::Surface) = Surface(copy(surf.surf))
|
||||
Base.eltype(surf::Surface) = eltype(surf.surf)
|
||||
Base.eltype(surf::Surface{T}) where {T} = eltype(T)
|
||||
|
||||
function expand_extrema!(a::Axis, surf::Surface)
|
||||
ex = a[:extrema]
|
||||
@@ -411,28 +631,69 @@ function expand_extrema!(a::Axis, surf::Surface)
|
||||
end
|
||||
|
||||
"For the case of representing a surface as a function of x/y... can possibly avoid allocations."
|
||||
immutable SurfaceFunction <: AbstractSurface
|
||||
struct SurfaceFunction <: AbstractSurface
|
||||
f::Function
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# # I don't want to clash with ValidatedNumerics, but this would be nice:
|
||||
# ..(a::T, b::T) = (a,b)
|
||||
|
||||
struct Volume{T}
|
||||
v::Array{T,3}
|
||||
x_extents::Tuple{T,T}
|
||||
y_extents::Tuple{T,T}
|
||||
z_extents::Tuple{T,T}
|
||||
end
|
||||
|
||||
default_extents(::Type{T}) where {T} = (zero(T), one(T))
|
||||
|
||||
function Volume(v::Array{T,3},
|
||||
x_extents = default_extents(T),
|
||||
y_extents = default_extents(T),
|
||||
z_extents = default_extents(T)) where T
|
||||
Volume(v, x_extents, y_extents, z_extents)
|
||||
end
|
||||
|
||||
Base.Array(vol::Volume) = vol.v
|
||||
for f in (:length, :size)
|
||||
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
|
||||
end
|
||||
Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
|
||||
Base.eltype(vol::Volume{T}) where {T} = T
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# style is :open or :closed (for now)
|
||||
immutable Arrow
|
||||
struct Arrow
|
||||
style::Symbol
|
||||
side::Symbol # :head (default), :tail, or :both
|
||||
headlength::Float64
|
||||
headwidth::Float64
|
||||
end
|
||||
|
||||
"""
|
||||
arrow(args...)
|
||||
|
||||
Define arrowheads to apply to lines - args are `style` (`:open` or `:closed`),
|
||||
`side` (`:head`, `:tail` or `:both`), `headlength` and `headwidth`
|
||||
"""
|
||||
function arrow(args...)
|
||||
style = :simple
|
||||
side = :head
|
||||
headlength = 0.3
|
||||
headwidth = 0.3
|
||||
setlength = false
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
if T == Symbol
|
||||
style = arg
|
||||
if arg in (:head, :tail, :both)
|
||||
side = arg
|
||||
else
|
||||
style = arg
|
||||
end
|
||||
elseif T <: Number
|
||||
# first we apply to both, but if there's more, then only change width after the first number
|
||||
headwidth = Float64(arg)
|
||||
@@ -443,10 +704,10 @@ function arrow(args...)
|
||||
elseif T <: Tuple && length(arg) == 2
|
||||
headlength, headwidth = Float64(arg[1]), Float64(arg[2])
|
||||
else
|
||||
warn("Skipped arrow arg $arg")
|
||||
@warn("Skipped arrow arg $arg")
|
||||
end
|
||||
end
|
||||
Arrow(style, headlength, headwidth)
|
||||
Arrow(style, side, headlength, headwidth)
|
||||
end
|
||||
|
||||
|
||||
@@ -465,58 +726,45 @@ function add_arrows(func::Function, x::AVec, y::AVec)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
type BezierCurve{T <: FixedSizeArrays.Vec}
|
||||
control_points::Vector{T}
|
||||
"Represents data values with formatting that should apply to the tick labels."
|
||||
struct Formatted{T}
|
||||
data::T
|
||||
formatter::Function
|
||||
end
|
||||
|
||||
@compat function (bc::BezierCurve)(t::Real)
|
||||
p = zero(P2)
|
||||
n = length(bc.control_points)-1
|
||||
for i in 0:n
|
||||
p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i
|
||||
end
|
||||
p
|
||||
# -----------------------------------------------------------------------
|
||||
"create a BezierCurve for plotting"
|
||||
mutable struct BezierCurve{T <: FixedSizeArrays.Vec}
|
||||
control_points::Vector{T}
|
||||
end
|
||||
|
||||
Base.mean(x::Real, y::Real) = 0.5*(x+y)
|
||||
Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps)
|
||||
function (bc::BezierCurve)(t::Real)
|
||||
p = zero(P2)
|
||||
n = length(bc.control_points)-1
|
||||
for i in 0:n
|
||||
p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i
|
||||
end
|
||||
p
|
||||
end
|
||||
|
||||
curve_points(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, linspace(range..., n))
|
||||
# mean(x::Real, y::Real) = 0.5*(x+y) #commented out as I cannot see this used anywhere and it overwrites a Base method with different functionality
|
||||
# mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps) # I also could not see this used anywhere, and it's type piracy - implementing a NaNMath version for this would just involve converting to a standard array
|
||||
|
||||
@deprecate curve_points coords
|
||||
|
||||
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, range(range..., stop=n, length=50))
|
||||
|
||||
# build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards.
|
||||
# may create a loop if necessary. Assumes the view is [0,1]
|
||||
function directed_curve(p::P2, q::P2; xview = 0:1, yview = 0:1)
|
||||
mn = mean(p, q)
|
||||
diff = q - p
|
||||
|
||||
minx, maxx = minimum(xview), maximum(xview)
|
||||
miny, maxy = minimum(yview), maximum(yview)
|
||||
diffpct = P2(diff[1] / (maxx - minx),
|
||||
diff[2] / (maxy - miny))
|
||||
|
||||
# these points give the initial/final "rise"
|
||||
# vertical_offset = P2(0, (maxy - miny) * max(0.03, min(abs(0.5diffpct[2]), 1.0)))
|
||||
vertical_offset = P2(0, max(0.15, 0.5norm(diff)))
|
||||
upper_control = p + vertical_offset
|
||||
lower_control = q - vertical_offset
|
||||
|
||||
# try to figure out when to loop around vs just connecting straight
|
||||
# TODO: choose loop direction based on sign of p[1]??
|
||||
# x_close_together = abs(diffpct[1]) <= 0.05
|
||||
p_is_higher = diff[2] <= 0
|
||||
inside_control_points = if p_is_higher
|
||||
# add curve points which will create a loop
|
||||
sgn = mn[1] < 0.5 * (maxx + minx) ? -1 : 1
|
||||
inside_offset = P2(0.3 * (maxx - minx), 0)
|
||||
additional_offset = P2(sgn * diff[1], 0) # make it even loopier
|
||||
[upper_control + sgn * (inside_offset + max(0, additional_offset)),
|
||||
lower_control + sgn * (inside_offset + max(0, -additional_offset))]
|
||||
else
|
||||
[]
|
||||
function directed_curve(args...; kw...)
|
||||
error("directed_curve has been moved to PlotRecipes")
|
||||
end
|
||||
|
||||
BezierCurve([p, upper_control, inside_control_points..., lower_control, q])
|
||||
function extrema_plus_buffer(v, buffmult = 0.2)
|
||||
vmin,vmax = ignorenan_extrema(v)
|
||||
vdiff = vmax-vmin
|
||||
buffer = vdiff * buffmult
|
||||
vmin - buffer, vmax + buffer
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# https://github.com/bokeh/Bokeh.jl
|
||||
|
||||
|
||||
supported_args(::BokehBackend) = merge_with_base_supported([
|
||||
supported_attrs(::BokehBackend) = merge_with_base_supported([
|
||||
# :annotations,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
@@ -72,7 +72,7 @@ is_subplot_supported(::BokehBackend) = false
|
||||
|
||||
function _initialize_backend(::BokehBackend; kw...)
|
||||
@eval begin
|
||||
warn("Bokeh is no longer supported... many features will likely be broken.")
|
||||
@warn("Bokeh is no longer supported... many features will likely be broken.")
|
||||
import Bokeh
|
||||
export Bokeh
|
||||
end
|
||||
@@ -194,9 +194,9 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{BokehBackend})
|
||||
function Base.show(io::IO, ::MIME"image/png", plt::AbstractPlot{BokehBackend})
|
||||
# TODO: write a png to io
|
||||
warn("mime png not implemented")
|
||||
@warn("mime png not implemented")
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{BokehBackend})
|
||||
@@ -2,7 +2,7 @@
|
||||
# https://github.com/dcjones/Gadfly.jl
|
||||
|
||||
|
||||
supported_args(::GadflyBackend) = merge_with_base_supported([
|
||||
supported_attrs(::GadflyBackend) = merge_with_base_supported([
|
||||
:annotations,
|
||||
:background_color, :foreground_color, :color_palette,
|
||||
:group, :label, :seriestype,
|
||||
@@ -41,7 +41,7 @@ function _initialize_backend(::GadflyBackend; kw...)
|
||||
@eval begin
|
||||
import Gadfly, Compose
|
||||
export Gadfly, Compose
|
||||
include(joinpath(Pkg.dir("Plots"), "src", "backends", "gadfly_shapes.jl"))
|
||||
include(joinpath(dirname(@__FILE__), "gadfly_shapes.jl"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,7 +197,7 @@ function getGadflyMarkerTheme(d::KW, attr::KW)
|
||||
|
||||
ms = d[:markersize]
|
||||
ms = if typeof(ms) <: AVec
|
||||
warn("Gadfly doesn't support variable marker sizes... using the average: $(mean(ms))")
|
||||
@warn("Gadfly doesn't support variable marker sizes... using the average: $(mean(ms))")
|
||||
mean(ms) * Gadfly.px
|
||||
else
|
||||
ms * Gadfly.px
|
||||
@@ -244,7 +244,7 @@ function addToGadflyLegend(plt::Plot, d::KW)
|
||||
|
||||
# add the legend if needed
|
||||
if all(g -> !isa(g, Gadfly.Guide.ManualColorKey), gplt.guides)
|
||||
unshift!(gplt.guides, Gadfly.Guide.manual_color_key("", @compat(AbstractString)[], Color[]))
|
||||
pushfirst!(gplt.guides, Gadfly.Guide.manual_color_key("", AbstractString[], Color[]))
|
||||
end
|
||||
|
||||
# now add the series to the legend
|
||||
@@ -334,8 +334,8 @@ end
|
||||
# # create a list of vertices that go: [x1,x2,x2,x3,x3, ... ,xi,xi, ... xn,xn] (same for y)
|
||||
# # then the vector passed to the "color" keyword should be a vector: [1,1,2,2,3,3,4,4, ..., i,i, ... , n,n]
|
||||
# csindices = Int[mod1(i,length(cscheme.v)) for i in 1:length(d[:y])]
|
||||
# cs = collect(repmat(csindices', 2, 1))[1:end-1]
|
||||
# grp = collect(repmat((1:length(d[:y]))', 2, 1))[1:end-1]
|
||||
# cs = collect(repeat(csindices', 2, 1))[1:end-1]
|
||||
# grp = collect(repeat((1:length(d[:y]))', 2, 1))[1:end-1]
|
||||
# d[:x], d[:y] = map(createSegments, (d[:x], d[:y]))
|
||||
# colorgroup = [(:linecolor, cs), (:group, grp)]
|
||||
|
||||
@@ -456,7 +456,7 @@ function updateGadflyAxisFlips(gplt, d::KW, xlims, ylims, xfunc, yfunc)
|
||||
end
|
||||
|
||||
|
||||
function findGuideAndSet(gplt, t::DataType, args...; kw...) #s::@compat(AbstractString))
|
||||
function findGuideAndSet(gplt, t::DataType, args...; kw...)
|
||||
for (i,guide) in enumerate(gplt.guides)
|
||||
if isa(guide, t)
|
||||
gplt.guides[i] = t(args...; kw...)
|
||||
@@ -537,7 +537,7 @@ end
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
function createGadflyAnnotationObject(x, y, val::@compat(AbstractString))
|
||||
function createGadflyAnnotationObject(x, y, val::AbstractString)
|
||||
Gadfly.Guide.annotation(Compose.compose(
|
||||
Compose.context(),
|
||||
Compose.text(x, y, val)
|
||||
@@ -558,7 +558,7 @@ function createGadflyAnnotationObject(x, y, txt::PlotText)
|
||||
))
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{GadflyBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
function _add_annotations(plt::Plot{GadflyBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
|
||||
for ann in anns
|
||||
push!(plt.o.guides, createGadflyAnnotationObject(ann...))
|
||||
end
|
||||
@@ -614,7 +614,7 @@ function getxy(plt::Plot{GadflyBackend}, i::Integer)
|
||||
mapping[:x], mapping[:y]
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
function setxy!(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
|
||||
for mapping in getGadflyMappings(plt, i)
|
||||
mapping[:x], mapping[:y] = xy
|
||||
end
|
||||
@@ -677,7 +677,7 @@ setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.attr[:size]...)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
function dowritemime{P<:Union{GadflyBackend,ImmerseBackend}}(io::IO, func, plt::AbstractPlot{P})
|
||||
function doshow(io::IO, func, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
|
||||
gplt = getGadflyContext(plt)
|
||||
setGadflyDisplaySize(plt)
|
||||
Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt)
|
||||
@@ -692,9 +692,9 @@ getGadflyWriteFunc(::MIME"application/x-tex") = Gadfly.PGF
|
||||
getGadflyWriteFunc(m::MIME) = error("Unsupported in Gadfly/Immerse: ", m)
|
||||
|
||||
for mime in (MIME"image/png", MIME"image/svg+xml", MIME"application/pdf", MIME"application/postscript", MIME"application/x-tex")
|
||||
@eval function Base.writemime{P<:Union{GadflyBackend,ImmerseBackend}}(io::IO, ::$mime, plt::AbstractPlot{P})
|
||||
@eval function Base.show(io::IO, ::$mime, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
|
||||
func = getGadflyWriteFunc($mime())
|
||||
dowritemime(io, func, plt)
|
||||
doshow(io, func, plt)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Geometry which displays arbitrary shapes at given (x, y) positions.
|
||||
# note: vertices is a list of shapes
|
||||
immutable ShapeGeometry <: Gadfly.GeometryElement
|
||||
struct ShapeGeometry <: Gadfly.GeometryElement
|
||||
vertices::AbstractVector #{Tuple{Float64,Float64}}
|
||||
tag::Symbol
|
||||
|
||||
@@ -84,7 +84,7 @@ function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray,
|
||||
x = Compose.x_measure(xs[mod1(i, length(xs))])
|
||||
y = Compose.y_measure(ys[mod1(i, length(ys))])
|
||||
r = rs[mod1(i, length(rs))]
|
||||
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in cycle(geom.vertices, i)]
|
||||
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in _cycle(geom.vertices, i)]
|
||||
end
|
||||
Gadfly.polygon(polys, geom.tag)
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# https://github.com/JuliaGraphics/Immerse.jl
|
||||
|
||||
supported_args(::ImmerseBackend) = supported_args(GadflyBackend())
|
||||
supported_attrs(::ImmerseBackend) = supported_attrs(GadflyBackend())
|
||||
supported_types(::ImmerseBackend) = supported_types(GadflyBackend())
|
||||
supported_styles(::ImmerseBackend) = supported_styles(GadflyBackend())
|
||||
supported_markers(::ImmerseBackend) = supported_markers(GadflyBackend())
|
||||
@@ -14,7 +14,7 @@ function _initialize_backend(::ImmerseBackend; kw...)
|
||||
@eval begin
|
||||
import Immerse, Gadfly, Compose, Gtk
|
||||
export Immerse, Gadfly, Compose, Gtk
|
||||
include(joinpath(Pkg.dir("Plots"), "src", "backends", "gadfly_shapes.jl"))
|
||||
include(joinpath(dirname(@__FILE__), "gadfly_shapes.jl"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,7 +61,7 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{ImmerseBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
function _add_annotations(plt::Plot{ImmerseBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
|
||||
for ann in anns
|
||||
push!(getGadflyContext(plt).guides, createGadflyAnnotationObject(ann...))
|
||||
end
|
||||
@@ -76,7 +76,7 @@ function getxy(plt::Plot{ImmerseBackend}, i::Integer)
|
||||
mapping[:x], mapping[:y]
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
function setxy!(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
|
||||
for mapping in getGadflyMappings(plt, i)
|
||||
mapping[:x], mapping[:y] = xy
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
# https://github.com/tbreloff/Qwt.jl
|
||||
|
||||
|
||||
supported_args(::QwtBackend) = merge_with_base_supported([
|
||||
supported_attrs(::QwtBackend) = merge_with_base_supported([
|
||||
:annotations,
|
||||
:linecolor,
|
||||
:fillrange,
|
||||
@@ -31,7 +31,7 @@ is_subplot_supported(::QwtBackend) = true
|
||||
|
||||
function _initialize_backend(::QwtBackend; kw...)
|
||||
@eval begin
|
||||
warn("Qwt is no longer supported... many features will likely be broken.")
|
||||
@warn("Qwt is no longer supported... many features will likely be broken.")
|
||||
import Qwt
|
||||
export Qwt
|
||||
end
|
||||
@@ -39,7 +39,7 @@ end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
@compat const _qwtAliases = KW(
|
||||
const _qwtAliases = KW(
|
||||
:bins => :heatmap_n,
|
||||
:fillrange => :fillto,
|
||||
:linewidth => :width,
|
||||
@@ -133,7 +133,7 @@ function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
|
||||
w = plt.o.widget
|
||||
axisid = Qwt.QWT.QwtPlot[isx ? :xBottom : :yLeft]
|
||||
|
||||
if typeof(lims) <: @compat(Union{Tuple,AVec}) && length(lims) == 2
|
||||
if typeof(lims) <: Union{Tuple,AVec} && length(lims) == 2
|
||||
if isx
|
||||
plt.o.autoscale_x = false
|
||||
else
|
||||
@@ -142,7 +142,7 @@ function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
|
||||
w[:setAxisScale](axisid, lims...)
|
||||
end
|
||||
|
||||
if typeof(ticks) <: Range
|
||||
if typeof(ticks) <: AbstractRange
|
||||
if isx
|
||||
plt.o.autoscale_x = false
|
||||
else
|
||||
@@ -150,7 +150,7 @@ function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
|
||||
end
|
||||
w[:setAxisScale](axisid, float(minimum(ticks)), float(maximum(ticks)), float(step(ticks)))
|
||||
elseif !(ticks in (nothing, :none, :auto))
|
||||
warn("Only Range types are supported for Qwt xticks/yticks. typeof(ticks)=$(typeof(ticks))")
|
||||
@warn("Only Range types are supported for Qwt xticks/yticks. typeof(ticks)=$(typeof(ticks))")
|
||||
end
|
||||
|
||||
# change the scale
|
||||
@@ -161,7 +161,7 @@ function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
|
||||
# scaletype == :log && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(e))
|
||||
# scaletype == :log2 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(2))
|
||||
scaletype == :log10 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLog10ScaleEngine())
|
||||
scaletype in supported_scales() || warn("Unsupported scale type: ", scaletype)
|
||||
scaletype in supported_scales() || @warn("Unsupported scale type: ", scaletype)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -211,14 +211,14 @@ function createQwtAnnotation(plt::Plot, x, y, val::PlotText)
|
||||
marker[:attach](plt.o.widget)
|
||||
end
|
||||
|
||||
function createQwtAnnotation(plt::Plot, x, y, val::@compat(AbstractString))
|
||||
function createQwtAnnotation(plt::Plot, x, y, val::AbstractString)
|
||||
marker = Qwt.QWT.QwtPlotMarker()
|
||||
marker[:setValue](x, y)
|
||||
marker[:setLabel](Qwt.QWT.QwtText(val))
|
||||
marker[:attach](plt.o.widget)
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{QwtBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
function _add_annotations(plt::Plot{QwtBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
|
||||
for ann in anns
|
||||
createQwtAnnotation(plt, ann...)
|
||||
end
|
||||
@@ -233,7 +233,7 @@ function getxy(plt::Plot{QwtBackend}, i::Int)
|
||||
series.x, series.y
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
function setxy!(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
|
||||
series = plt.o.lines[i]
|
||||
series.x, series.y = xy
|
||||
plt
|
||||
@@ -242,8 +242,6 @@ end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# savepng(::QwtBackend, plt::AbstractPlot, fn::@compat(AbstractString), args...) = Qwt.savepng(plt.o, fn)
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# # create the underlying object (each backend will do this differently)
|
||||
@@ -282,13 +280,13 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::Plot{QwtBackend})
|
||||
function Base.show(io::IO, ::MIME"image/png", plt::Plot{QwtBackend})
|
||||
Qwt.refresh(plt.o)
|
||||
Qwt.savepng(plt.o, "/tmp/dfskjdhfkh.png")
|
||||
write(io, readall("/tmp/dfskjdhfkh.png"))
|
||||
end
|
||||
|
||||
# function Base.writemime(io::IO, ::MIME"image/png", subplt::Subplot{QwtBackend})
|
||||
# function Base.show(io::IO, ::MIME"image/png", subplt::Subplot{QwtBackend})
|
||||
# for plt in subplt.plts
|
||||
# Qwt.refresh(plt.o)
|
||||
# end
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation
|
||||
|
||||
supported_args(::WinstonBackend) = merge_with_base_supported([
|
||||
supported_attrs(::WinstonBackend) = merge_with_base_supported([
|
||||
:annotations,
|
||||
:linecolor,
|
||||
:fillrange,
|
||||
@@ -34,7 +34,7 @@ is_subplot_supported(::WinstonBackend) = false
|
||||
function _initialize_backend(::WinstonBackend; kw...)
|
||||
@eval begin
|
||||
# ENV["WINSTON_OUTPUT"] = "gtk"
|
||||
warn("Winston is no longer supported... many features will likely be broken.")
|
||||
@warn("Winston is no longer supported... many features will likely be broken.")
|
||||
import Winston, Gtk
|
||||
export Winston, Gtk
|
||||
end
|
||||
@@ -44,13 +44,13 @@ end
|
||||
|
||||
|
||||
## dictionaries for conversion of Plots.jl names to Winston ones.
|
||||
@compat const winston_linestyle = KW(:solid=>"solid",
|
||||
const winston_linestyle = KW(:solid=>"solid",
|
||||
:dash=>"dash",
|
||||
:dot=>"dotted",
|
||||
:dashdot=>"dotdashed"
|
||||
)
|
||||
|
||||
@compat const winston_marker = KW(:none=>".",
|
||||
const winston_marker = KW(:none=>".",
|
||||
:rect => "square",
|
||||
:circle=>"circle",
|
||||
:diamond=>"diamond",
|
||||
@@ -185,7 +185,7 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
@compat const _winstonNames = KW(
|
||||
const _winstonNames = KW(
|
||||
:xlims => :xrange,
|
||||
:ylims => :yrange,
|
||||
:xscale => :xlog,
|
||||
@@ -213,11 +213,11 @@ end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::@compat(AbstractString))
|
||||
function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::AbstractString)
|
||||
Winston.text(x, y, val)
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{WinstonBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
function _add_annotations(plt::Plot{WinstonBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
|
||||
for ann in anns
|
||||
createWinstonAnnotationObject(plt, ann...)
|
||||
end
|
||||
@@ -238,10 +238,10 @@ function addWinstonLegend(plt::Plot, wplt)
|
||||
end
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{WinstonBackend})
|
||||
function Base.show(io::IO, ::MIME"image/png", plt::AbstractPlot{WinstonBackend})
|
||||
window, canvas, wplt = getWinstonItems(plt)
|
||||
addWinstonLegend(plt, wplt)
|
||||
writemime(io, "image/png", wplt)
|
||||
show(io, "image/png", wplt)
|
||||
end
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
|
||||
function sample_evenly(v::AVec, n::Integer = length(v))
|
||||
idx = Int[round(Int, x) for x in linspace(1, length(v), n)]
|
||||
idx = Int[round(Int, x) for x in range(1, stop=length(v), length=n)]
|
||||
v[idx]
|
||||
end
|
||||
|
||||
@@ -1,14 +1,42 @@
|
||||
|
||||
abstract ColorScheme
|
||||
abstract type ColorScheme end
|
||||
|
||||
Base.getindex(scheme::ColorScheme, i::Integer) = getColor(scheme, i)
|
||||
|
||||
export
|
||||
cgrad
|
||||
|
||||
cgrad() = default_gradient()
|
||||
|
||||
function cgrad(arg, values = nothing; alpha = nothing, scale = :identity)
|
||||
colors = ColorGradient(arg, alpha=alpha).colors
|
||||
values = if values != nothing
|
||||
values
|
||||
elseif scale in (:log, :log10)
|
||||
log10(range(1, stop=10, length=30))
|
||||
elseif scale == :log2
|
||||
log2(range(1, stop=2, length=30))
|
||||
elseif scale == :ln
|
||||
log(range(1, stop=pi, length=30))
|
||||
elseif scale in (:exp, :exp10)
|
||||
(exp10(range(0, stop=1, length=30)) - 1) / 9
|
||||
else
|
||||
range(0, stop=1, length=length(colors))
|
||||
end
|
||||
ColorGradient(colors, values)
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
getColor(scheme::ColorScheme) = getColor(scheme, 1)
|
||||
getColorVector(scheme::ColorScheme) = [getColor(scheme)]
|
||||
|
||||
colorscheme(scheme::ColorScheme) = scheme
|
||||
colorscheme(s::AbstractString; kw...) = colorscheme(Symbol(s); kw...)
|
||||
colorscheme(s::Symbol; kw...) = haskey(_gradients, s) ? ColorGradient(s; kw...) : ColorWrapper(convertColor(s); kw...)
|
||||
colorscheme{T<:Real}(s::Symbol, vals::AVec{T}; kw...) = ColorGradient(s, vals; kw...)
|
||||
colorscheme(s::Symbol, vals::AVec{T}; kw...) where {T<:Real} = ColorGradient(s, vals; kw...)
|
||||
colorscheme(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...)
|
||||
colorscheme{T<:Colorant}(cs::AVec{T}; kw...) = ColorGradient(cs; kw...)
|
||||
colorscheme(cs::AVec{T}; kw...) where {T<:Colorant} = ColorGradient(cs; kw...)
|
||||
colorscheme(f::Function; kw...) = ColorFunction(f; kw...)
|
||||
colorscheme(v::AVec; kw...) = ColorVector(v; kw...)
|
||||
colorscheme(m::AMat; kw...) = size(m,1) == 1 ? map(c->colorscheme(c; kw...), m) : [colorscheme(m[:,i]; kw...) for i in 1:size(m,2)]'
|
||||
@@ -23,15 +51,15 @@ convertColor(c::Symbol) = parse(Colorant, string(c))
|
||||
convertColor(c::Colorant) = c
|
||||
convertColor(cvec::AbstractVector) = map(convertColor, cvec)
|
||||
convertColor(c::ColorScheme) = c
|
||||
convertColor(v::Void) = RGBA(0,0,0,0)
|
||||
convertColor(v::Nothing) = RGBA(0,0,0,0)
|
||||
convertColor(b::Bool) = b ? RGBA(0,0,0,1) : RGBA(0,0,0,0)
|
||||
|
||||
function convertColor(c, α::Real)
|
||||
c = convertColor(c)
|
||||
RGBA(RGB(c), α)
|
||||
RGBA(RGB(getColor(c)), α)
|
||||
end
|
||||
convertColor(cs::AVec, α::Real) = map(c -> convertColor(c, α), cs)
|
||||
convertColor(c, α::@compat(Void)) = convertColor(c)
|
||||
convertColor(c, α::Nothing) = convertColor(c)
|
||||
|
||||
# backup... try to convert
|
||||
getColor(c) = convertColor(c)
|
||||
@@ -51,7 +79,7 @@ end
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
const _rainbowColors = [colorant"blue", colorant"purple", colorant"green", colorant"orange", colorant"red"]
|
||||
const _rainbowColors = [colorant"purple", colorant"blue", colorant"green", colorant"orange", colorant"red"]
|
||||
const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcyan",colorant"green",
|
||||
darken(colorant"yellow",0.3), colorant"orange", darken(colorant"red",0.2)]
|
||||
|
||||
@@ -70,7 +98,7 @@ const _gradients = KW(
|
||||
:lighttest => map(c -> lighten(c, 0.3), _testColors),
|
||||
)
|
||||
|
||||
function register_gradient_colors{C<:Colorant}(name::Symbol, colors::AVec{C})
|
||||
function register_gradient_colors(name::Symbol, colors::AVec{C}) where C<:Colorant
|
||||
_gradients[name] = colors
|
||||
end
|
||||
|
||||
@@ -81,39 +109,45 @@ default_gradient() = ColorGradient(:inferno)
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Continuous gradient between values. Wraps a list of bounding colors and the values they represent."
|
||||
immutable ColorGradient <: ColorScheme
|
||||
struct ColorGradient <: ColorScheme
|
||||
colors::Vector
|
||||
values::Vector
|
||||
|
||||
function ColorGradient{S<:Real}(cs::AVec, vals::AVec{S} = linspace(0, 1, length(cs)); alpha = nothing)
|
||||
function ColorGradient(cs::AVec, vals::AVec{S} = range(0, stop=1, length=length(cs)); alpha = nothing) where S<:Real
|
||||
if length(cs) == length(vals)
|
||||
return new(convertColor(cs,alpha), collect(vals))
|
||||
end
|
||||
|
||||
# # otherwise interpolate evenly between the minval and maxval
|
||||
# minval, maxval = minimum(vals), maximum(vals)
|
||||
# vs = Float64[interpolate(minval, maxval, w) for w in linspace(0, 1, length(cs))]
|
||||
# vs = Float64[interpolate(minval, maxval, w) for w in range(0, stop = 1, length = length(cs))]
|
||||
# new(convertColor(cs,alpha), vs)
|
||||
|
||||
# interpolate the colors for each value
|
||||
vals = merge(linspace(0, 1, length(cs)), vals)
|
||||
vals = merge(range(0, stop=1, length=length(cs)), vals)
|
||||
grad = ColorGradient(cs)
|
||||
cs = [getColorZ(grad, z) for z in linspace(0, 1, length(vals))]
|
||||
cs = [getColorZ(grad, z) for z in range(0, stop=1, length=length(vals))]
|
||||
new(convertColor(cs, alpha), vals)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
Base.getindex(cs::ColorGradient, i::Integer) = getColor(cs, i)
|
||||
Base.getindex(cs::ColorGradient, z::Number) = getColorZ(cs, z)
|
||||
|
||||
|
||||
# create a gradient from a symbol (blues, reds, etc) and vector of boundary values
|
||||
function ColorGradient{T<:Real}(s::Symbol, vals::AVec{T} = 0:0; kw...)
|
||||
function ColorGradient(s::Symbol, vals::AVec{T} = 0:0; kw...) where T<:Real
|
||||
haskey(_gradients, s) || error("Invalid gradient symbol. Choose from: ", sort(collect(keys(_gradients))))
|
||||
cs = _gradients[s]
|
||||
if vals == 0:0
|
||||
vals = linspace(0, 1, length(cs))
|
||||
vals = range(0, stop=1, length=length(cs))
|
||||
end
|
||||
ColorGradient(cs, vals; kw...)
|
||||
end
|
||||
|
||||
# function ColorGradient{T<:Real}(cs::AVec, vals::AVec{T} = linspace(0, 1, length(cs)); kw...)
|
||||
# function ColorGradient{T<:Real}(cs::AVec, vals::AVec{T} = range(0, stop = 1, length = length(cs)); kw...)
|
||||
# ColorGradient(map(convertColor, cs), vals; kw...)
|
||||
# end
|
||||
|
||||
@@ -174,7 +208,7 @@ end
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a function, taking an index and returning a Colorant"
|
||||
immutable ColorFunction <: ColorScheme
|
||||
struct ColorFunction <: ColorScheme
|
||||
f::Function
|
||||
end
|
||||
|
||||
@@ -183,16 +217,16 @@ getColor(scheme::ColorFunction, idx::Int) = scheme.f(idx)
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a function, taking an z-value and returning a Colorant"
|
||||
immutable ColorZFunction <: ColorScheme
|
||||
struct ColorZFunction <: ColorScheme
|
||||
f::Function
|
||||
end
|
||||
|
||||
getColorZ(scheme::ColorFunction, z::Real) = scheme.f(z)
|
||||
getColorZ(scheme::ColorZFunction, z::Real) = scheme.f(z)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a vector of colors... may be vector of Symbol/String/Colorant"
|
||||
immutable ColorVector <: ColorScheme
|
||||
struct ColorVector <: ColorScheme
|
||||
v::Vector{Colorant}
|
||||
ColorVector(v::AVec; alpha = nothing) = new(convertColor(v,alpha))
|
||||
end
|
||||
@@ -204,7 +238,7 @@ getColorVector(scheme::ColorVector) = scheme.v
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a single color"
|
||||
immutable ColorWrapper <: ColorScheme
|
||||
struct ColorWrapper <: ColorScheme
|
||||
c::RGBA
|
||||
ColorWrapper(c::Colorant; alpha = nothing) = new(convertColor(c, alpha))
|
||||
end
|
||||
@@ -213,6 +247,7 @@ ColorWrapper(s::Symbol; alpha = nothing) = ColorWrapper(convertColor(parse(Color
|
||||
|
||||
getColor(scheme::ColorWrapper, idx::Int) = scheme.c
|
||||
getColorZ(scheme::ColorWrapper, z::Real) = scheme.c
|
||||
convertColor(c::ColorWrapper, α::Nothing) = c.c
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
@@ -297,7 +332,7 @@ function generate_colorgradient(bgcolor = colorant"white";
|
||||
seed_colors,
|
||||
lchoices=Float64[lightness],
|
||||
cchoices=Float64[chroma],
|
||||
hchoices=linspace(0, 340, 20)
|
||||
hchoices=range(0, stop=340, length=20)
|
||||
)[2:end]
|
||||
gradient_from_list(colors)
|
||||
end
|
||||
@@ -312,8 +347,8 @@ function get_color_palette(palette, bgcolor::Union{Colorant,ColorWrapper}, numco
|
||||
RGBA[getColorZ(grad, z) for z in zrng]
|
||||
end
|
||||
|
||||
function get_color_palette{C<:Colorant}(palette::Vector{C},
|
||||
bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer)
|
||||
function get_color_palette(palette::Vector{C},
|
||||
bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer) where C<:Colorant
|
||||
palette
|
||||
end
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
|
||||
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
|
||||
# Special handling for: no args, xmin/xmax, parametric, dataframes
|
||||
# Then once inputs have been converted, build the series args, map functions, etc.
|
||||
# This should cut down on boilerplate code and allow more focused dispatch on type
|
||||
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
|
||||
|
||||
const FuncOrFuncs = Union{Function, AVec{Function}}
|
||||
|
||||
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none))
|
||||
|
||||
# missing
|
||||
convertToAnyVector(v::Nothing, d::KW) = Any[nothing], nothing
|
||||
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
|
||||
|
||||
# numeric vector
|
||||
convertToAnyVector(v::AVec{T}, d::KW) where {T<:Number} = Any[v], nothing
|
||||
|
||||
# string vector
|
||||
convertToAnyVector(v::AVec{T}, d::KW) where {T<:AbstractString} = Any[v], nothing
|
||||
|
||||
function convertToAnyVector(v::AMat, d::KW)
|
||||
if all3D(d)
|
||||
Any[Surface(v)]
|
||||
else
|
||||
Any[v[:,i] for i in 1:size(v,2)]
|
||||
end, nothing
|
||||
end
|
||||
|
||||
# function
|
||||
convertToAnyVector(f::Function, d::KW) = Any[f], nothing
|
||||
|
||||
# surface
|
||||
convertToAnyVector(s::Surface, d::KW) = Any[s], nothing
|
||||
|
||||
# # vector of OHLC
|
||||
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
|
||||
|
||||
# dates
|
||||
convertToAnyVector(dts::AVec{D}, d::KW) where {D<:Union{Date,DateTime}} = Any[dts], nothing
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function convertToAnyVector(v::AVec, d::KW)
|
||||
if all(x -> typeof(x) <: Number, v)
|
||||
# all real numbers wrap the whole vector as one item
|
||||
Any[convert(Vector{Float64}, v)], nothing
|
||||
else
|
||||
# something else... treat each element as an item
|
||||
vcat(Any[convertToAnyVector(vi, d)[1] for vi in v]...), nothing
|
||||
# Any[vi for vi in v], nothing
|
||||
end
|
||||
end
|
||||
|
||||
convertToAnyVector(t::Tuple, d::KW) = Any[t], nothing
|
||||
|
||||
|
||||
function convertToAnyVector(args...)
|
||||
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
|
||||
# result in that array being shared. push!, etc will add too many items to that array
|
||||
|
||||
compute_x(x::Nothing, y::Nothing, z) = 1:size(z,1)
|
||||
compute_x(x::Nothing, y, z) = 1:size(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = copy(x)
|
||||
|
||||
# compute_y(x::Void, y::Function, z) = error()
|
||||
compute_y(x::Nothing, y::Nothing, z) = 1:size(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = copy(y)
|
||||
|
||||
compute_z(x, y, z::Function) = map(z, x, y)
|
||||
compute_z(x, y, z::AbstractMatrix) = Surface(z)
|
||||
compute_z(x, y, z::Nothing) = nothing
|
||||
compute_z(x, y, z) = copy(z)
|
||||
|
||||
nobigs(v::AVec{BigFloat}) = map(Float64, v)
|
||||
nobigs(v::AVec{BigInt}) = map(Int64, v)
|
||||
nobigs(v) = v
|
||||
|
||||
@noinline function compute_xyz(x, y, z)
|
||||
x = compute_x(x,y,z)
|
||||
y = compute_y(x,y,z)
|
||||
z = compute_z(x,y,z)
|
||||
nobigs(x), nobigs(y), nobigs(z)
|
||||
end
|
||||
|
||||
# not allowed
|
||||
compute_xyz(x::Nothing, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
+208
-77
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Holds all data needed for a documentation example... header, description, and plotting expression (Expr)
|
||||
"""
|
||||
type PlotExample
|
||||
mutable struct PlotExample
|
||||
header::AbstractString
|
||||
desc::AbstractString
|
||||
exprs::Vector{Expr}
|
||||
@@ -18,11 +18,18 @@ PlotExample("Lines",
|
||||
),
|
||||
|
||||
PlotExample("Functions, adding data, and animations",
|
||||
"Plot multiple functions. You can also put the function first, or use the form `plot(f, xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data: `x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with `push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the animation.",
|
||||
"""
|
||||
Plot multiple functions. You can also put the function first, or use the form `plot(f,
|
||||
xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data:
|
||||
`x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with
|
||||
`push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available
|
||||
to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the
|
||||
animation.
|
||||
""",
|
||||
[:(begin
|
||||
p = plot([sin,cos], zeros(0), leg=false)
|
||||
anim = Animation()
|
||||
for x in linspace(0, 10π, 100)
|
||||
for x in range(0, stop=10π, length=100)
|
||||
push!(p, x, Float64[sin(x), cos(x)])
|
||||
frame(anim)
|
||||
end
|
||||
@@ -37,23 +44,35 @@ PlotExample("Parametric plots",
|
||||
),
|
||||
|
||||
PlotExample("Colors",
|
||||
"Access predefined palettes (or build your own with the `colorscheme` method). Line/marker colors are auto-generated from the plot's palette, unless overridden. Set the `z` argument to turn on series gradients.",
|
||||
"""
|
||||
Access predefined palettes (or build your own with the `colorscheme` method).
|
||||
Line/marker colors are auto-generated from the plot's palette, unless overridden. Set
|
||||
the `z` argument to turn on series gradients.
|
||||
""",
|
||||
[:(begin
|
||||
y = rand(100)
|
||||
plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=(0,:auto), α=0.6)
|
||||
scatter!(y, zcolor=abs(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs(y-0.5)+4, lab="grad")
|
||||
y = rand(100)
|
||||
plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=0, α=0.6)
|
||||
scatter!(y, zcolor=abs.(y.-0.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs.(y.-0.5).+4,
|
||||
lab="grad")
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Global",
|
||||
"Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow you to pass a tuple or value which will be mapped to the relevant args automatically. The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`, `yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!`",
|
||||
"""
|
||||
Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow
|
||||
you to pass a tuple or value which will be mapped to the relevant args automatically.
|
||||
The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during
|
||||
the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`,
|
||||
`yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!`
|
||||
""",
|
||||
[:(begin
|
||||
y = rand(20,3)
|
||||
plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2), leg=false)
|
||||
hline!(mean(y,1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen]))
|
||||
vline!([5,10])
|
||||
title!("TITLE")
|
||||
yaxis!("YLABEL", :log10)
|
||||
y = rand(20,3)
|
||||
plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2),
|
||||
leg=false)
|
||||
hline!(mean(y, dims = 1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen]))
|
||||
vline!([5,10])
|
||||
title!("TITLE")
|
||||
yaxis!("YLABEL", :log10)
|
||||
end)]
|
||||
),
|
||||
|
||||
@@ -61,19 +80,26 @@ PlotExample("Global",
|
||||
# "Use the `axis` arguments.",
|
||||
# [
|
||||
# :(plot(Vector[randn(100), randn(100)*100], axis = [:l :r], ylabel="LEFT", yrightlabel="RIGHT", xlabel="X", title="TITLE"))
|
||||
# ]),
|
||||
# ]),
|
||||
|
||||
PlotExample("Images",
|
||||
"Plot an image. y-axis is set to flipped",
|
||||
[:(begin
|
||||
import Images
|
||||
img = Images.load(Pkg.dir("PlotReferenceImages","Plots","pyplot","0.7.0","ref1.png"))
|
||||
import FileIO
|
||||
img = FileIO.load(Pkg.dir("PlotReferenceImages","Plots","pyplot","0.7.0","ref1.png"))
|
||||
plot(img)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Arguments",
|
||||
"Plot multiple series with different numbers of points. Mix arguments that apply to all series (marker/markersize) with arguments unique to each series (colors). Special arguments `line`, `marker`, and `fill` will automatically figure out what arguments to set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with `line`.) Note that we pass a matrix of colors, and this applies the colors to each series.",
|
||||
"""
|
||||
Plot multiple series with different numbers of points. Mix arguments that apply to all
|
||||
series (marker/markersize) with arguments unique to each series (colors). Special
|
||||
arguments `line`, `marker`, and `fill` will automatically figure out what arguments to
|
||||
set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with
|
||||
`line`.) Note that we pass a matrix of colors, and this applies the colors to each
|
||||
series.
|
||||
""",
|
||||
[:(begin
|
||||
ys = Vector[rand(10), rand(20)]
|
||||
plot(ys, color=[:black :orange], line=(:dot,4), marker=([:hex :d],12,0.8,stroke(3,:gray)))
|
||||
@@ -115,20 +141,23 @@ PlotExample("Line types",
|
||||
PlotExample("Line styles",
|
||||
"",
|
||||
[:(begin
|
||||
styles = filter(s -> s in supported_styles(), [:solid, :dash, :dot, :dashdot, :dashdotdot])'
|
||||
n = length(styles)
|
||||
y = cumsum(randn(20,n),1)
|
||||
plot(y, line = (5, styles), label = map(string,styles))
|
||||
end)]
|
||||
styles = filter(s -> s in Plots.supported_styles(),
|
||||
[:solid, :dash, :dot, :dashdot, :dashdotdot])
|
||||
styles = reshape(styles, 1, length(styles)) # Julia 0.6 unfortunately gives an error when transposing symbol vectors
|
||||
n = length(styles)
|
||||
y = cumsum(randn(20,n), dims = 1)
|
||||
plot(y, line = (5, styles), label = map(string,styles), legendtitle = "linestyle")
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Marker types",
|
||||
"",
|
||||
[:(begin
|
||||
markers = filter(m -> m in supported_markers(), Plots._shape_keys)'
|
||||
markers = filter(m -> m in Plots.supported_markers(), Plots._shape_keys)
|
||||
markers = reshape(markers, 1, length(markers))
|
||||
n = length(markers)
|
||||
x = linspace(0,10,n+2)[2:end-1]
|
||||
y = repmat(reverse(x)', n, 1)
|
||||
x = range(0, stop=10, length=n+2)[2:end-1]
|
||||
y = repeat(reshape(reverse(x),1,:), n, 1)
|
||||
scatter(x, y, m=(8,:auto), lab=map(string,markers), bg=:linen, xlim=(0,10), ylim=(0,10))
|
||||
end)]
|
||||
),
|
||||
@@ -143,90 +172,110 @@ PlotExample("Bar",
|
||||
PlotExample("Histogram",
|
||||
"",
|
||||
[:(begin
|
||||
histogram(randn(1000), nbins=20)
|
||||
histogram(randn(1000), bins = :scott, weights = repeat(1:5, outer = 200))
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Subplots",
|
||||
"""
|
||||
Use the `layout` keyword, and optionally the convenient `@layout` macro to generate arbitrarily complex subplot layouts.
|
||||
""",
|
||||
"""
|
||||
Use the `layout` keyword, and optionally the convenient `@layout` macro to generate
|
||||
arbitrarily complex subplot layouts.
|
||||
""",
|
||||
[:(begin
|
||||
l = @layout([a{0.1h}; b [c;d e]])
|
||||
plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false, ticks=nothing, border=false)
|
||||
l = @layout([a{0.1h}; b [c;d e]])
|
||||
plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false,
|
||||
ticks=nothing, border=:none)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Adding to subplots",
|
||||
"Note here the automatic grid layout, as well as the order in which new series are added to the plots.",
|
||||
"""
|
||||
Note here the automatic grid layout, as well as the order in which new series are added
|
||||
to the plots.
|
||||
""",
|
||||
[:(begin
|
||||
plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow], bg_inside=[:orange :pink :darkblue :black])
|
||||
plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow],
|
||||
bg_inside=[:orange :pink :darkblue :black])
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("",
|
||||
"",
|
||||
[:(begin
|
||||
srand(111)
|
||||
Random.srand(111)
|
||||
plot!(Plots.fakedata(100,10))
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Open/High/Low/Close",
|
||||
"Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` argument. This uses recipes to first convert the tuples to OHLC objects, and subsequently create a :path series with the appropriate line segments.",
|
||||
"""
|
||||
Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y`
|
||||
argument. This uses recipes to first convert the tuples to OHLC objects, and
|
||||
subsequently create a :path series with the appropriate line segments.
|
||||
""",
|
||||
[:(begin
|
||||
n=20
|
||||
hgt=rand(n)+1
|
||||
bot=randn(n)
|
||||
openpct=rand(n)
|
||||
closepct=rand(n)
|
||||
y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i], closepct[i]*hgt[i]+bot[i]) for i in 1:n]
|
||||
ohlc(y)
|
||||
n=20
|
||||
hgt=rand(n).+1
|
||||
bot=randn(n)
|
||||
openpct=rand(n)
|
||||
closepct=rand(n)
|
||||
y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i],
|
||||
closepct[i]*hgt[i]+bot[i]) for i in 1:n]
|
||||
ohlc(y)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Annotations",
|
||||
"The `annotations` keyword is used for text annotations in data-coordinates. Pass in a tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(; annotation=ann)`. Series annotations are used for annotating individual data points. They require only the annotation... x/y values are computed. A `PlotText` object can be build with the method `text(string, attr...)`, which wraps font and color attributes.",
|
||||
"""
|
||||
The `annotations` keyword is used for text annotations in data-coordinates. Pass in a
|
||||
tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(;
|
||||
annotation=ann)`. Series annotations are used for annotating individual data points.
|
||||
They require only the annotation... x/y values are computed. A `PlotText` object can be
|
||||
build with the method `text(string, attr...)`, which wraps font and color attributes.
|
||||
""",
|
||||
[:(begin
|
||||
y = rand(10)
|
||||
plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false)
|
||||
annotate!([(5, y[5], text("this is #5",16,:red,:center)), (10, y[10], text("this is #10",:right,20,"courier"))])
|
||||
scatter!(linspace(2,8,6), rand(6), marker=(50,0.2,:orange), series_annotations = ["series","annotations","map","to","series",text("data",:green)])
|
||||
y = rand(10)
|
||||
plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false)
|
||||
annotate!([(5, y[5], text("this is #5",16,:red,:center)),
|
||||
(10, y[10], text("this is #10",:right,20,"courier"))])
|
||||
scatter!(range(2, stop=8, length=6), rand(6), marker=(50,0.2,:orange),
|
||||
series_annotations = ["series","annotations","map","to","series",
|
||||
text("data",:green)])
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Custom Markers",
|
||||
"A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and the size is expected to be rougly the area of the unit circle.",
|
||||
"""A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported
|
||||
backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and
|
||||
the size is expected to be rougly the area of the unit circle.
|
||||
""",
|
||||
[:(begin
|
||||
verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0),
|
||||
(-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4),
|
||||
(-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)]
|
||||
x = 0.1:0.2:0.9
|
||||
y = 0.7rand(5)+0.15
|
||||
plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)),
|
||||
bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false)
|
||||
verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0),
|
||||
(-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4),
|
||||
(-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)]
|
||||
x = 0.1:0.2:0.9
|
||||
y = 0.7rand(5).+0.15
|
||||
plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)),
|
||||
bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Contours",
|
||||
"Any value for fill works here. We first build a filled contour from a function, then an unfilled contour from a matrix.",
|
||||
"""
|
||||
Any value for fill works here. We first build a filled contour from a function, then an
|
||||
unfilled contour from a matrix.
|
||||
""",
|
||||
[:(begin
|
||||
x = 1:0.5:20
|
||||
y = 1:0.5:10
|
||||
f(x,y) = (3x+y^2)*abs(sin(x)+cos(y))
|
||||
X = repmat(x', length(y), 1)
|
||||
Y = repmat(y, 1, length(x))
|
||||
X = repeat(reshape(x,1,:), length(y), 1)
|
||||
Y = repeat(y, 1, length(x))
|
||||
Z = map(f, X, Y)
|
||||
p1 = contour(x, y, f, fill=true)
|
||||
p2 = contour(x, y, Z)
|
||||
plot(p1, p2)
|
||||
end)]
|
||||
# [:(begin
|
||||
# x = 1:0.3:20
|
||||
# y = x
|
||||
# f(x,y) = sin(x)+cos(y)
|
||||
# contour(x, y, f, fill=true)
|
||||
# end)]
|
||||
),
|
||||
|
||||
PlotExample("Pie",
|
||||
@@ -242,7 +291,7 @@ PlotExample("3D",
|
||||
"",
|
||||
[:(begin
|
||||
n = 100
|
||||
ts = linspace(0,8π,n)
|
||||
ts = range(0, stop=8π, length=n)
|
||||
x = ts .* map(cos,ts)
|
||||
y = 0.1ts .* map(sin,ts)
|
||||
z = 1:n
|
||||
@@ -256,9 +305,9 @@ PlotExample("DataFrames",
|
||||
[:(begin
|
||||
import RDatasets
|
||||
iris = RDatasets.dataset("datasets", "iris")
|
||||
scatter(iris, :SepalLength, :SepalWidth, group=:Species,
|
||||
@df iris scatter(:SepalLength, :SepalWidth, group=:Species,
|
||||
title = "My awesome plot", xlabel = "Length", ylabel = "Width",
|
||||
marker = (0.5, [:+ :h :star7], 12), bg=RGB(.2,.2,.2))
|
||||
marker = (0.5, [:cross :hex :star7], 12), bg=RGB(.2,.2,.2))
|
||||
end)]
|
||||
),
|
||||
|
||||
@@ -266,15 +315,16 @@ PlotExample("Groups and Subplots",
|
||||
"",
|
||||
[:(begin
|
||||
group = rand(map(i->"group $i",1:4),100)
|
||||
plot(rand(100), layout=@layout([a b;c]), group=group, linetype=[:bar :scatter :steppre])
|
||||
plot(rand(100), layout=@layout([a b;c]), group=group,
|
||||
linetype=[:bar :scatter :steppre], linecolor = :match)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Polar Plots",
|
||||
"",
|
||||
[:(begin
|
||||
Θ = linspace(0,1.5π,100)
|
||||
r = abs(0.1randn(100)+sin(3Θ))
|
||||
Θ = range(0, stop=1.5π, length=100)
|
||||
r = abs.(0.1randn(100)+sin.(3Θ))
|
||||
plot(Θ, r, proj=:polar, m=2)
|
||||
end)]
|
||||
),
|
||||
@@ -284,7 +334,7 @@ PlotExample("Heatmap, categorical axes, and aspect_ratio",
|
||||
[:(begin
|
||||
xs = [string("x",i) for i=1:10]
|
||||
ys = [string("y",i) for i=1:4]
|
||||
z = float((1:4)*(1:10)')
|
||||
z = float((1:4)*reshape(1:10,1,:))
|
||||
heatmap(xs, ys, z, aspect_ratio=1)
|
||||
end)]
|
||||
),
|
||||
@@ -292,9 +342,10 @@ PlotExample("Heatmap, categorical axes, and aspect_ratio",
|
||||
PlotExample("Layouts, margins, label rotation, title location",
|
||||
"",
|
||||
[:(begin
|
||||
using Plots.PlotMeasures # for Measures, e.g. mm and px
|
||||
plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"],
|
||||
title_location=:left, left_margin=[20mm 0mm],
|
||||
bottom_margin=50px, xrotation=60)
|
||||
bottom_margin=10px, xrotation=60)
|
||||
end)]
|
||||
),
|
||||
|
||||
@@ -303,10 +354,83 @@ PlotExample("Boxplot and Violin series recipes",
|
||||
[:(begin
|
||||
import RDatasets
|
||||
singers = RDatasets.dataset("lattice", "singer")
|
||||
violin(singers, :VoicePart, :Height, marker = (0.2, :blue, stroke(0)))
|
||||
boxplot!(singers, :VoicePart, :Height, marker = (0.3, :orange, stroke(2)))
|
||||
@df singers violin(:VoicePart, :Height, line = 0, fill = (0.2, :blue))
|
||||
@df singers boxplot!(:VoicePart, :Height, line = (2,:black), fill = (0.3, :orange))
|
||||
end)]
|
||||
)
|
||||
),
|
||||
|
||||
PlotExample("Animation with subplots",
|
||||
"The `layout` macro can be used to create an animation with subplots.",
|
||||
[:(begin
|
||||
l = @layout([[a; b] c])
|
||||
p = plot(plot([sin,cos],1,leg=false),
|
||||
scatter([atan,cos],1,leg=false),
|
||||
plot(log,1,xlims=(1,10π),ylims=(0,5),leg=false),layout=l)
|
||||
|
||||
anim = Animation()
|
||||
for x = range(1, stop=10π, length=100)
|
||||
plot(push!(p,x,Float64[sin(x),cos(x),atan(x),cos(x),log(x)]))
|
||||
frame(anim)
|
||||
end
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Spy",
|
||||
"""
|
||||
For a matrix `mat` with unique nonzeros `spy(mat)` returns a colorless plot. If `mat` has
|
||||
various different nonzero values, a colorbar is added. The colorbar can be disabled with
|
||||
`legend = nothing`.
|
||||
""",
|
||||
[:(begin
|
||||
a = spdiagm((ones(50), ones(49), ones(49), ones(40), ones(40)),(0, 1, -1, 10, -10))
|
||||
b = spdiagm((1:50, 1:49, 1:49, 1:40, 1:40),(0, 1, -1, 10, -10))
|
||||
plot(spy(a), spy(b), title = ["Unique nonzeros" "Different nonzeros"])
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Magic grid argument",
|
||||
"""
|
||||
The grid lines can be modified individually for each axis with the magic `grid` argument.
|
||||
""",
|
||||
[:(begin
|
||||
x = rand(10)
|
||||
p1 = plot(x, title = "Default looks")
|
||||
p2 = plot(x, grid = (:y, :olivedrab, :dot, 1, 0.9), title = "Modified y grid")
|
||||
p3 = plot(deepcopy(p2), title = "Add x grid")
|
||||
xgrid!(p3, :on, :cadetblue, 2, :dashdot, 0.4)
|
||||
plot(p1, p2, p3, layout = (1, 3), label = "", fillrange = 0, fillalpha = 0.3)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Framestyle",
|
||||
"""
|
||||
The style of the frame/axes of a (sub)plot can be changed with the `framestyle`
|
||||
attribute. The default framestyle is `:axes`.
|
||||
""",
|
||||
[:(begin
|
||||
scatter(fill(randn(10), 6), fill(randn(10), 6),
|
||||
framestyle = [:box :semi :origin :zerolines :grid :none],
|
||||
title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"],
|
||||
color = RowVector(1:6), layout = 6, label = "", markerstrokewidth = 0,
|
||||
ticks = -2:2)
|
||||
end)]
|
||||
),
|
||||
|
||||
PlotExample("Lines and markers with varying colors",
|
||||
"""
|
||||
You can use the `line_z` and `marker_z` properties to associate a color with
|
||||
each line segment or marker in the plot.
|
||||
""",
|
||||
[:(begin
|
||||
t = range(0, stop=1, length=100)
|
||||
θ = 6π .* t
|
||||
x = t .* cos.(θ)
|
||||
y = t .* sin.(θ)
|
||||
p1 = plot(x, y, line_z=t, linewidth=3, legend=false)
|
||||
p2 = scatter(x, y, marker_z=t, color=:bluesreds, legend=false)
|
||||
plot(p1, p2)
|
||||
end)]
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
@@ -315,7 +439,7 @@ PlotExample("Boxplot and Violin series recipes",
|
||||
# make and display one plot
|
||||
function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true)
|
||||
Plots._debugMode.on = debug
|
||||
info("Testing plot: $pkgname:$idx:$(_examples[idx].header)")
|
||||
@info("Testing plot: $pkgname:$idx:$(_examples[idx].header)")
|
||||
backend(pkgname)
|
||||
backend()
|
||||
map(eval, _examples[idx].exprs)
|
||||
@@ -327,6 +451,13 @@ function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true)
|
||||
end
|
||||
|
||||
# generate all plots and create a dict mapping idx --> plt
|
||||
"""
|
||||
test_examples(pkgname[, idx]; debug = false, disp = true, sleep = nothing,
|
||||
skip = [], only = nothing
|
||||
|
||||
Run the `idx` test example for a given backend, or all examples if `idx`
|
||||
is not specified.
|
||||
"""
|
||||
function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = nothing,
|
||||
skip = [], only = nothing)
|
||||
Plots._debugMode.on = debug
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
function __init__()
|
||||
include(joinpath(@__DIR__, "backends", "plotly.jl"))
|
||||
include(joinpath(@__DIR__, "backends", "gr.jl"))
|
||||
include(joinpath(@__DIR__, "backends", "web.jl"))
|
||||
|
||||
@require GLVisualize = "4086de5b-f4b6-55f3-abb0-b8c73827585f" include(joinpath(@__DIR__, "backends", "glvisualize.jl"))
|
||||
@require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" include(joinpath(@__DIR__, "backends", "hdf5.jl"))
|
||||
@require InspectDR = "d0351b0e-4b05-5898-87b3-e2a8edfddd1d" include(joinpath(@__DIR__, "backends", "inspectdr.jl"))
|
||||
@require PGFPlots = "3b7a836e-365b-5785-a47d-02c71176b4aa" include(joinpath(@__DIR__, "backends", "pgfplots.jl"))
|
||||
@require PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a" include(joinpath(@__DIR__, "backends", "plotlyjs.jl"))
|
||||
@require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" include(joinpath(@__DIR__, "backends", "pyplot.jl"))
|
||||
@require UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" include(joinpath(@__DIR__, "backends", "unicodeplots.jl"))
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# IJulia
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
|
||||
if IJulia.inited
|
||||
|
||||
"""
|
||||
Add extra jupyter mimetypes to display_dict based on the plot backed.
|
||||
|
||||
The default is nothing, except for plotly based backends, where it
|
||||
adds data for `application/vnd.plotly.v1+json` that is used in
|
||||
frontends like jupyterlab and nteract.
|
||||
"""
|
||||
_extra_mime_info!(plt::Plot, out::Dict) = out
|
||||
function _extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict)
|
||||
out["application/vnd.plotly.v1+json"] = JSON.lower(plt.o)
|
||||
out
|
||||
end
|
||||
|
||||
function _extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict)
|
||||
out["application/vnd.plotly.v1+json"] = Dict(
|
||||
:data => plotly_series(plt),
|
||||
:layout => plotly_layout(plt)
|
||||
)
|
||||
out
|
||||
end
|
||||
|
||||
function IJulia.display_dict(plt::Plot)
|
||||
output_type = Symbol(plt.attr[:html_output_format])
|
||||
if output_type == :auto
|
||||
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
|
||||
end
|
||||
out = Dict()
|
||||
if output_type == :txt
|
||||
mime = "text/plain"
|
||||
out[mime] = sprint(show, MIME(mime), plt)
|
||||
elseif output_type == :png
|
||||
mime = "image/png"
|
||||
out[mime] = base64encode(show, MIME(mime), plt)
|
||||
elseif output_type == :svg
|
||||
mime = "image/svg+xml"
|
||||
out[mime] = sprint(show, MIME(mime), plt)
|
||||
elseif output_type == :html
|
||||
mime = "text/html"
|
||||
out[mime] = sprint(show, MIME(mime), plt)
|
||||
else
|
||||
error("Unsupported output type $output_type")
|
||||
end
|
||||
_extra_mime_info!(plt, out)
|
||||
out
|
||||
end
|
||||
|
||||
ENV["MPLBACKEND"] = "Agg"
|
||||
end
|
||||
end
|
||||
end
|
||||
+176
-50
@@ -1,34 +1,30 @@
|
||||
|
||||
# NOTE: (0,0) is the top-left !!!
|
||||
|
||||
# allow pixels and percentages
|
||||
const px = AbsoluteLength(0.254)
|
||||
const pct = Length{:pct, Float64}(1.0)
|
||||
|
||||
to_pixels(m::AbsoluteLength) = m.value / 0.254
|
||||
|
||||
const _cbar_width = 5mm
|
||||
|
||||
@compat Base.:.*(m::Measure, n::Number) = m * n
|
||||
@compat Base.:.*(n::Number, m::Measure) = m * n
|
||||
@compat Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
|
||||
@compat Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
|
||||
#Base.broadcast(::typeof(Base.:.*), m::Measure, n::Number) = m * n
|
||||
#Base.broadcast(::typeof(Base.:.*), m::Number, n::Measure) = m * n
|
||||
Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
|
||||
Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
|
||||
Base.zero(::Type{typeof(mm)}) = 0mm
|
||||
Base.one(::Type{typeof(mm)}) = 1mm
|
||||
Base.typemin(::typeof(mm)) = -Inf*mm
|
||||
Base.typemax(::typeof(mm)) = Inf*mm
|
||||
Base.convert{F<:AbstractFloat}(::Type{F}, l::AbsoluteLength) = convert(F, l.value)
|
||||
Base.convert(::Type{F}, l::AbsoluteLength) where {F<:AbstractFloat} = convert(F, l.value)
|
||||
|
||||
# TODO: these are unintuitive and may cause tricky bugs
|
||||
# @compat Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
|
||||
# @compat Base.:+(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
|
||||
# @compat Base.:-(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
|
||||
# @compat Base.:-(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
|
||||
# Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
|
||||
# Base.:+(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
|
||||
# Base.:-(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
|
||||
# Base.:-(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
|
||||
|
||||
@compat Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
|
||||
@compat Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
|
||||
@compat Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
|
||||
@compat Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
|
||||
Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
|
||||
Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
|
||||
Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
|
||||
Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
|
||||
|
||||
|
||||
Base.zero(::Type{typeof(pct)}) = 0pct
|
||||
@@ -44,11 +40,11 @@ right(bbox::BoundingBox) = left(bbox) + width(bbox)
|
||||
bottom(bbox::BoundingBox) = top(bbox) + height(bbox)
|
||||
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
|
||||
|
||||
# @compat Base.:*{T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
|
||||
# Base.:*{T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
|
||||
ispositive(m::Measure) = m.value > 0
|
||||
|
||||
# union together bounding boxes
|
||||
@compat function Base.:+(bb1::BoundingBox, bb2::BoundingBox)
|
||||
function Base.:+(bb1::BoundingBox, bb2::BoundingBox)
|
||||
# empty boxes don't change the union
|
||||
ispositive(width(bb1)) || return bb2
|
||||
ispositive(height(bb1)) || return bb2
|
||||
@@ -96,24 +92,88 @@ function Base.show(io::IO, bbox::BoundingBox)
|
||||
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# points combined by x/y, pct, and length
|
||||
mutable struct MixedMeasures
|
||||
xy::Float64
|
||||
pct::Float64
|
||||
len::AbsoluteLength
|
||||
end
|
||||
|
||||
function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
|
||||
xy = mix.xy
|
||||
pct = mix.pct
|
||||
if mix.len != 0mm
|
||||
f = (letter == :x ? width : height)
|
||||
totlen = f(plotarea(sp))
|
||||
@show totlen
|
||||
pct += mix.len / totlen
|
||||
end
|
||||
if pct != 0
|
||||
amin, amax = axis_limits(sp[Symbol(letter,:axis)])
|
||||
xy += pct * (amax-amin)
|
||||
end
|
||||
xy
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# AbstractLayout
|
||||
|
||||
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
|
||||
|
||||
make_measure_hor(n::Number) = n * w
|
||||
make_measure_hor(m::Measure) = m
|
||||
|
||||
make_measure_vert(n::Number) = n * h
|
||||
make_measure_vert(m::Measure) = m
|
||||
|
||||
"""
|
||||
bbox(x, y, w, h [,originargs...])
|
||||
bbox(layout)
|
||||
|
||||
Create a bounding box for plotting
|
||||
"""
|
||||
function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...)
|
||||
oargs = vcat(oarg1, originargs...)
|
||||
orighor = :left
|
||||
origver = :top
|
||||
for oarg in oargs
|
||||
if oarg == :center
|
||||
orighor = origver = oarg
|
||||
elseif oarg in (:left, :right, :hcenter)
|
||||
orighor = oarg
|
||||
elseif oarg in (:top, :bottom, :vcenter)
|
||||
origver = oarg
|
||||
else
|
||||
@warn("Unused origin arg in bbox construction: $oarg")
|
||||
end
|
||||
end
|
||||
bbox(x, y, w, h; h_anchor = orighor, v_anchor = origver)
|
||||
end
|
||||
|
||||
# create a new bbox
|
||||
function bbox(x, y, w, h; h_anchor = :left, v_anchor = :top)
|
||||
function bbox(x, y, width, height; h_anchor = :left, v_anchor = :top)
|
||||
x = make_measure_hor(x)
|
||||
y = make_measure_vert(y)
|
||||
width = make_measure_hor(width)
|
||||
height = make_measure_vert(height)
|
||||
left = if h_anchor == :left
|
||||
x
|
||||
elseif h_anchor in (:center, :hcenter)
|
||||
0.5w - 0.5width + x
|
||||
else
|
||||
x - w * (h_anchor == :right ? 1.0 : 0.5)
|
||||
1w - x - width
|
||||
end
|
||||
top = if v_anchor == :top
|
||||
y
|
||||
elseif v_anchor in (:center, :vcenter)
|
||||
0.5h - 0.5height + y
|
||||
else
|
||||
y - h * (v_anchor == :bottom ? 1.0 : 0.5)
|
||||
1h - y - height
|
||||
end
|
||||
BoundingBox(left, top, w, h)
|
||||
BoundingBox(left, top, width, height)
|
||||
end
|
||||
|
||||
# this is the available area for drawing everything in this layout... as percentages of total canvas
|
||||
@@ -156,7 +216,7 @@ bottompad(layout::AbstractLayout) = 0mm
|
||||
# RootLayout
|
||||
|
||||
# this is the parent of the top-level layout
|
||||
immutable RootLayout <: AbstractLayout end
|
||||
struct RootLayout <: AbstractLayout end
|
||||
|
||||
Base.parent(::RootLayout) = nothing
|
||||
parent_bbox(::RootLayout) = defaultbox
|
||||
@@ -166,7 +226,7 @@ bbox(::RootLayout) = defaultbox
|
||||
# EmptyLayout
|
||||
|
||||
# contains blank space
|
||||
type EmptyLayout <: AbstractLayout
|
||||
mutable struct EmptyLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox
|
||||
attr::KW # store label, width, and height for initialization
|
||||
@@ -184,7 +244,7 @@ _update_min_padding!(layout::EmptyLayout) = nothing
|
||||
# GridLayout
|
||||
|
||||
# nested, gridded layout with optional size percentages
|
||||
type GridLayout <: AbstractLayout
|
||||
mutable struct GridLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox
|
||||
@@ -194,6 +254,13 @@ type GridLayout <: AbstractLayout
|
||||
attr::KW
|
||||
end
|
||||
|
||||
"""
|
||||
grid(args...; kw...)
|
||||
|
||||
Create a grid layout for subplots. `args` specify the dimensions, e.g.
|
||||
`grid(3,2, widths = (0.6,04))` creates a grid with three rows and two
|
||||
columns of different width.
|
||||
"""
|
||||
grid(args...; kw...) = GridLayout(args...; kw...)
|
||||
|
||||
function GridLayout(dims...;
|
||||
@@ -201,7 +268,7 @@ function GridLayout(dims...;
|
||||
widths = zeros(dims[2]),
|
||||
heights = zeros(dims[1]),
|
||||
kw...)
|
||||
grid = Matrix{AbstractLayout}(dims...)
|
||||
grid = Matrix{AbstractLayout}(undef, dims...)
|
||||
layout = GridLayout(
|
||||
parent,
|
||||
(20mm, 5mm, 2mm, 10mm),
|
||||
@@ -212,7 +279,9 @@ function GridLayout(dims...;
|
||||
# convert(Vector{Float64}, widths),
|
||||
# convert(Vector{Float64}, heights),
|
||||
KW(kw))
|
||||
fill!(grid, EmptyLayout(layout))
|
||||
for i in eachindex(grid)
|
||||
grid[i] = EmptyLayout(layout)
|
||||
end
|
||||
layout
|
||||
end
|
||||
|
||||
@@ -229,7 +298,7 @@ rightpad(layout::GridLayout) = layout.minpad[3]
|
||||
bottompad(layout::GridLayout) = layout.minpad[4]
|
||||
|
||||
|
||||
# here's how this works... first we recursively "update the minimum padding" (which
|
||||
# here's how this works... first we recursively "update the minimum padding" (which
|
||||
# means to calculate the minimum size needed from the edge of the subplot to plot area)
|
||||
# for the whole layout tree. then we can compute the "padding borders" of this
|
||||
# layout as the biggest padding of the children on the perimeter. then we need to
|
||||
@@ -284,16 +353,14 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
|
||||
minpad_top = map(toppad, layout.grid)
|
||||
minpad_right = map(rightpad, layout.grid)
|
||||
minpad_bottom = map(bottompad, layout.grid)
|
||||
# @show minpad_left minpad_top minpad_right minpad_bottom
|
||||
|
||||
# get the max horizontal (left and right) padding over columns,
|
||||
# and max vertical (bottom and top) padding over rows
|
||||
# TODO: add extra padding here
|
||||
pad_left = maximum(minpad_left, 1)
|
||||
pad_top = maximum(minpad_top, 2)
|
||||
pad_right = maximum(minpad_right, 1)
|
||||
pad_bottom = maximum(minpad_bottom, 2)
|
||||
# @show pad_left pad_top pad_right pad_bottom
|
||||
pad_left = maximum(minpad_left, dims = 1)
|
||||
pad_top = maximum(minpad_top, dims = 2)
|
||||
pad_right = maximum(minpad_right, dims = 1)
|
||||
pad_bottom = maximum(minpad_bottom, dims = 2)
|
||||
|
||||
# make sure the perimeter match the parent
|
||||
pad_left[1] = max(pad_left[1], minimum_perimeter[1])
|
||||
@@ -304,22 +371,18 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
|
||||
# scale this up to the total padding in each direction
|
||||
total_pad_horizontal = sum(pad_left + pad_right)
|
||||
total_pad_vertical = sum(pad_top + pad_bottom)
|
||||
# @show total_pad_horizontal total_pad_vertical
|
||||
|
||||
# now we can compute the total plot area in each direction
|
||||
total_plotarea_horizontal = width(layout) - total_pad_horizontal
|
||||
total_plotarea_vertical = height(layout) - total_pad_vertical
|
||||
# @show total_plotarea_horizontal total_plotarea_vertical
|
||||
|
||||
# recompute widths/heights
|
||||
layout.widths = recompute_lengths(layout.widths)
|
||||
layout.heights = recompute_lengths(layout.heights)
|
||||
# @show layout.widths layout.heights
|
||||
|
||||
# normalize widths/heights so they sum to 1
|
||||
# denom_w = sum(layout.widths)
|
||||
# denom_h = sum(layout.heights)
|
||||
# @show layout.widths layout.heights denom_w, denom_h
|
||||
|
||||
# we have all the data we need... lets compute the plot areas and set the bounding boxes
|
||||
for r=1:nr, c=1:nc
|
||||
@@ -360,7 +423,6 @@ end
|
||||
function update_inset_bboxes!(plt::Plot)
|
||||
for sp in plt.inset_subplots
|
||||
p_area = Measures.resolve(plotarea(sp.parent), sp[:relative_bbox])
|
||||
# @show bbox(sp.parent) sp[:relative_bbox] p_area
|
||||
plotarea!(sp, p_area)
|
||||
|
||||
bbox!(sp, bbox(
|
||||
@@ -409,7 +471,7 @@ end
|
||||
function layout_args(d::KW, n_override::Integer)
|
||||
layout, n = layout_args(get(d, :layout, n_override))
|
||||
if n != n_override
|
||||
error("When doing layout, n != n_override. You're probably trying to force existing plots into a layout that doesn't fit them.")
|
||||
error("When doing layout, n ($n) != n_override ($(n_override)). You're probably trying to force existing plots into a layout that doesn't fit them.")
|
||||
end
|
||||
layout, n
|
||||
end
|
||||
@@ -419,12 +481,12 @@ function layout_args(n::Integer)
|
||||
GridLayout(nr, nc), n
|
||||
end
|
||||
|
||||
function layout_args{I<:Integer}(sztup::NTuple{2,I})
|
||||
function layout_args(sztup::NTuple{2,I}) where I<:Integer
|
||||
nr, nc = sztup
|
||||
GridLayout(nr, nc), nr*nc
|
||||
end
|
||||
|
||||
function layout_args{I<:Integer}(sztup::NTuple{3,I})
|
||||
function layout_args(sztup::NTuple{3,I}) where I<:Integer
|
||||
n, nr, nc = sztup
|
||||
nr, nc = compute_gridsize(n, nr, nc)
|
||||
GridLayout(nr, nc), n
|
||||
@@ -475,6 +537,12 @@ function build_layout(layout::GridLayout, n::Integer)
|
||||
i += 1
|
||||
elseif isa(l, GridLayout)
|
||||
# sub-grid
|
||||
if get(l.attr, :width, :auto) != :auto
|
||||
layout.widths[c] = attr(l,:width)
|
||||
end
|
||||
if get(l.attr, :height, :auto) != :auto
|
||||
layout.heights[r] = attr(l,:height)
|
||||
end
|
||||
l, sps, m = build_layout(l, n-i)
|
||||
append!(subplots, sps)
|
||||
merge!(spmap, m)
|
||||
@@ -498,7 +566,7 @@ function build_layout(layout::GridLayout, numsp::Integer, plts::AVec{Plot})
|
||||
for r=1:nr, c=1:nc
|
||||
l = layout[r,c]
|
||||
if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
|
||||
plt = shift!(plts) # grab the first plot out of the list
|
||||
plt = popfirst!(plts) # grab the first plot out of the list
|
||||
layout[r,c] = plt.layout
|
||||
append!(subplots, plt.subplots)
|
||||
merge!(spmap, plt.spmap)
|
||||
@@ -511,6 +579,12 @@ function build_layout(layout::GridLayout, numsp::Integer, plts::AVec{Plot})
|
||||
i += length(plt.subplots)
|
||||
elseif isa(l, GridLayout)
|
||||
# sub-grid
|
||||
if get(l.attr, :width, :auto) != :auto
|
||||
layout.widths[c] = attr(l,:width)
|
||||
end
|
||||
if get(l.attr, :height, :auto) != :auto
|
||||
layout.heights[r] = attr(l,:height)
|
||||
end
|
||||
l, sps, m = build_layout(l, numsp-i, plts)
|
||||
append!(subplots, sps)
|
||||
merge!(spmap, m)
|
||||
@@ -570,7 +644,7 @@ function create_grid(expr::Expr)
|
||||
create_grid_curly(expr)
|
||||
else
|
||||
# if it's something else, just return that (might be an existing layout?)
|
||||
expr
|
||||
esc(expr)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -607,13 +681,18 @@ function create_grid_vcat(expr::Expr)
|
||||
end
|
||||
|
||||
function create_grid_curly(expr::Expr)
|
||||
s = expr.args[1]
|
||||
kw = KW()
|
||||
for (i,arg) in enumerate(expr.args[2:end])
|
||||
add_layout_pct!(kw, arg, i, length(expr.args)-1)
|
||||
end
|
||||
# @show kw
|
||||
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))
|
||||
s = expr.args[1]
|
||||
if isa(s, Expr) && s.head == :call && s.args[1] == :grid
|
||||
create_grid(:(grid($(s.args[2:end]...), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto))))))
|
||||
elseif isa(s, Symbol)
|
||||
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))
|
||||
else
|
||||
error("Unknown use of curly brackets: $expr")
|
||||
end
|
||||
end
|
||||
|
||||
function create_grid(s::Symbol)
|
||||
@@ -627,20 +706,42 @@ end
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# make all reference the same axis extrema/values
|
||||
# make all reference the same axis extrema/values.
|
||||
# merge subplot lists.
|
||||
function link_axes!(axes::Axis...)
|
||||
a1 = axes[1]
|
||||
for i=2:length(axes)
|
||||
a2 = axes[i]
|
||||
expand_extrema!(a1, ignorenan_extrema(a2))
|
||||
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
|
||||
a2[k] = a1[k]
|
||||
end
|
||||
|
||||
# make a2's subplot list refer to a1's and add any missing values
|
||||
sps2 = a2.sps
|
||||
for sp in sps2
|
||||
sp in a1.sps || push!(a1.sps, sp)
|
||||
end
|
||||
a2.sps = a1.sps
|
||||
end
|
||||
end
|
||||
|
||||
# figure out which subplots to link
|
||||
function link_subplots(a::AbstractArray{AbstractLayout}, axissym::Symbol)
|
||||
subplots = []
|
||||
for l in a
|
||||
if isa(l, Subplot)
|
||||
push!(subplots, l)
|
||||
elseif isa(l, GridLayout) && size(l) == (1,1)
|
||||
push!(subplots, l[1,1])
|
||||
end
|
||||
end
|
||||
subplots
|
||||
end
|
||||
|
||||
# for some vector or matrix of layouts, filter only the Subplots and link those axes
|
||||
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
|
||||
subplots = filter(l -> isa(l, Subplot), a)
|
||||
subplots = link_subplots(a, axissym)
|
||||
axes = [sp.attr[axissym] for sp in subplots]
|
||||
if length(axes) > 0
|
||||
link_axes!(axes...)
|
||||
@@ -664,6 +765,16 @@ function link_axes!(layout::GridLayout, link::Symbol)
|
||||
link_axes!(layout.grid[r,:], :yaxis)
|
||||
end
|
||||
end
|
||||
if link == :square
|
||||
sps = filter(l -> isa(l, Subplot), layout.grid)
|
||||
if !isempty(sps)
|
||||
base_axis = sps[1][:xaxis]
|
||||
for sp in sps
|
||||
link_axes!(base_axis, sp[:xaxis])
|
||||
link_axes!(base_axis, sp[:yaxis])
|
||||
end
|
||||
end
|
||||
end
|
||||
if link == :all
|
||||
link_axes!(layout.grid, :xaxis)
|
||||
link_axes!(layout.grid, :yaxis)
|
||||
@@ -672,3 +783,18 @@ function link_axes!(layout::GridLayout, link::Symbol)
|
||||
link_axes!(l, link)
|
||||
end
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
"Adds a new, empty subplot overlayed on top of `sp`, with a mirrored y-axis and linked x-axis."
|
||||
function twinx(sp::Subplot)
|
||||
sp[:right_margin] = max(sp[:right_margin], 30px)
|
||||
plot!(sp.plt, inset = (sp[:subplot_index], bbox(0,0,1,1)))
|
||||
twinsp = sp.plt.subplots[end]
|
||||
twinsp[:yaxis][:mirror] = true
|
||||
twinsp[:background_color_inside] = RGBA{Float64}(0,0,0,0)
|
||||
link_axes!(sp[:xaxis], twinsp[:xaxis])
|
||||
twinsp
|
||||
end
|
||||
|
||||
twinx(plt::Plot = current()) = twinx(plt[1])
|
||||
|
||||
+118
-97
@@ -5,7 +5,7 @@ defaultOutputFormat(plt::Plot) = "png"
|
||||
function png(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "png")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("image/png"), plt)
|
||||
show(io, MIME("image/png"), plt)
|
||||
close(io)
|
||||
end
|
||||
png(fn::AbstractString) = png(current(), fn)
|
||||
@@ -13,7 +13,7 @@ png(fn::AbstractString) = png(current(), fn)
|
||||
function svg(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "svg")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("image/svg+xml"), plt)
|
||||
show(io, MIME("image/svg+xml"), plt)
|
||||
close(io)
|
||||
end
|
||||
svg(fn::AbstractString) = svg(current(), fn)
|
||||
@@ -22,7 +22,7 @@ svg(fn::AbstractString) = svg(current(), fn)
|
||||
function pdf(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "pdf")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/pdf"), plt)
|
||||
show(io, MIME("application/pdf"), plt)
|
||||
close(io)
|
||||
end
|
||||
pdf(fn::AbstractString) = pdf(current(), fn)
|
||||
@@ -31,30 +31,49 @@ pdf(fn::AbstractString) = pdf(current(), fn)
|
||||
function ps(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "ps")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/postscript"), plt)
|
||||
show(io, MIME("application/postscript"), plt)
|
||||
close(io)
|
||||
end
|
||||
ps(fn::AbstractString) = ps(current(), fn)
|
||||
|
||||
function eps(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "eps")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/eps"), plt)
|
||||
close(io)
|
||||
end
|
||||
eps(fn::AbstractString) = eps(current(), fn)
|
||||
|
||||
function tex(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "tex")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/x-tex"), plt)
|
||||
show(io, MIME("application/x-tex"), plt)
|
||||
close(io)
|
||||
end
|
||||
tex(fn::AbstractString) = tex(current(), fn)
|
||||
|
||||
function html(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "html")
|
||||
io = open(fn, "w")
|
||||
_use_remote[] = true
|
||||
show(io, MIME("text/html"), plt)
|
||||
_use_remote[] = false
|
||||
close(io)
|
||||
end
|
||||
html(fn::AbstractString) = html(current(), fn)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
@compat const _savemap = Dict(
|
||||
const _savemap = Dict(
|
||||
"png" => png,
|
||||
"svg" => svg,
|
||||
"pdf" => pdf,
|
||||
"ps" => ps,
|
||||
"eps" => eps,
|
||||
"tex" => tex,
|
||||
"html" => html,
|
||||
)
|
||||
|
||||
function getExtension(fn::AbstractString)
|
||||
@@ -78,6 +97,13 @@ function addExtension(fn::AbstractString, ext::AbstractString)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
savefig([plot,] filename)
|
||||
|
||||
Save a Plot (the current plot if `plot` is not passed) to file. The file
|
||||
type is inferred from the file extension. All backends support png and pdf
|
||||
file types, some also support svg, ps, eps, html and tex.
|
||||
"""
|
||||
function savefig(plt::Plot, fn::AbstractString)
|
||||
|
||||
# get the extension
|
||||
@@ -100,99 +126,127 @@ savefig(fn::AbstractString) = savefig(current(), fn)
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
"""
|
||||
gui([plot])
|
||||
|
||||
Display a plot using the backends' gui window
|
||||
"""
|
||||
gui(plt::Plot = current()) = display(PlotsDisplay(), plt)
|
||||
|
||||
# IJulia only... inline display
|
||||
function inline(plt::Plot = current())
|
||||
isijulia() || error("inline() is IJulia-only")
|
||||
Main.IJulia.clear_output(true)
|
||||
display(Main.IJulia.InlineDisplay(), plt)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot)
|
||||
prepare_output(plt)
|
||||
_display(plt)
|
||||
end
|
||||
|
||||
# override the REPL display to open a gui window
|
||||
Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::Plot) = gui(plt)
|
||||
using REPL
|
||||
Base.display(::REPL.REPLDisplay, ::MIME"text/plain", plt::Plot) = gui(plt)
|
||||
|
||||
|
||||
_do_plot_show(plt, showval::Bool) = showval && gui(plt)
|
||||
function _do_plot_show(plt, showval::Symbol)
|
||||
showval == :gui && gui(plt)
|
||||
showval in (:inline,:ijulia) && inline(plt)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const _mimeformats = Dict(
|
||||
"application/eps" => "eps",
|
||||
"image/eps" => "eps",
|
||||
"application/pdf" => "pdf",
|
||||
"image/png" => "png",
|
||||
"application/postscript" => "ps",
|
||||
"image/svg+xml" => "svg",
|
||||
"text/plain" => "txt",
|
||||
)
|
||||
|
||||
const _best_html_output_type = KW(
|
||||
:pyplot => :png,
|
||||
:unicodeplots => :txt,
|
||||
:glvisualize => :png,
|
||||
:plotlyjs => :html,
|
||||
:plotly => :html
|
||||
)
|
||||
|
||||
# a backup for html... passes to svg or png depending on the html_output_format arg
|
||||
function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
|
||||
function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
output_type = Symbol(plt.attr[:html_output_format])
|
||||
if output_type == :auto
|
||||
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
|
||||
end
|
||||
if output_type == :png
|
||||
# info("writing png to html output")
|
||||
print(io, "<img src=\"data:image/png;base64,", base64encode(writemime, MIME("image/png"), plt), "\" />")
|
||||
# @info("writing png to html output")
|
||||
print(io, "<img src=\"data:image/png;base64,", base64encode(show, MIME("image/png"), plt), "\" />")
|
||||
elseif output_type == :svg
|
||||
# info("writing svg to html output")
|
||||
writemime(io, MIME("image/svg+xml"), plt)
|
||||
# @info("writing svg to html output")
|
||||
show(io, MIME("image/svg+xml"), plt)
|
||||
elseif output_type == :txt
|
||||
writemime(io, MIME("text/plain"), plt)
|
||||
show(io, MIME("text/plain"), plt)
|
||||
else
|
||||
error("only png or svg allowed. got: $output_type")
|
||||
error("only png or svg allowed. got: $(repr(output_type))")
|
||||
end
|
||||
end
|
||||
|
||||
# delegate showable to _show instead
|
||||
function Base.showable(m::M, plt::P) where {M<:MIME, P<:Plot}
|
||||
return hasmethod(_show, Tuple{IO, M, P})
|
||||
end
|
||||
|
||||
function _display(plt::Plot)
|
||||
@warn("_display is not defined for this backend.")
|
||||
end
|
||||
|
||||
# for writing to io streams... first prepare, then callback
|
||||
for mime in keys(_mimeformats)
|
||||
@eval function Base.writemime(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
|
||||
prepare_output(plt)
|
||||
_writemime(io, m, plt)
|
||||
for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml",
|
||||
"application/eps", "application/pdf", "application/postscript",
|
||||
"application/x-tex")
|
||||
@eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
|
||||
if haskey(io, :juno_plotsize)
|
||||
showjuno(io, m, plt)
|
||||
else
|
||||
prepare_output(plt)
|
||||
_show(io, m, plt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# default text/plain for all backends
|
||||
_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
|
||||
|
||||
"Close all open gui windows of the current backend"
|
||||
closeall() = closeall(backend())
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
|
||||
|
||||
const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend}
|
||||
if is_installed("FileIO")
|
||||
@eval begin
|
||||
import FileIO
|
||||
function _writemime(io::IO, ::MIME"image/png", plt::Plot)
|
||||
fn = tempname()
|
||||
@eval import FileIO
|
||||
function _show(io::IO, ::MIME"image/png", plt::Plot{<:PDFBackends})
|
||||
fn = tempname()
|
||||
|
||||
# first save a pdf file
|
||||
pdf(plt, fn)
|
||||
# first save a pdf file
|
||||
pdf(plt, fn)
|
||||
|
||||
# load that pdf into a FileIO Stream
|
||||
s = FileIO.load(fn * ".pdf")
|
||||
# load that pdf into a FileIO Stream
|
||||
s = FileIO.load(fn * ".pdf")
|
||||
|
||||
# save a png
|
||||
pngfn = fn * ".png"
|
||||
FileIO.save(pngfn, s)
|
||||
# save a png
|
||||
pngfn = fn * ".png"
|
||||
FileIO.save(pngfn, s)
|
||||
|
||||
# now write from the file
|
||||
write(io, readall(open(pngfn)))
|
||||
end
|
||||
# now write from the file
|
||||
write(io, read(open(pngfn), String))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# function html_output_format(fmt)
|
||||
# if fmt == "png"
|
||||
# @eval function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
|
||||
# print(io, "<img src=\"data:image/png;base64,", base64(writemime, MIME("image/png"), plt), "\" />")
|
||||
# @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
# print(io, "<img src=\"data:image/png;base64,", base64(show, MIME("image/png"), plt), "\" />")
|
||||
# end
|
||||
# elseif fmt == "svg"
|
||||
# @eval function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
|
||||
# writemime(io, MIME("image/svg+xml"), plt)
|
||||
# @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
# show(io, MIME("image/svg+xml"), plt)
|
||||
# end
|
||||
# else
|
||||
# error("only png or svg allowed. got: $fmt")
|
||||
@@ -201,59 +255,26 @@ end
|
||||
#
|
||||
# html_output_format("svg")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# IJulia
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const _ijulia_output = Compat.ASCIIString["text/html"]
|
||||
|
||||
function setup_ijulia()
|
||||
# override IJulia inline display
|
||||
if isijulia()
|
||||
@eval begin
|
||||
import IJulia
|
||||
export set_ijulia_output
|
||||
function set_ijulia_output(mimestr::AbstractString)
|
||||
# info("Setting IJulia output format to $mimestr")
|
||||
global _ijulia_output
|
||||
_ijulia_output[1] = mimestr
|
||||
end
|
||||
function IJulia.display_dict(plt::Plot)
|
||||
global _ijulia_output
|
||||
Dict{Compat.ASCIIString, ByteString}(_ijulia_output[1] => sprint(writemime, _ijulia_output[1], plt))
|
||||
end
|
||||
end
|
||||
set_ijulia_output("text/html")
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Atom PlotPane
|
||||
# ---------------------------------------------------------
|
||||
function showjuno(io::IO, m, plt)
|
||||
sz = plt[:size]
|
||||
dpi = plt[:dpi]
|
||||
thickness_scaling = plt[:thickness_scaling]
|
||||
|
||||
function setup_atom()
|
||||
# @require Atom begin
|
||||
if isatom() && get(ENV, "PLOTS_USE_ATOM_PLOTPANE", false) in (true, 1, "1", "true", "yes")
|
||||
# @eval import Atom, Media
|
||||
@eval import Atom
|
||||
jsize = get(io, :juno_plotsize, [400, 500])
|
||||
|
||||
# connects the render function
|
||||
for T in (GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend)
|
||||
Atom.Media.media(Plot{T}, Atom.Media.Plot)
|
||||
end
|
||||
# Atom.Media.media{T <: Union{GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend}}(Plot{T}, Atom.Media.Plot)
|
||||
scale = minimum(jsize[i] / sz[i] for i in 1:2)
|
||||
plt[:size] = (s * scale for s in sz)
|
||||
plt[:dpi] = Plots.DPI
|
||||
plt[:thickness_scaling] *= scale
|
||||
|
||||
# Atom.displaysize(::Plot) = (535, 379)
|
||||
# Atom.displaytitle(plt::Plot) = "Plots.jl (backend: $(backend(plt)))"
|
||||
prepare_output(plt)
|
||||
_show(io, m, plt)
|
||||
|
||||
# this is like "display"... sends an html div with the plot to the PlotPane
|
||||
function Atom.Media.render(pane::Atom.PlotPane, plt::Plot)
|
||||
Atom.Media.render(pane, Atom.div(Atom.d(), Atom.HTML(stringmime(MIME("text/html"), plt))))
|
||||
end
|
||||
|
||||
|
||||
# function Atom.Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyBackend})
|
||||
# html = Media.render(pane, Atom.div(Atom.d(), Atom.HTML(stringmime(MIME("text/html"), plt))))
|
||||
# end
|
||||
end
|
||||
plt[:size] = sz
|
||||
plt[:dpi] = dpi
|
||||
plt[:thickness_scaling] = thickness_scaling
|
||||
end
|
||||
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# preprocessing
|
||||
|
||||
function command_idx(kw_list::AVec{KW}, kw::KW)
|
||||
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
|
||||
end
|
||||
|
||||
function _expand_seriestype_array(d::KW, args)
|
||||
sts = get(d, :seriestype, :path)
|
||||
if typeof(sts) <: AbstractArray
|
||||
delete!(d, :seriestype)
|
||||
rd = Vector{RecipeData}(undef, size(sts, 1))
|
||||
for r in 1:size(sts, 1)
|
||||
dc = copy(d)
|
||||
dc[:seriestype] = sts[r:r,:]
|
||||
rd[r] = RecipeData(dc, args)
|
||||
end
|
||||
rd
|
||||
else
|
||||
RecipeData[RecipeData(copy(d), args)]
|
||||
end
|
||||
end
|
||||
|
||||
function _preprocess_args(d::KW, args, still_to_process::Vector{RecipeData})
|
||||
# the grouping mechanism is a recipe on a GroupBy object
|
||||
# we simply add the GroupBy object to the front of the args list to allow
|
||||
# the recipe to be applied
|
||||
if haskey(d, :group)
|
||||
args = (extractGroupArgs(d[:group], args...), args...)
|
||||
end
|
||||
|
||||
# if we were passed a vector/matrix of seriestypes and there's more than one row,
|
||||
# we want to duplicate the inputs, once for each seriestype row.
|
||||
if !isempty(args)
|
||||
append!(still_to_process, _expand_seriestype_array(d, args))
|
||||
end
|
||||
|
||||
# remove subplot and axis args from d... they will be passed through in the kw_list
|
||||
if !isempty(args)
|
||||
for (k,v) in d
|
||||
for defdict in (_subplot_defaults,
|
||||
_axis_defaults,
|
||||
_axis_defaults_byletter)
|
||||
if haskey(defdict, k)
|
||||
delete!(d, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
args
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# user recipes
|
||||
|
||||
|
||||
function _process_userrecipes(plt::Plot, d::KW, args)
|
||||
still_to_process = RecipeData[]
|
||||
args = _preprocess_args(d, args, still_to_process)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
# we are keeping a stack of series that still need to be processed.
|
||||
# each pass through the loop, we pop one off and apply the recipe.
|
||||
# the recipe will return a list a Series objects... the ones that are
|
||||
# finished (no more args) get added to the kw_list, the ones that are not
|
||||
# are placed on top of the stack and are then processed further.
|
||||
kw_list = KW[]
|
||||
while !isempty(still_to_process)
|
||||
# grab the first in line to be processed and either add it to the kw_list or
|
||||
# pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
|
||||
# for further processing.
|
||||
next_series = popfirst!(still_to_process)
|
||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
||||
if !(typeof(next_series) <: RecipeData)
|
||||
error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
|
||||
end
|
||||
if isempty(next_series.args)
|
||||
_process_userrecipe(plt, kw_list, next_series)
|
||||
else
|
||||
rd_list = RecipesBase.apply_recipe(next_series.d, next_series.args...)
|
||||
prepend!(still_to_process,rd_list)
|
||||
end
|
||||
end
|
||||
|
||||
# don't allow something else to handle it
|
||||
d[:smooth] = false
|
||||
kw_list
|
||||
end
|
||||
|
||||
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||
# process... finish up and add to the kw_list
|
||||
kw = recipedata.d
|
||||
preprocessArgs!(kw)
|
||||
_preprocess_userrecipe(kw)
|
||||
warnOnUnsupported_scales(plt.backend, kw)
|
||||
|
||||
# add the plot index
|
||||
plt.n += 1
|
||||
kw[:series_plotindex] = plt.n
|
||||
|
||||
push!(kw_list, kw)
|
||||
_add_errorbar_kw(kw_list, kw)
|
||||
_add_smooth_kw(kw_list, kw)
|
||||
return
|
||||
end
|
||||
|
||||
function _preprocess_userrecipe(kw::KW)
|
||||
_add_markershape(kw)
|
||||
|
||||
# if there was a grouping, filter the data here
|
||||
_filter_input_data!(kw)
|
||||
|
||||
# map marker_z if it's a Function
|
||||
if isa(get(kw, :marker_z, nothing), Function)
|
||||
# TODO: should this take y and/or z as arguments?
|
||||
kw[:marker_z] = map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# map line_z if it's a Function
|
||||
if isa(get(kw, :line_z, nothing), Function)
|
||||
kw[:line_z] = map(kw[:line_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# convert a ribbon into a fillrange
|
||||
if get(kw, :ribbon, nothing) != nothing
|
||||
make_fillrange_from_ribbon(kw)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function _add_errorbar_kw(kw_list::Vector{KW}, kw::KW)
|
||||
# handle error bars by creating new recipedata data... these will have
|
||||
# the same recipedata index as the recipedata they are copied from
|
||||
for esym in (:xerror, :yerror)
|
||||
if get(kw, esym, nothing) != nothing
|
||||
# we make a copy of the KW and apply an errorbar recipe
|
||||
errkw = copy(kw)
|
||||
errkw[:seriestype] = esym
|
||||
errkw[:label] = ""
|
||||
errkw[:primary] = false
|
||||
push!(kw_list, errkw)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _add_smooth_kw(kw_list::Vector{KW}, kw::KW)
|
||||
# handle smoothing by adding a new series
|
||||
if get(kw, :smooth, false)
|
||||
x, y = kw[:x], kw[:y]
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
sx = [ignorenan_minimum(x), ignorenan_maximum(x)]
|
||||
sy = β .* sx .+ α
|
||||
push!(kw_list, merge(copy(kw), KW(
|
||||
:seriestype => :path,
|
||||
:x => sx,
|
||||
:y => sy,
|
||||
:fillrange => nothing,
|
||||
:label => "",
|
||||
:primary => false,
|
||||
)))
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plot recipes
|
||||
|
||||
# Grab the first in line to be processed and pass it through apply_recipe
|
||||
# to generate a list of RecipeData objects (data + attributes).
|
||||
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
||||
# otherwise we just add the original KW.
|
||||
function _process_plotrecipe(plt::Plot, kw::KW, kw_list::Vector{KW}, still_to_process::Vector{KW})
|
||||
if !isa(get(kw, :seriestype, nothing), Symbol)
|
||||
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
|
||||
push!(kw_list, kw)
|
||||
return
|
||||
end
|
||||
try
|
||||
st = kw[:seriestype]
|
||||
st = kw[:seriestype] = get(_typeAliases, st, st)
|
||||
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
|
||||
for data in datalist
|
||||
preprocessArgs!(data.d)
|
||||
if data.d[:seriestype] == st
|
||||
error("Plot recipe $st returned the same seriestype: $(data.d)")
|
||||
end
|
||||
push!(still_to_process, data.d)
|
||||
end
|
||||
catch err
|
||||
if isa(err, MethodError)
|
||||
push!(kw_list, kw)
|
||||
else
|
||||
rethrow()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# setup plot and subplot
|
||||
|
||||
function _plot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
|
||||
# merge in anything meant for the Plot
|
||||
for kw in kw_list, (k,v) in kw
|
||||
haskey(_plot_defaults, k) && (d[k] = pop!(kw, k))
|
||||
end
|
||||
|
||||
# TODO: init subplots here
|
||||
_update_plot_args(plt, d)
|
||||
if !plt.init
|
||||
plt.o = Base.invokelatest(_create_backend_figure, plt)
|
||||
|
||||
# create the layout and subplots from the inputs
|
||||
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
sp.plt = plt
|
||||
sp.attr[:subplot_index] = idx
|
||||
end
|
||||
|
||||
plt.init = true
|
||||
end
|
||||
|
||||
|
||||
# handle inset subplots
|
||||
insets = plt[:inset_subplots]
|
||||
if insets != nothing
|
||||
if !(typeof(insets) <: AVec)
|
||||
insets = [insets]
|
||||
end
|
||||
for inset in insets
|
||||
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
|
||||
P = typeof(parent)
|
||||
if P <: Integer
|
||||
parent = plt.subplots[parent]
|
||||
elseif P == Symbol
|
||||
parent = plt.spmap[parent]
|
||||
else
|
||||
parent = plt.layout
|
||||
end
|
||||
sp = Subplot(backend(), parent=parent)
|
||||
sp.plt = plt
|
||||
push!(plt.subplots, sp)
|
||||
push!(plt.inset_subplots, sp)
|
||||
sp.attr[:relative_bbox] = bb
|
||||
sp.attr[:subplot_index] = length(plt.subplots)
|
||||
end
|
||||
end
|
||||
plt[:inset_subplots] = nothing
|
||||
end
|
||||
|
||||
function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
|
||||
# we'll keep a map of subplot to an attribute override dict.
|
||||
# Subplot/Axis attributes set by a user/series recipe apply only to the
|
||||
# Subplot object which they belong to.
|
||||
# TODO: allow matrices to still apply to all subplots
|
||||
sp_attrs = Dict{Subplot,Any}()
|
||||
for kw in kw_list
|
||||
# get the Subplot object to which the series belongs.
|
||||
sps = get(kw, :subplot, :auto)
|
||||
sp = get_subplot(plt, _cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
|
||||
kw[:subplot] = sp
|
||||
|
||||
# extract subplot/axis attributes from kw and add to sp_attr
|
||||
attr = KW()
|
||||
for (k,v) in collect(kw)
|
||||
if haskey(_subplot_defaults, k) || haskey(_axis_defaults_byletter, k)
|
||||
attr[k] = pop!(kw, k)
|
||||
end
|
||||
if haskey(_axis_defaults, k)
|
||||
v = pop!(kw, k)
|
||||
for letter in (:x,:y,:z)
|
||||
attr[Symbol(letter,k)] = v
|
||||
end
|
||||
end
|
||||
for k in (:scale,), letter in (:x,:y,:z)
|
||||
# Series recipes may need access to this information
|
||||
lk = Symbol(letter,k)
|
||||
if haskey(attr, lk)
|
||||
kw[lk] = attr[lk]
|
||||
end
|
||||
end
|
||||
end
|
||||
sp_attrs[sp] = attr
|
||||
end
|
||||
|
||||
# override subplot/axis args. `sp_attrs` take precendence
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
attr = if !haskey(d, :subplot) || d[:subplot] == idx
|
||||
merge(d, get(sp_attrs, sp, KW()))
|
||||
else
|
||||
get(sp_attrs, sp, KW())
|
||||
end
|
||||
_update_subplot_args(plt, sp, attr, idx, false)
|
||||
end
|
||||
|
||||
# do we need to link any axes together?
|
||||
link_axes!(plt.layout, plt[:link])
|
||||
end
|
||||
|
||||
# getting ready to add the series... last update to subplot from anything
|
||||
# that might have been added during series recipes
|
||||
function _prepare_subplot(plt::Plot{T}, d::KW) where T
|
||||
st::Symbol = d[:seriestype]
|
||||
sp::Subplot{T} = d[:subplot]
|
||||
sp_idx = get_subplot_index(plt, sp)
|
||||
_update_subplot_args(plt, sp, d, sp_idx, true)
|
||||
|
||||
st = _override_seriestype_check(d, st)
|
||||
|
||||
# change to a 3d projection for this subplot?
|
||||
if is3d(st)
|
||||
sp.attr[:projection] = "3d"
|
||||
end
|
||||
|
||||
# initialize now that we know the first series type
|
||||
if !haskey(sp.attr, :init)
|
||||
_initialize_subplot(plt, sp)
|
||||
sp.attr[:init] = true
|
||||
end
|
||||
sp
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# series types
|
||||
|
||||
function _override_seriestype_check(d::KW, st::Symbol)
|
||||
# do we want to override the series type?
|
||||
if !is3d(st) && !(st in (:contour,:contour3d))
|
||||
z = d[:z]
|
||||
if !isa(z, Nothing) && (size(d[:x]) == size(d[:y]) == size(z))
|
||||
st = (st == :scatter ? :scatter3d : :path3d)
|
||||
d[:seriestype] = st
|
||||
end
|
||||
end
|
||||
st
|
||||
end
|
||||
|
||||
function _prepare_annotations(sp::Subplot, d::KW)
|
||||
# strip out series annotations (those which are based on series x/y coords)
|
||||
# and add them to the subplot attr
|
||||
sp_anns = annotations(sp[:annotations])
|
||||
# series_anns = annotations(pop!(d, :series_annotations, []))
|
||||
# if isa(series_anns, SeriesAnnotations)
|
||||
# series_anns.x = d[:x]
|
||||
# series_anns.y = d[:y]
|
||||
# elseif length(series_anns) > 0
|
||||
# x, y = d[:x], d[:y]
|
||||
# nx, ny, na = map(length, (x,y,series_anns))
|
||||
# n = max(nx, ny, na)
|
||||
# series_anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(series_anns[mod1(i,na)])) for i=1:n]
|
||||
# end
|
||||
# sp.attr[:annotations] = vcat(sp_anns, series_anns)
|
||||
end
|
||||
|
||||
function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol)
|
||||
# adjust extrema and discrete info
|
||||
if st == :image
|
||||
xmin, xmax = ignorenan_extrema(d[:x]); ymin, ymax = ignorenan_extrema(d[:y])
|
||||
expand_extrema!(sp[:xaxis], (xmin, xmax))
|
||||
expand_extrema!(sp[:yaxis], (ymin, ymax))
|
||||
elseif !(st in (:pie, :histogram, :bins2d, :histogram2d))
|
||||
expand_extrema!(sp, d)
|
||||
end
|
||||
# expand for zerolines (axes through origin)
|
||||
if sp[:framestyle] in (:origin, :zerolines)
|
||||
expand_extrema!(sp[:xaxis], 0.0)
|
||||
expand_extrema!(sp[:yaxis], 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
function _add_the_series(plt, sp, d)
|
||||
warnOnUnsupported_args(plt.backend, d)
|
||||
warnOnUnsupported(plt.backend, d)
|
||||
series = Series(d)
|
||||
push!(plt.series_list, series)
|
||||
push!(sp.series_list, series)
|
||||
_series_added(plt, series)
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
# this method recursively applies series recipes when the seriestype is not supported
|
||||
# natively by the backend
|
||||
function _process_seriesrecipe(plt::Plot, d::KW)
|
||||
# replace seriestype aliases
|
||||
st = Symbol(d[:seriestype])
|
||||
st = d[:seriestype] = get(_typeAliases, st, st)
|
||||
|
||||
# shapes shouldn't have fillrange set
|
||||
if d[:seriestype] == :shape
|
||||
d[:fillrange] = nothing
|
||||
end
|
||||
|
||||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
||||
if is_seriestype_supported(st)
|
||||
sp = _prepare_subplot(plt, d)
|
||||
_prepare_annotations(sp, d)
|
||||
_expand_subplot_extrema(sp, d, st)
|
||||
_update_series_attributes!(d, plt, sp)
|
||||
_add_the_series(plt, sp, d)
|
||||
|
||||
else
|
||||
# get a sub list of series for this seriestype
|
||||
datalist = RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
|
||||
|
||||
# assuming there was no error, recursively apply the series recipes
|
||||
for data in datalist
|
||||
if isa(data, RecipeData)
|
||||
preprocessArgs!(data.d)
|
||||
if data.d[:seriestype] == st
|
||||
error("The seriestype didn't change in series recipe $st. This will cause a StackOverflow.")
|
||||
end
|
||||
_process_seriesrecipe(plt, data.d)
|
||||
else
|
||||
@warn("Unhandled recipe: $(data)")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
nothing
|
||||
end
|
||||
+100
-427
@@ -1,18 +1,22 @@
|
||||
|
||||
type CurrentPlot
|
||||
nullableplot::Nullable{AbstractPlot}
|
||||
mutable struct CurrentPlot
|
||||
nullableplot::Union{AbstractPlot, Nothing}
|
||||
end
|
||||
const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}())
|
||||
const CURRENT_PLOT = CurrentPlot(nothing)
|
||||
|
||||
isplotnull() = isnull(CURRENT_PLOT.nullableplot)
|
||||
isplotnull() = CURRENT_PLOT.nullableplot == nothing
|
||||
|
||||
"""
|
||||
current()
|
||||
Returns the Plot object for the current plot
|
||||
"""
|
||||
function current()
|
||||
if isplotnull()
|
||||
error("No current plot/subplot")
|
||||
end
|
||||
get(CURRENT_PLOT.nullableplot)
|
||||
CURRENT_PLOT.nullableplot
|
||||
end
|
||||
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = Nullable(plot))
|
||||
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = plot)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -29,7 +33,7 @@ convertSeriesIndex(plt::Plot, n::Int) = n
|
||||
|
||||
|
||||
"""
|
||||
The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
|
||||
The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
|
||||
|
||||
```
|
||||
plot(args...; kw...) # creates a new plot window, and sets it to be the current
|
||||
@@ -38,18 +42,19 @@ The main plot command. Use `plot` to create a new plot object, and `plot!` to a
|
||||
```
|
||||
|
||||
There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected.
|
||||
When you pass in matrices, it splits by columns. See the documentation for more info.
|
||||
When you pass in matrices, it splits by columns. To see the list of available attributes, use the `plotattr([attr])`
|
||||
function, where `attr` is the symbol `:Series:`, `:Subplot:`, `:Plot` or `:Axis`. Pass any attribute to `plotattr`
|
||||
as a String to look up its docstring; e.g. `plotattr("seriestype")`.
|
||||
"""
|
||||
|
||||
# this creates a new plot with args/kw and sets it to be the current plot
|
||||
function plot(args...; kw...)
|
||||
# this creates a new plot with args/kw and sets it to be the current plot
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
|
||||
# create an empty Plot then process
|
||||
plt = Plot()
|
||||
# plt.user_attr = d
|
||||
_plot!(plt, d, args...)
|
||||
_plot!(plt, d, args)
|
||||
end
|
||||
|
||||
# build a new plot from existing plots
|
||||
@@ -60,7 +65,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||||
|
||||
# build our plot vector from the args
|
||||
n = length(plts_tail) + 1
|
||||
plts = Array(Plot, n)
|
||||
plts = Array{Plot}(undef, n)
|
||||
plts[1] = plt1
|
||||
for (i,plt) in enumerate(plts_tail)
|
||||
plts[i+1] = plt
|
||||
@@ -80,7 +85,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||||
# TODO: replace this with proper processing from a merged user_attr KW
|
||||
# update plot args, first with existing plots, then override with d
|
||||
for p in plts
|
||||
_update_plot_args(plt, p.attr)
|
||||
_update_plot_args(plt, copy(p.attr))
|
||||
plt.n += p.n
|
||||
end
|
||||
_update_plot_args(plt, d)
|
||||
@@ -89,8 +94,21 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||||
plt.o = _create_backend_figure(plt)
|
||||
plt.init = true
|
||||
|
||||
# create the layout and initialize the subplots
|
||||
series_attr = KW()
|
||||
for (k,v) in d
|
||||
if haskey(_series_defaults, k)
|
||||
series_attr[k] = pop!(d,k)
|
||||
end
|
||||
end
|
||||
|
||||
# create the layout
|
||||
plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts))
|
||||
|
||||
# do we need to link any axes together?
|
||||
link_axes!(plt.layout, plt[:link])
|
||||
|
||||
# initialize the subplots
|
||||
cmdidx = 1
|
||||
for (idx, sp) in enumerate(plt.subplots)
|
||||
_initialize_subplot(plt, sp)
|
||||
serieslist = series_list(sp)
|
||||
@@ -100,35 +118,22 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||||
sp.plt = plt
|
||||
sp.attr[:subplot_index] = idx
|
||||
for series in serieslist
|
||||
merge!(series.d, series_attr)
|
||||
_add_defaults!(series.d, plt, sp, cmdidx)
|
||||
push!(plt.series_list, series)
|
||||
_series_added(plt, series)
|
||||
cmdidx += 1
|
||||
end
|
||||
end
|
||||
|
||||
# # just in case the backend needs to set up the plot (make it current or something)
|
||||
# _prepare_plot_object(plt)
|
||||
|
||||
# first apply any args for the subplots
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
_update_subplot_args(plt, sp, d, idx, remove_pair = false)
|
||||
_update_subplot_args(plt, sp, d, idx, false)
|
||||
end
|
||||
|
||||
# # now we can get rid of the axis keys without a letter
|
||||
# for k in keys(_axis_defaults)
|
||||
# delete!(d, k)
|
||||
# for letter in (:x,:y,:z)
|
||||
# delete!(d, Symbol(letter,k))
|
||||
# end
|
||||
# end
|
||||
|
||||
# do we need to link any axes together?
|
||||
link_axes!(plt.layout, plt[:link])
|
||||
|
||||
# finish up
|
||||
current(plt)
|
||||
if get(d, :show, default(:show))
|
||||
gui()
|
||||
end
|
||||
_do_plot_show(plt, get(d, :show, default(:show)))
|
||||
plt
|
||||
end
|
||||
|
||||
@@ -150,341 +155,75 @@ function plot!(plt::Plot, args...; kw...)
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
# merge!(plt.user_attr, d)
|
||||
_plot!(plt, d, args...)
|
||||
end
|
||||
|
||||
function strip_first_letter(s::Symbol)
|
||||
str = string(s)
|
||||
str[1:1], Symbol(str[2:end])
|
||||
end
|
||||
|
||||
|
||||
# this method recursively applies series recipes when the seriestype is not supported
|
||||
# natively by the backend
|
||||
function _apply_series_recipe(plt::Plot, d::KW)
|
||||
st = d[:seriestype]
|
||||
# @show st
|
||||
if st in supported_types()
|
||||
|
||||
# getting ready to add the series... last update to subplot from anything
|
||||
# that might have been added during series recipes
|
||||
sp = d[:subplot]
|
||||
sp_idx = get_subplot_index(plt, sp)
|
||||
_update_subplot_args(plt, sp, d, sp_idx)
|
||||
|
||||
# do we want to override the series type?
|
||||
if !is3d(st) && d[:z] != nothing && (size(d[:x]) == size(d[:y]) == size(d[:z]))
|
||||
st = d[:seriestype] = (st == :scatter ? :scatter3d : :path3d)
|
||||
end
|
||||
|
||||
# change to a 3d projection for this subplot?
|
||||
if is3d(st)
|
||||
sp.attr[:projection] = "3d"
|
||||
end
|
||||
|
||||
# initialize now that we know the first series type
|
||||
if !haskey(sp.attr, :init)
|
||||
_initialize_subplot(plt, sp)
|
||||
sp.attr[:init] = true
|
||||
end
|
||||
|
||||
# strip out series annotations (those which are based on series x/y coords)
|
||||
# and add them to the subplot attr
|
||||
sp_anns = annotations(sp[:annotations])
|
||||
anns = annotations(pop!(d, :series_annotations, []))
|
||||
if length(anns) > 0
|
||||
x, y = d[:x], d[:y]
|
||||
nx, ny, na = map(length, (x,y,anns))
|
||||
n = max(nx, ny, na)
|
||||
anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n]
|
||||
end
|
||||
sp.attr[:annotations] = vcat(sp_anns, anns)
|
||||
|
||||
# adjust extrema and discrete info
|
||||
if st == :image
|
||||
w, h = size(d[:z])
|
||||
expand_extrema!(sp[:xaxis], (0,w))
|
||||
expand_extrema!(sp[:yaxis], (0,h))
|
||||
sp[:yaxis].d[:flip] = true
|
||||
elseif !(st in (:pie, :histogram, :histogram2d))
|
||||
expand_extrema!(sp, d)
|
||||
end
|
||||
|
||||
|
||||
# add the series!
|
||||
warnOnUnsupported_args(plt.backend, d)
|
||||
warnOnUnsupported(plt.backend, d)
|
||||
series = Series(d)
|
||||
push!(plt.series_list, series)
|
||||
# @show series
|
||||
|
||||
_series_added(plt, series)
|
||||
|
||||
else
|
||||
# get a sub list of series for this seriestype
|
||||
datalist = try
|
||||
RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
|
||||
catch
|
||||
warn("Exception during apply_recipe(Val{$st}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))")
|
||||
rethrow()
|
||||
end
|
||||
|
||||
# assuming there was no error, recursively apply the series recipes
|
||||
for data in datalist
|
||||
if isa(data, RecipeData)
|
||||
_apply_series_recipe(plt, data.d)
|
||||
else
|
||||
warn("Unhandled recipe: $(data)")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
_plot!(plt, d, args)
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
# this is the core plotting function. recursively apply recipes to build
|
||||
# a list of series KW dicts.
|
||||
# note: at entry, we only have those preprocessed args which were passed in... no default values yet
|
||||
function _plot!(plt::Plot, d::KW, args...)
|
||||
# d = plt.user_attr
|
||||
function _plot!(plt::Plot, d::KW, args::Tuple)
|
||||
d[:plot_object] = plt
|
||||
|
||||
# the grouping mechanism is a recipe on a GroupBy object
|
||||
# we simply add the GroupBy object to the front of the args list to allow
|
||||
# the recipe to be applied
|
||||
if haskey(d, :group)
|
||||
args = (extractGroupArgs(d[:group], args...), args...)
|
||||
if !isempty(args) && !isdefined(Main, :StatPlots) &&
|
||||
first(split(string(typeof(args[1])), ".")) == "DataFrames"
|
||||
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatPlots")
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
# "USER RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
# we are keeping a queue of series that still need to be processed.
|
||||
# each pass through the loop, we pop one off and apply the recipe.
|
||||
# the recipe will return a list a Series objects... the ones that are
|
||||
# finished (no more args) get added to the kw_list, and the rest go into the queue
|
||||
# for processing.
|
||||
kw_list = _process_userrecipes(plt, d, args)
|
||||
|
||||
# @info(1)
|
||||
# map(DD, kw_list)
|
||||
|
||||
|
||||
# --------------------------------
|
||||
# "PLOT RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# "plot recipe", which acts like a series type, and is processed before
|
||||
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
|
||||
# we get inputs which have been fully processed by "user recipes" and "type recipes",
|
||||
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
|
||||
still_to_process = kw_list
|
||||
kw_list = KW[]
|
||||
still_to_process = isempty(args) ? [] : [RecipeData(copy(d), args)]
|
||||
while !isempty(still_to_process)
|
||||
|
||||
# grab the first in line to be processed and pass it through apply_recipe
|
||||
# to generate a list of RecipeData objects (data + attributes)
|
||||
next_series = shift!(still_to_process)
|
||||
for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
|
||||
|
||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
||||
if !(typeof(recipedata) <: RecipeData)
|
||||
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
|
||||
end
|
||||
|
||||
if isempty(recipedata.args)
|
||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||
# process... finish up and add to the kw_list
|
||||
kw = recipedata.d
|
||||
_add_markershape(kw)
|
||||
|
||||
# if there was a grouping, filter the data here
|
||||
_filter_input_data!(kw)
|
||||
|
||||
# map marker_z if it's a Function
|
||||
if isa(get(kw, :marker_z, nothing), Function)
|
||||
# TODO: should this take y and/or z as arguments?
|
||||
kw[:marker_z] = map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# map line_z if it's a Function
|
||||
if isa(get(kw, :line_z, nothing), Function)
|
||||
kw[:line_z] = map(kw[:line_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# convert a ribbon into a fillrange
|
||||
if get(kw, :ribbon, nothing) != nothing
|
||||
make_fillrange_from_ribbon(kw)
|
||||
end
|
||||
|
||||
# add the plot index
|
||||
plt.n += 1
|
||||
kw[:series_plotindex] = plt.n
|
||||
|
||||
# check that the backend will support the command and add it to the list
|
||||
warnOnUnsupported_scales(plt.backend, kw)
|
||||
push!(kw_list, kw)
|
||||
|
||||
# handle error bars by creating new recipedata data... these will have
|
||||
# the same recipedata index as the recipedata they are copied from
|
||||
for esym in (:xerror, :yerror)
|
||||
if get(d, esym, nothing) != nothing
|
||||
# we make a copy of the KW and apply an errorbar recipe
|
||||
errkw = copy(kw)
|
||||
errkw[:seriestype] = esym
|
||||
errkw[:label] = ""
|
||||
errkw[:primary] = false
|
||||
push!(kw_list, errkw)
|
||||
end
|
||||
end
|
||||
|
||||
# handle smoothing by adding a new series
|
||||
if get(d, :smooth, false)
|
||||
x, y = kw[:x], kw[:y]
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
sx = [minimum(x), maximum(x)]
|
||||
sy = β * sx + α
|
||||
push!(kw_list, merge(copy(kw), KW(
|
||||
:seriestype => :path,
|
||||
:x => sx,
|
||||
:y => sy,
|
||||
:fillrange => nothing,
|
||||
:label => "",
|
||||
:primary => false,
|
||||
)))
|
||||
end
|
||||
|
||||
else
|
||||
# args are non-empty, so there's still processing to do... add it back to the queue
|
||||
push!(still_to_process, recipedata)
|
||||
end
|
||||
end
|
||||
next_kw = popfirst!(still_to_process)
|
||||
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
|
||||
end
|
||||
|
||||
# don't allow something else to handle it
|
||||
d[:smooth] = false
|
||||
# @info(2)
|
||||
# map(DD, kw_list)
|
||||
|
||||
# merge in anything meant for plot/subplot/axis
|
||||
for kw in kw_list
|
||||
for (k,v) in kw
|
||||
for defdict in (_plot_defaults,)
|
||||
# _subplot_defaults,
|
||||
# _axis_defaults,
|
||||
# _axis_defaults_byletter)
|
||||
if haskey(defdict, k)
|
||||
d[k] = pop!(kw, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: init subplots here
|
||||
_update_plot_args(plt, d)
|
||||
if !plt.init
|
||||
plt.o = _create_backend_figure(plt)
|
||||
# DD(d)
|
||||
|
||||
# create the layout and subplots from the inputs
|
||||
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
sp.plt = plt
|
||||
sp.attr[:subplot_index] = idx
|
||||
end
|
||||
|
||||
plt.init = true
|
||||
end
|
||||
|
||||
|
||||
# handle inset subplots
|
||||
insets = plt[:inset_subplots]
|
||||
if insets != nothing
|
||||
for inset in insets
|
||||
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
|
||||
P = typeof(parent)
|
||||
if P <: Integer
|
||||
parent = plt.subplots[parent]
|
||||
elseif P == Symbol
|
||||
parent = plt.spmap[parent]
|
||||
else
|
||||
parent = plt.layout
|
||||
end
|
||||
sp = Subplot(backend(), parent=parent)
|
||||
sp.plt = plt
|
||||
sp.attr[:relative_bbox] = bb
|
||||
push!(plt.subplots, sp)
|
||||
sp.attr[:subplot_index] = length(plt.subplots)
|
||||
push!(plt.inset_subplots, sp)
|
||||
end
|
||||
end
|
||||
|
||||
# we'll keep a map of subplot to an attribute override dict.
|
||||
# any series which belong to that subplot
|
||||
sp_attrs = Dict{Subplot,Any}()
|
||||
for kw in kw_list
|
||||
# get the Subplot object to which the series belongs
|
||||
sp = get(kw, :subplot, :auto)
|
||||
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
|
||||
sp = if sp == :auto
|
||||
cycle(plt.subplots, command_idx)
|
||||
# mod1(command_idx, length(plt.subplots))
|
||||
else
|
||||
slice_arg(sp, command_idx)
|
||||
end
|
||||
sp = kw[:subplot] = get_subplot(plt, sp)
|
||||
# idx = get_subplot_index(plt, sp)
|
||||
attr = KW()
|
||||
|
||||
for (k,v) in kw
|
||||
for defdict in (_subplot_defaults,
|
||||
_axis_defaults,
|
||||
_axis_defaults_byletter)
|
||||
if haskey(defdict, k)
|
||||
attr[k] = pop!(kw, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
sp_attrs[sp] = attr
|
||||
end
|
||||
|
||||
|
||||
|
||||
# # just in case the backend needs to set up the plot (make it current or something)
|
||||
# _prepare_plot_object(plt)
|
||||
|
||||
# first apply any args for the subplots
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
# if we picked up any subplot-specific overrides, merge them here
|
||||
attr = merge(d, get(sp_attrs, sp, KW()))
|
||||
# DD(attr, "sp$idx")
|
||||
_update_subplot_args(plt, sp, attr, idx, remove_pair = false)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# do we need to link any axes together?
|
||||
link_axes!(plt.layout, plt[:link])
|
||||
# --------------------------------
|
||||
# Plot/Subplot/Layout setup
|
||||
# --------------------------------
|
||||
_plot_setup(plt, d, kw_list)
|
||||
_subplot_setup(plt, d, kw_list)
|
||||
|
||||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
||||
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
|
||||
|
||||
# this is it folks!
|
||||
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes
|
||||
# --------------------------------
|
||||
# "SERIES RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# @info(3)
|
||||
# map(DD, kw_list)
|
||||
|
||||
for kw in kw_list
|
||||
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
|
||||
sp::Subplot = kw[:subplot]
|
||||
# idx = get_subplot_index(plt, sp)
|
||||
|
||||
# # get the Subplot object to which the series belongs
|
||||
# sp = get(kw, :subplot, :auto)
|
||||
# sp = if sp == :auto
|
||||
# mod1(i,length(plt.subplots))
|
||||
# else
|
||||
# slice_arg(sp, i)
|
||||
# end
|
||||
# sp = kw[:subplot] = get_subplot(plt, sp)
|
||||
sp = kw[:subplot]
|
||||
idx = get_subplot_index(plt, sp)
|
||||
|
||||
# # strip out series annotations (those which are based on series x/y coords)
|
||||
# # and add them to the subplot attr
|
||||
# sp_anns = annotations(sp[:annotations])
|
||||
# anns = annotations(pop!(kw, :series_annotations, []))
|
||||
# if length(anns) > 0
|
||||
# x, y = kw[:x], kw[:y]
|
||||
# nx, ny, na = map(length, (x,y,anns))
|
||||
# n = max(nx, ny, na)
|
||||
# anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n]
|
||||
# end
|
||||
# sp.attr[:annotations] = vcat(sp_anns, anns)
|
||||
|
||||
# we update subplot args in case something like the color palatte is part of the recipe
|
||||
_update_subplot_args(plt, sp, kw, idx)
|
||||
# # we update subplot args in case something like the color palatte is part of the recipe
|
||||
# _update_subplot_args(plt, sp, kw, idx, true)
|
||||
|
||||
# set default values, select from attribute cycles, and generally set the final attributes
|
||||
_add_defaults!(kw, plt, sp, command_idx)
|
||||
_add_defaults!(kw, plt, sp, command_idx(kw_list,kw))
|
||||
|
||||
# now we have a fully specified series, with colors chosen. we must recursively handle
|
||||
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
|
||||
@@ -492,29 +231,23 @@ function _plot!(plt::Plot, d::KW, args...)
|
||||
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
|
||||
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
|
||||
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||||
_apply_series_recipe(plt, kw)
|
||||
_process_seriesrecipe(plt, kw)
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
|
||||
current(plt)
|
||||
|
||||
# note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
|
||||
# # do we want to show it?
|
||||
# if haskey(d, :show) && d[:show]
|
||||
if get(d, :show, default(:show))
|
||||
gui()
|
||||
end
|
||||
# do we want to force display?
|
||||
# if plt[:show]
|
||||
# gui(plt)
|
||||
# end
|
||||
_do_plot_show(plt, plt[:show])
|
||||
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
function _replace_linewidth(d::KW)
|
||||
# get a good default linewidth... 0 for surface and heatmaps
|
||||
if get(d, :linewidth, :auto) == :auto
|
||||
d[:linewidth] = (get(d, :seriestype, :path) in (:surface,:heatmap,:image) ? 0 : 1)
|
||||
end
|
||||
end
|
||||
|
||||
# we're getting ready to display/output. prep for layout calcs, then update
|
||||
# the plot object after
|
||||
function prepare_output(plt::Plot)
|
||||
@@ -540,81 +273,21 @@ function prepare_output(plt::Plot)
|
||||
_update_plot_object(plt)
|
||||
end
|
||||
|
||||
function prepared_object(plt::Plot)
|
||||
function backend_object(plt::Plot)
|
||||
prepare_output(plt)
|
||||
plt.o
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# plot to a Subplot
|
||||
|
||||
# function get_indices(orig, labels)
|
||||
# Int[findnext(labels, l, 1) for l in orig]
|
||||
# end
|
||||
|
||||
# # TODO: remove?? this is the old way of handling discrete data... should be
|
||||
# # replaced by the Axis type and logic
|
||||
# function setTicksFromStringVector(plt::Plot, d::KW, di::KW, letter)
|
||||
# sym = Symbol(letter)
|
||||
# ticksym = Symbol(letter * "ticks")
|
||||
# pargs = plt.attr
|
||||
# v = di[sym]
|
||||
#
|
||||
# # do we really want to do this?
|
||||
# typeof(v) <: AbstractArray || return
|
||||
# isempty(v) && return
|
||||
# trueOrAllTrue(_ -> typeof(_) <: AbstractString, v) || return
|
||||
#
|
||||
# # compute the ticks and labels
|
||||
# ticks, labels = if ticksType(pargs[ticksym]) == :ticks_and_labels
|
||||
# # extend the existing ticks and labels. only add to labels if they're new!
|
||||
# ticks, labels = pargs[ticksym]
|
||||
# newlabels = filter(_ -> !(_ in labels), unique(v))
|
||||
# newticks = if isempty(ticks)
|
||||
# collect(1:length(newlabels))
|
||||
# else
|
||||
# maximum(ticks) + collect(1:length(newlabels))
|
||||
# end
|
||||
# ticks = vcat(ticks, newticks)
|
||||
# labels = vcat(labels, newlabels)
|
||||
# ticks, labels
|
||||
# else
|
||||
# # create new ticks and labels
|
||||
# newlabels = unique(v)
|
||||
# collect(1:length(newlabels)), newlabels
|
||||
# end
|
||||
#
|
||||
# d[ticksym] = ticks, labels
|
||||
# plt.attr[ticksym] = ticks, labels
|
||||
#
|
||||
# # add an origsym field so that later on we can re-compute the x vector if ticks change
|
||||
# origsym = Symbol(letter * "orig")
|
||||
# di[origsym] = v
|
||||
# di[sym] = get_indices(v, labels)
|
||||
#
|
||||
# # loop through existing plt.seriesargs and adjust indices if there is an origsym key
|
||||
# for sargs in plt.seriesargs
|
||||
# if haskey(sargs, origsym)
|
||||
# # TODO: might need to call the setindex function instead to trigger a plot update for some backends??
|
||||
# sargs[sym] = get_indices(sargs[origsym], labels)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# function Base.copy(plt::Plot)
|
||||
# backend(plt.backend)
|
||||
# plt2 = plot(; plt.attr...)
|
||||
# for sargs in plt.seriesargs
|
||||
# sargs = filter((k,v) -> haskey(_series_defaults,k), sargs)
|
||||
# plot!(plt2; sargs...)
|
||||
# end
|
||||
# plt2
|
||||
# end
|
||||
function plot(sp::Subplot, args...; kw...)
|
||||
plt = sp.plt
|
||||
plot(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots))
|
||||
end
|
||||
function plot!(sp::Subplot, args...; kw...)
|
||||
plt = sp.plt
|
||||
plot!(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots))
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
const _attribute_defaults = Dict(:Series => _series_defaults,
|
||||
:Subplot => _subplot_defaults,
|
||||
:Plot => _plot_defaults,
|
||||
:Axis => _axis_defaults)
|
||||
|
||||
attrtypes() = join(keys(_attribute_defaults), ", ")
|
||||
attributes(attrtype::Symbol) = sort(collect(keys(_attribute_defaults[attrtype])))
|
||||
|
||||
function lookup_aliases(attrtype, attribute)
|
||||
attribute = Symbol(attribute)
|
||||
attribute = in(attribute, keys(_keyAliases)) ? _keyAliases[attribute] : attribute
|
||||
in(attribute, keys(_attribute_defaults[attrtype])) && return attribute
|
||||
error("There is no attribute named $attribute in $attrtype")
|
||||
end
|
||||
|
||||
"""
|
||||
plotattr([attr])
|
||||
|
||||
Look up the properties of a Plots attribute, or specify an attribute type. Call `plotattr()` for options.
|
||||
The information is the same as that given on https://juliaplots.github.io/attributes/.
|
||||
"""
|
||||
function plotattr()
|
||||
println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())")
|
||||
end
|
||||
|
||||
function plotattr(attrtype::Symbol)
|
||||
in(attrtype, keys(_attribute_defaults)) || error("Viable options are $(attrtypes())")
|
||||
println("Defined $attrtype attributes are:\n$(join(attributes(attrtype), ", "))")
|
||||
end
|
||||
|
||||
function plotattr(attribute::AbstractString)
|
||||
attribute = Symbol(attribute)
|
||||
attribute = in(attribute, keys(_keyAliases)) ? _keyAliases[attribute] : attribute
|
||||
for (k, v) in _attribute_defaults
|
||||
if in(attribute, keys(v))
|
||||
return plotattr(k, "$attribute")
|
||||
end
|
||||
end
|
||||
error("There is no attribute named $attribute")
|
||||
end
|
||||
|
||||
function plotattr(attrtype::Symbol, attribute::AbstractString)
|
||||
in(attrtype, keys(_attribute_defaults)) || ArgumentError("`attrtype` must match one of $(attrtypes())")
|
||||
|
||||
attribute = Symbol(lookup_aliases(attrtype, attribute))
|
||||
|
||||
desc = get(_arg_desc, attribute, "")
|
||||
first_period_idx = findfirst(isequal('.'), desc)
|
||||
typedesc = desc[1:first_period_idx-1]
|
||||
desc = strip(desc[first_period_idx+1:end])
|
||||
als = keys(filter((_,v)->v==attribute, _keyAliases)) |> collect |> sort
|
||||
als = join(map(string,als), ", ")
|
||||
def = _attribute_defaults[attrtype][attribute]
|
||||
|
||||
|
||||
# Looks up the different elements and plots them
|
||||
println("$attribute ", typedesc == "" ? "" : "{$typedesc}", "\n",
|
||||
als == "" ? "" : "$als\n",
|
||||
"\n$desc\n",
|
||||
"$(attrtype) attribute, ", def == "" ? "" : " default: $def")
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
function _precompile_()
|
||||
ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
|
||||
end
|
||||
+630
-741
File diff suppressed because it is too large
Load Diff
+649
@@ -0,0 +1,649 @@
|
||||
|
||||
|
||||
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
|
||||
# Special handling for: no args, xmin/xmax, parametric, dataframes
|
||||
# Then once inputs have been converted, build the series args, map functions, etc.
|
||||
# This should cut down on boilerplate code and allow more focused dispatch on type
|
||||
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
|
||||
|
||||
const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
|
||||
|
||||
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(d, :seriestype, :none))
|
||||
|
||||
# unknown
|
||||
convertToAnyVector(x, d::KW) = error("No user recipe defined for $(typeof(x))")
|
||||
|
||||
# missing
|
||||
convertToAnyVector(v::Nothing, d::KW) = Any[nothing], nothing
|
||||
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
|
||||
|
||||
# numeric vector
|
||||
convertToAnyVector(v::AVec{T}, d::KW) where {T<:Number} = Any[v], nothing
|
||||
|
||||
# string vector
|
||||
convertToAnyVector(v::AVec{T}, d::KW) where {T<:AbstractString} = Any[v], nothing
|
||||
|
||||
function convertToAnyVector(v::AMat, d::KW)
|
||||
if all3D(d)
|
||||
Any[Surface(v)]
|
||||
else
|
||||
Any[v[:,i] for i in 1:size(v,2)]
|
||||
end, nothing
|
||||
end
|
||||
|
||||
# function
|
||||
convertToAnyVector(f::Function, d::KW) = Any[f], nothing
|
||||
|
||||
# surface
|
||||
convertToAnyVector(s::Surface, d::KW) = Any[s], nothing
|
||||
|
||||
# volume
|
||||
convertToAnyVector(v::Volume, d::KW) = Any[v], nothing
|
||||
|
||||
# # vector of OHLC
|
||||
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
|
||||
|
||||
# # dates
|
||||
convertToAnyVector(dts::AVec{D}, d::KW) where {D<:Union{Date,DateTime}} = Any[dts], nothing
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function convertToAnyVector(v::AVec, d::KW)
|
||||
if all(x -> typeof(x) <: Number, v)
|
||||
# all real numbers wrap the whole vector as one item
|
||||
Any[convert(Vector{Float64}, v)], nothing
|
||||
else
|
||||
# something else... treat each element as an item
|
||||
vcat(Any[convertToAnyVector(vi, d)[1] for vi in v]...), nothing
|
||||
# Any[vi for vi in v], nothing
|
||||
end
|
||||
end
|
||||
|
||||
convertToAnyVector(t::Tuple, d::KW) = Any[t], nothing
|
||||
|
||||
|
||||
function convertToAnyVector(args...)
|
||||
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
|
||||
# result in that array being shared. push!, etc will add too many items to that array
|
||||
|
||||
compute_x(x::Nothing, y::Nothing, z) = 1:size(z,1)
|
||||
compute_x(x::Nothing, y, z) = 1:size(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = copy(x)
|
||||
|
||||
# compute_y(x::Void, y::Function, z) = error()
|
||||
compute_y(x::Nothing, y::Nothing, z) = 1:size(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = copy(y)
|
||||
|
||||
compute_z(x, y, z::Function) = map(z, x, y)
|
||||
compute_z(x, y, z::AbstractMatrix) = Surface(z)
|
||||
compute_z(x, y, z::Nothing) = nothing
|
||||
compute_z(x, y, z) = copy(z)
|
||||
|
||||
nobigs(v::AVec{BigFloat}) = map(Float64, v)
|
||||
nobigs(v::AVec{BigInt}) = map(Int64, v)
|
||||
nobigs(v) = v
|
||||
|
||||
@noinline function compute_xyz(x, y, z)
|
||||
x = compute_x(x,y,z)
|
||||
y = compute_y(x,y,z)
|
||||
z = compute_z(x,y,z)
|
||||
nobigs(x), nobigs(y), nobigs(z)
|
||||
end
|
||||
|
||||
# not allowed
|
||||
compute_xyz(x::Nothing, y::FuncOrFuncs{F}, z) where {F<:Function} = error("If you want to plot the function `$y`, you need to define the x values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs{F}) where {F<:Function} = error("If you want to plot the function `$z`, you need to define x and y values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
# we are going to build recipes to do the processing and splitting of the args
|
||||
|
||||
# ensure we dispatch to the slicer
|
||||
struct SliceIt end
|
||||
|
||||
# the catch-all recipes
|
||||
@recipe function f(::Type{SliceIt}, x, y, z)
|
||||
|
||||
# handle data with formatting attached
|
||||
if typeof(x) <: Formatted
|
||||
xformatter := x.formatter
|
||||
x = x.data
|
||||
end
|
||||
if typeof(y) <: Formatted
|
||||
yformatter := y.formatter
|
||||
y = y.data
|
||||
end
|
||||
if typeof(z) <: Formatted
|
||||
zformatter := z.formatter
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs, _ = convertToAnyVector(x, plotattributes)
|
||||
ys, _ = convertToAnyVector(y, plotattributes)
|
||||
zs, _ = convertToAnyVector(z, plotattributes)
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges, _ = if typeof(fr) <: Number
|
||||
([fr],nothing)
|
||||
else
|
||||
convertToAnyVector(fr, plotattributes)
|
||||
end
|
||||
mf = length(fillranges)
|
||||
|
||||
rib = pop!(plotattributes, :ribbon, nothing)
|
||||
ribbons, _ = if typeof(rib) <: Number
|
||||
([fr],nothing)
|
||||
else
|
||||
convertToAnyVector(rib, plotattributes)
|
||||
end
|
||||
mr = length(ribbons)
|
||||
|
||||
# @show zs
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
if mx > 0 && my > 0 && mz > 0
|
||||
for i in 1:max(mx, my, mz)
|
||||
# add a new series
|
||||
di = copy(plotattributes)
|
||||
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
|
||||
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
|
||||
|
||||
# handle fillrange
|
||||
fr = fillranges[mod1(i,mf)]
|
||||
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
|
||||
|
||||
# handle ribbons
|
||||
rib = ribbons[mod1(i,mr)]
|
||||
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
|
||||
|
||||
push!(series_list, RecipeData(di, ()))
|
||||
end
|
||||
end
|
||||
nothing # don't add a series for the main block
|
||||
end
|
||||
|
||||
# this is the default "type recipe"... just pass the object through
|
||||
@recipe f(::Type{T}, v::T) where {T<:Any} = v
|
||||
|
||||
# this should catch unhandled "series recipes" and error with a nice message
|
||||
@recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
|
||||
|
||||
_apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1]
|
||||
|
||||
# Handle type recipes when the recipe is defined on the elements.
|
||||
# This sort of recipe should return a pair of functions... one to convert to number,
|
||||
# and one to format tick values.
|
||||
function _apply_type_recipe(d, v::AbstractArray)
|
||||
isempty(v) && return Float64[]
|
||||
args = RecipesBase.apply_recipe(d, typeof(v[1]), v[1])[1].args
|
||||
if length(args) == 2 && typeof(args[1]) <: Function && typeof(args[2]) <: Function
|
||||
numfunc, formatter = args
|
||||
Formatted(map(numfunc, v), formatter)
|
||||
else
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
# # special handling for Surface... need to properly unwrap and re-wrap
|
||||
# function _apply_type_recipe(d, v::Surface)
|
||||
# T = eltype(v.surf)
|
||||
# @show T
|
||||
# if T <: Integer || T <: AbstractFloat
|
||||
# v
|
||||
# else
|
||||
# ret = _apply_type_recipe(d, v.surf)
|
||||
# if typeof(ret) <: Formatted
|
||||
# Formatted(Surface(ret.data), ret.formatter)
|
||||
# else
|
||||
# v
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# don't do anything for ints or floats
|
||||
_apply_type_recipe(d, v::AbstractArray{T}) where {T<:Union{Integer,AbstractFloat}} = v
|
||||
|
||||
# handle "type recipes" by converting inputs, and then either re-calling or slicing
|
||||
@recipe function f(x, y, z)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
y === newy || (did_replace = true)
|
||||
newz = _apply_type_recipe(plotattributes, z)
|
||||
z === newz || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy, newz
|
||||
else
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
end
|
||||
@recipe function f(x, y)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
y === newy || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy
|
||||
else
|
||||
SliceIt, x, y, nothing
|
||||
end
|
||||
end
|
||||
@recipe function f(y)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
if y !== newy
|
||||
newy
|
||||
else
|
||||
SliceIt, nothing, y, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if there's more than 3 inputs, it can't be passed directly to SliceIt
|
||||
# so we'll apply_type_recipe to all of them
|
||||
@recipe function f(v1, v2, v3, v4, vrest...)
|
||||
did_replace = false
|
||||
newargs = map(v -> begin
|
||||
newv = _apply_type_recipe(plotattributes, v)
|
||||
if newv !== v
|
||||
did_replace = true
|
||||
end
|
||||
newv
|
||||
end, (v1, v2, v3, v4, vrest...))
|
||||
if !did_replace
|
||||
error("Couldn't process recipe args: $(map(typeof, (v1, v2, v3, v4, vrest...)))")
|
||||
end
|
||||
newargs
|
||||
end
|
||||
|
||||
|
||||
# # --------------------------------------------------------------------
|
||||
# # 1 argument
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# helper function to ensure relevant attributes are wrapped by Surface
|
||||
function wrap_surfaces(d::KW)
|
||||
if haskey(d, :fill_z)
|
||||
v = d[:fill_z]
|
||||
if !isa(v, Surface)
|
||||
d[:fill_z] = Surface(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@recipe f(n::Integer) = is3d(get(plotattributes,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
|
||||
|
||||
# return a surface if this is a 3d plot, otherwise let it be sliced up
|
||||
@recipe function f(mat::AMat{T}) where T<:Union{Integer,AbstractFloat}
|
||||
if all3D(plotattributes)
|
||||
n,m = size(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
SliceIt, nothing, mat, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface
|
||||
@recipe function f(fmt::Formatted{T}) where T<:AbstractMatrix
|
||||
if all3D(plotattributes)
|
||||
mat = fmt.data
|
||||
n,m = size(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter)
|
||||
else
|
||||
SliceIt, nothing, fmt, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# assume this is a Volume, so construct one
|
||||
@recipe function f(vol::AbstractArray{T,3}, args...) where T<:Number
|
||||
seriestype := :volume
|
||||
SliceIt, nothing, Volume(vol, args...), nothing
|
||||
end
|
||||
|
||||
|
||||
# # images - grays
|
||||
|
||||
@recipe function f(mat::AMat{T}) where T<:Gray
|
||||
n, m = size(mat)
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, 1:m, 1:n, Surface(convert(Matrix{Float64}, mat))
|
||||
end
|
||||
end
|
||||
|
||||
# # images - colors
|
||||
|
||||
@recipe function f(mat::AMat{T}) where T<:Colorant
|
||||
n, m = size(mat)
|
||||
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, 1:m, 1:n, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# # plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype --> :shape
|
||||
coords(shape)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AVec{Shape})
|
||||
seriestype --> :shape
|
||||
coords(shapes)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AMat{Shape})
|
||||
seriestype --> :shape
|
||||
for j in 1:size(shapes,2)
|
||||
@series coords(vec(shapes[:,j]))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# function without range... use the current range of the x-axis
|
||||
|
||||
@recipe function f(f::FuncOrFuncs{F}) where F<:Function
|
||||
plt = plotattributes[:plot_object]
|
||||
xmin, xmax = try
|
||||
axis_limits(plt[1][:xaxis])
|
||||
catch
|
||||
xm = tryrange(f, [-5,-1,0,0.01])
|
||||
xm, tryrange(f, filter(x->x>xm, [5,1,0.99, 0, -0.01]))
|
||||
end
|
||||
|
||||
f, xmin, xmax
|
||||
end
|
||||
|
||||
# try some intervals over which the function may be defined
|
||||
function tryrange(F::AbstractArray, vec)
|
||||
rets = [tryrange(f, vec) for f in F] # get the preferred for each
|
||||
maxind = maximum(indexin(rets, vec)) # get the last attempt that succeeded (most likely to fit all)
|
||||
rets .= [tryrange(f, vec[maxind:maxind]) for f in F] # ensure that all functions compute there
|
||||
rets[1]
|
||||
end
|
||||
|
||||
function tryrange(F, vec)
|
||||
for v in vec
|
||||
try
|
||||
tmp = F(v)
|
||||
return v
|
||||
catch
|
||||
end
|
||||
end
|
||||
error("$F is not a Function, or is not defined at any of the values $vec")
|
||||
end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 2 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # if functions come first, just swap the order (not to be confused with parametric functions...
|
||||
# # as there would be more than one function passed in)
|
||||
|
||||
@recipe function f(f::FuncOrFuncs{F}, x) where F<:Function
|
||||
F2 = typeof(x)
|
||||
@assert !(F2 <: Function || (F2 <: AbstractArray && F2.parameters[1] <: Function)) # otherwise we'd hit infinite recursion here
|
||||
x, f
|
||||
end
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 3 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # 3d line or scatter
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, z::AVec)
|
||||
# st = get(d, :seriestype, :none)
|
||||
# if st == :scatter
|
||||
# d[:seriestype] = :scatter3d
|
||||
# elseif !is3d(st)
|
||||
# d[:seriestype] = :path3d
|
||||
# end
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
@recipe function f(x::AMat, y::AMat, z::AMat)
|
||||
# st = get(d, :seriestype, :none)
|
||||
# if size(x) == size(y) == size(z)
|
||||
# if !is3d(st)
|
||||
# seriestype := :path3d
|
||||
# end
|
||||
# end
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... function
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, zf::Function)
|
||||
# x = X <: Number ? sort(x) : x
|
||||
# y = Y <: Number ? sort(y) : y
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... matrix grid
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, z::AMat)
|
||||
if !like_surface(get(plotattributes, :seriestype, :none))
|
||||
plotattributes[:seriestype] = :contour
|
||||
end
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
|
||||
# # images - grays
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
|
||||
end
|
||||
end
|
||||
|
||||
# # images - colors
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Parametric functions
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# # special handling... xmin/xmax with parametric function(s)
|
||||
@recipe function f(f::Function, xmin::Number, xmax::Number)
|
||||
xs = adapted_grid(f, (xmin, xmax))
|
||||
xs, f
|
||||
end
|
||||
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
|
||||
xs = Any[adapted_grid(f, (xmin, xmax)) for f in fs]
|
||||
xs, fs
|
||||
end
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n)
|
||||
|
||||
#
|
||||
# # special handling... 3D parametric function(s)
|
||||
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function}
|
||||
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
|
||||
end
|
||||
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, umin::Number, umax::Number, numPoints = 200) where {F<:Function,G<:Function,H<:Function}
|
||||
fx, fy, fz, range(umin, stop = umax, length = numPoints)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Lists of tuples and FixedSizeArrays
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # if we get an unhandled tuple, just splat it in
|
||||
@recipe f(tup::Tuple) = tup
|
||||
|
||||
#
|
||||
# # (x,y) tuples
|
||||
@recipe f(xy::AVec{Tuple{R1,R2}}) where {R1<:Number,R2<:Number} = unzip(xy)
|
||||
@recipe f(xy::Tuple{R1,R2}) where {R1<:Number,R2<:Number} = [xy[1]], [xy[2]]
|
||||
|
||||
#
|
||||
# # (x,y,z) tuples
|
||||
@recipe f(xyz::AVec{Tuple{R1,R2,R3}}) where {R1<:Number,R2<:Number,R3<:Number} = unzip(xyz)
|
||||
@recipe f(xyz::Tuple{R1,R2,R3}) where {R1<:Number,R2<:Number,R3<:Number} = [xyz[1]], [xyz[2]], [xyz[3]]
|
||||
|
||||
# these might be points+velocity, or OHLC or something else
|
||||
@recipe f(xyuv::AVec{Tuple{R1,R2,R3,R4}}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = get(plotattributes,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
|
||||
@recipe f(xyuv::Tuple{R1,R2,R3,R4}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = [xyuv[1]], [xyuv[2]], [xyuv[3]], [xyuv[4]]
|
||||
|
||||
|
||||
#
|
||||
# # 2D FixedSizeArrays
|
||||
@recipe f(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T<:Number} = unzip(xy)
|
||||
@recipe f(xy::FixedSizeArrays.Vec{2,T}) where {T<:Number} = [xy[1]], [xy[2]]
|
||||
|
||||
#
|
||||
# # 3D FixedSizeArrays
|
||||
@recipe f(xyz::AVec{FixedSizeArrays.Vec{3,T}}) where {T<:Number} = unzip(xyz)
|
||||
@recipe f(xyz::FixedSizeArrays.Vec{3,T}) where {T<:Number} = [xyz[1]], [xyz[2]], [xyz[3]]
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # handle grouping
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# @recipe function f(groupby::GroupBy, args...)
|
||||
# for (i,glab) in enumerate(groupby.groupLabels)
|
||||
# # create a new series, with the label of the group, and an idxfilter (to be applied in slice_and_dice)
|
||||
# # TODO: use @series instead
|
||||
# @show i, glab, groupby.groupIds[i]
|
||||
# di = copy(d)
|
||||
# get!(di, :label, string(glab))
|
||||
# get!(di, :idxfilter, groupby.groupIds[i])
|
||||
# push!(series_list, RecipeData(di, args))
|
||||
# end
|
||||
# nothing
|
||||
# end
|
||||
|
||||
splittable_kw(key, val, lengthGroup) = false
|
||||
splittable_kw(key, val::AbstractArray, lengthGroup) = (key != :group) && size(val,1) == lengthGroup
|
||||
splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, lengthGroup))
|
||||
splittable_kw(key, val::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)
|
||||
|
||||
split_kw(key, val::AbstractArray, indices) = val[indices, fill(Colon(), ndims(val)-1)...]
|
||||
split_kw(key, val::Tuple, indices) = Tuple(split_kw(key, v, indices) for v in val)
|
||||
function split_kw(key, val::SeriesAnnotations, indices)
|
||||
split_strs = split_kw(key, val.strs, indices)
|
||||
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
|
||||
end
|
||||
|
||||
function groupedvec2mat(x_ind, x, y::AbstractArray, groupby, def_val = y[1])
|
||||
y_mat = Array{promote_type(eltype(y), typeof(def_val))}(length(keys(x_ind)), length(groupby.groupLabels))
|
||||
fill!(y_mat, def_val)
|
||||
for i in 1:length(groupby.groupLabels)
|
||||
xi = x[groupby.groupIds[i]]
|
||||
yi = y[groupby.groupIds[i]]
|
||||
y_mat[getindex.(x_ind, xi), i] = yi
|
||||
end
|
||||
return y_mat
|
||||
end
|
||||
|
||||
groupedvec2mat(x_ind, x, y::Tuple, groupby) = Tuple(groupedvec2mat(x_ind, x, v, groupby) for v in y)
|
||||
|
||||
group_as_matrix(t) = false
|
||||
|
||||
# split the group into 1 series per group, and set the label and idxfilter for each
|
||||
@recipe function f(groupby::GroupBy, args...)
|
||||
lengthGroup = maximum(union(groupby.groupIds...))
|
||||
if !(group_as_matrix(args[1]))
|
||||
for (i,glab) in enumerate(groupby.groupLabels)
|
||||
@series begin
|
||||
label --> string(glab)
|
||||
idxfilter --> groupby.groupIds[i]
|
||||
for (key,val) in plotattributes
|
||||
if splittable_kw(key, val, lengthGroup)
|
||||
:($key) := split_kw(key, val, groupby.groupIds[i])
|
||||
end
|
||||
end
|
||||
args
|
||||
end
|
||||
end
|
||||
else
|
||||
g = args[1]
|
||||
if length(g.args) == 1
|
||||
x = zeros(Int, lengthGroup)
|
||||
for indexes in groupby.groupIds
|
||||
x[indexes] = 1:length(indexes)
|
||||
end
|
||||
last_args = g.args
|
||||
else
|
||||
x = g.args[1]
|
||||
last_args = g.args[2:end]
|
||||
end
|
||||
x_u = unique(x)
|
||||
x_ind = Dict(zip(x_u, 1:length(x_u)))
|
||||
for (key,val) in plotattributes
|
||||
if splittable_kw(key, val, lengthGroup)
|
||||
:($key) := groupedvec2mat(x_ind, x, val, groupby)
|
||||
end
|
||||
end
|
||||
label --> reshape(groupby.groupLabels, 1, :)
|
||||
typeof(g)((x_u, (groupedvec2mat(x_ind, x, arg, groupby, NaN) for arg in last_args)...))
|
||||
end
|
||||
end
|
||||
@@ -1,550 +0,0 @@
|
||||
|
||||
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
|
||||
# Special handling for: no args, xmin/xmax, parametric, dataframes
|
||||
# Then once inputs have been converted, build the series args, map functions, etc.
|
||||
# This should cut down on boilerplate code and allow more focused dispatch on type
|
||||
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
|
||||
|
||||
typealias FuncOrFuncs @compat(Union{Function, AVec{Function}})
|
||||
|
||||
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :heatmap, :surface, :wireframe, :contour3d), get(d, :seriestype, :none))
|
||||
|
||||
# missing
|
||||
convertToAnyVector(v::@compat(Void), d::KW) = Any[nothing], nothing
|
||||
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
|
||||
|
||||
# numeric vector
|
||||
convertToAnyVector{T<:Number}(v::AVec{T}, d::KW) = Any[v], nothing
|
||||
|
||||
# string vector
|
||||
convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}, d::KW) = Any[v], nothing
|
||||
|
||||
# numeric matrix
|
||||
function convertToAnyVector{T<:Number}(v::AMat{T}, d::KW)
|
||||
if all3D(d)
|
||||
Any[Surface(v)]
|
||||
else
|
||||
Any[v[:,i] for i in 1:size(v,2)]
|
||||
end, nothing
|
||||
end
|
||||
|
||||
# other matrix... vector of columns
|
||||
function convertToAnyVector(m::AMat, d::KW)
|
||||
Any[begin
|
||||
v = vec(m[:,i])
|
||||
length(v) == 1 ? v[1] : v
|
||||
end for i=1:size(m,2)], nothing
|
||||
end
|
||||
|
||||
# function
|
||||
convertToAnyVector(f::Function, d::KW) = Any[f], nothing
|
||||
|
||||
# surface
|
||||
convertToAnyVector(s::Surface, d::KW) = Any[s], nothing
|
||||
|
||||
# # vector of OHLC
|
||||
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
|
||||
|
||||
# dates
|
||||
convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function convertToAnyVector(v::AVec, d::KW)
|
||||
if all(x -> typeof(x) <: Number, v)
|
||||
# all real numbers wrap the whole vector as one item
|
||||
Any[convert(Vector{Float64}, v)], nothing
|
||||
else
|
||||
# something else... treat each element as an item
|
||||
vcat(Any[convertToAnyVector(vi, d)[1] for vi in v]...), nothing
|
||||
# Any[vi for vi in v], nothing
|
||||
end
|
||||
end
|
||||
|
||||
convertToAnyVector(t::Tuple, d::KW) = Any[t], nothing
|
||||
|
||||
|
||||
function convertToAnyVector(args...)
|
||||
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
|
||||
# result in that array being shared. push!, etc will add too many items to that array
|
||||
|
||||
compute_x(x::Void, y::Void, z) = 1:size(z,1)
|
||||
compute_x(x::Void, y, z) = 1:size(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = copy(x)
|
||||
|
||||
# compute_y(x::Void, y::Function, z) = error()
|
||||
compute_y(x::Void, y::Void, z) = 1:size(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = copy(y)
|
||||
|
||||
compute_z(x, y, z::Function) = map(z, x, y)
|
||||
compute_z(x, y, z::AbstractMatrix) = Surface(z)
|
||||
compute_z(x, y, z::Void) = nothing
|
||||
compute_z(x, y, z) = copy(z)
|
||||
|
||||
nobigs(v::AVec{BigFloat}) = map(Float64, v)
|
||||
nobigs(v::AVec{BigInt}) = map(Int64, v)
|
||||
nobigs(v) = v
|
||||
|
||||
@noinline function compute_xyz(x, y, z)
|
||||
x = compute_x(x,y,z)
|
||||
y = compute_y(x,y,z)
|
||||
z = compute_z(x,y,z)
|
||||
nobigs(x), nobigs(y), nobigs(z)
|
||||
end
|
||||
|
||||
# not allowed
|
||||
compute_xyz(x::Void, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!")
|
||||
compute_xyz(x::Void, y::Void, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!")
|
||||
compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# # create n=max(mx,my) series arguments. the shorter list is cycled through
|
||||
# # note: everything should flow through this
|
||||
# function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
# x, y, z = map(sym -> pop!(kw, sym, nothing), (:x, :y, :z))
|
||||
# if nothing == x == y == z
|
||||
# return [], nothing, nothing
|
||||
# end
|
||||
#
|
||||
# xs, xmeta = convertToAnyVector(x, kw)
|
||||
# ys, ymeta = convertToAnyVector(y, kw)
|
||||
# zs, zmeta = convertToAnyVector(z, kw)
|
||||
#
|
||||
# fr = pop!(kw, :fillrange, nothing)
|
||||
# fillranges, _ = if typeof(fr) <: Number
|
||||
# ([fr],nothing)
|
||||
# else
|
||||
# convertToAnyVector(fr, kw)
|
||||
# end
|
||||
#
|
||||
# mx = length(xs)
|
||||
# my = length(ys)
|
||||
# mz = length(zs)
|
||||
# ret = Any[]
|
||||
# for i in 1:max(mx, my, mz)
|
||||
#
|
||||
# # try to set labels using ymeta
|
||||
# d = copy(kw)
|
||||
# if !haskey(d, :label) && ymeta != nothing
|
||||
# if isa(ymeta, Symbol)
|
||||
# d[:label] = string(ymeta)
|
||||
# elseif isa(ymeta, AVec{Symbol})
|
||||
# d[:label] = string(ymeta[mod1(i,length(ymeta))])
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # build the series arg dict
|
||||
# numUncounted = pop!(d, :numUncounted, 0)
|
||||
# commandIndex = i + numUncounted
|
||||
# n = plt.n + i
|
||||
#
|
||||
# dumpdict(d, "before getSeriesArgs")
|
||||
# d = getSeriesArgs(plt.backend, getattr(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
|
||||
# dumpdict(d, "after getSeriesArgs")
|
||||
#
|
||||
# d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
|
||||
# st = d[:seriestype]
|
||||
#
|
||||
# # for seriestype `line`, need to sort by x values
|
||||
# if st == :line
|
||||
# # order by x
|
||||
# indices = sortperm(d[:x])
|
||||
# d[:x] = d[:x][indices]
|
||||
# d[:y] = d[:y][indices]
|
||||
# d[:seriestype] = :path
|
||||
# end
|
||||
#
|
||||
# # special handling for missing x in box plot... all the same category
|
||||
# if st == :box && xs[mod1(i,mx)] == nothing
|
||||
# d[:x] = ones(Int, length(d[:y]))
|
||||
# end
|
||||
#
|
||||
# # map functions to vectors
|
||||
# if isa(d[:marker_z], Function)
|
||||
# d[:marker_z] = map(d[:marker_z], d[:x])
|
||||
# end
|
||||
#
|
||||
# # @show fillranges
|
||||
# d[:fillrange] = fillranges[mod1(i,length(fillranges))]
|
||||
# if isa(d[:fillrange], Function)
|
||||
# d[:fillrange] = map(d[:fillrange], d[:x])
|
||||
# end
|
||||
#
|
||||
# # handle error bars
|
||||
# for esym in (:xerror, :yerror)
|
||||
# if get(d, esym, nothing) != nothing
|
||||
# # we make a copy of the KW and apply an errorbar recipe
|
||||
# append!(ret, apply_series_recipe(copy(d), Val{esym}))
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # handle ribbons
|
||||
# if get(d, :ribbon, nothing) != nothing
|
||||
# rib = d[:ribbon]
|
||||
# d[:fillrange] = (d[:y] - rib, d[:y] + rib)
|
||||
# end
|
||||
#
|
||||
# # handle quiver plots
|
||||
# # either a series of velocity vectors are passed in (`:quiver` keyword),
|
||||
# # or we just add arrows to the path
|
||||
#
|
||||
# # if st == :quiver
|
||||
# # d[:seriestype] = st = :path
|
||||
# # d[:linewidth] = 0
|
||||
# # end
|
||||
# if get(d, :quiver, nothing) != nothing
|
||||
# append!(ret, apply_series_recipe(copy(d), Val{:quiver}))
|
||||
# elseif st == :quiver
|
||||
# d[:seriestype] = st = :path
|
||||
# d[:arrow] = arrow()
|
||||
# end
|
||||
#
|
||||
# # now that we've processed a given series... optionally split into
|
||||
# # multiple dicts through a recipe (for example, a box plot is split into component
|
||||
# # parts... polygons, lines, and scatters)
|
||||
# # note: we pass in a Val type (i.e. Val{:box}) so that we can dispatch on the seriestype
|
||||
# kwlist = apply_series_recipe(d, Val{st})
|
||||
# append!(ret, kwlist)
|
||||
#
|
||||
# # # add it to our series list
|
||||
# # push!(ret, d)
|
||||
# end
|
||||
#
|
||||
# ret, xmeta, ymeta
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # process_inputs
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # These methods take a plot and the keyword arguments, and processes the input
|
||||
# # arguments (x/y/z, group, etc), populating the KW dict with appropriate values.
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 0 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # don't do anything
|
||||
# function process_inputs(plt::AbstractPlot, d::KW)
|
||||
# end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 1 argument
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, n::Integer)
|
||||
# # d[:x], d[:y], d[:z] = zeros(0), zeros(0), zeros(0)
|
||||
# d[:x] = d[:y] = d[:z] = n
|
||||
# end
|
||||
#
|
||||
# # no special handling... assume x and z are nothing
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, y)
|
||||
# d[:y] = y
|
||||
# end
|
||||
#
|
||||
# # matrix... is it z or y?
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, mat::AMat{T})
|
||||
# if all3D(d)
|
||||
# n,m = size(mat)
|
||||
# d[:x], d[:y], d[:z] = 1:n, 1:m, mat
|
||||
# else
|
||||
# d[:y] = mat
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # images - grays
|
||||
# function process_inputs{T<:Gray}(plt::AbstractPlot, d::KW, mat::AMat{T})
|
||||
# d[:seriestype] = :image
|
||||
# n,m = size(mat)
|
||||
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
|
||||
# # handle images... when not supported natively, do a hack to use heatmap machinery
|
||||
# if !nativeImagesSupported()
|
||||
# d[:seriestype] = :heatmap
|
||||
# d[:yflip] = true
|
||||
# d[:z] = Surface(convert(Matrix{Float64}, mat.surf))
|
||||
# d[:fillcolor] = ColorGradient([:black, :white])
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # images - colors
|
||||
# function process_inputs{T<:Colorant}(plt::AbstractPlot, d::KW, mat::AMat{T})
|
||||
# d[:seriestype] = :image
|
||||
# n,m = size(mat)
|
||||
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
|
||||
# # handle images... when not supported natively, do a hack to use heatmap machinery
|
||||
# if !nativeImagesSupported()
|
||||
# d[:yflip] = true
|
||||
# imageHack(d)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # plotting arbitrary shapes/polygons
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, shape::Shape)
|
||||
# d[:x], d[:y] = shape_coords(shape)
|
||||
# d[:seriestype] = :shape
|
||||
# end
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AVec{Shape})
|
||||
# d[:x], d[:y] = shape_coords(shapes)
|
||||
# d[:seriestype] = :shape
|
||||
# end
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AMat{Shape})
|
||||
# x, y = [], []
|
||||
# for j in 1:size(shapes, 2)
|
||||
# tmpx, tmpy = shape_coords(vec(shapes[:,j]))
|
||||
# push!(x, tmpx)
|
||||
# push!(y, tmpy)
|
||||
# end
|
||||
# d[:x], d[:y] = x, y
|
||||
# d[:seriestype] = :shape
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # function without range... use the current range of the x-axis
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs)
|
||||
# process_inputs(plt, d, f, xmin(plt), xmax(plt))
|
||||
# end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 2 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, x, y)
|
||||
# d[:x], d[:y] = x, y
|
||||
# end
|
||||
#
|
||||
# # if functions come first, just swap the order (not to be confused with parametric functions...
|
||||
# # as there would be more than one function passed in)
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, x)
|
||||
# @assert !(typeof(x) <: FuncOrFuncs) # otherwise we'd hit infinite recursion here
|
||||
# process_inputs(plt, d, x, f)
|
||||
# end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 3 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # no special handling... just pass them through
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, x, y, z)
|
||||
# d[:x], d[:y], d[:z] = x, y, z
|
||||
# end
|
||||
#
|
||||
# # 3d line or scatter
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zvec::AVec)
|
||||
# # default to path3d if we haven't set a 3d seriestype
|
||||
# st = get(d, :seriestype, :none)
|
||||
# if st == :scatter
|
||||
# d[:seriestype] = :scatter3d
|
||||
# elseif !(st in _3dTypes)
|
||||
# d[:seriestype] = :path3d
|
||||
# end
|
||||
# d[:x], d[:y], d[:z] = x, y, zvec
|
||||
# end
|
||||
#
|
||||
# # surface-like... function
|
||||
# function process_inputs{TX,TY}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zf::Function)
|
||||
# x = TX <: Number ? sort(x) : x
|
||||
# y = TY <: Number ? sort(y) : y
|
||||
# # x, y = sort(x), sort(y)
|
||||
# d[:z] = Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
# d[:x], d[:y] = x, y
|
||||
# end
|
||||
#
|
||||
# # surface-like... matrix grid
|
||||
# function process_inputs{TX,TY,TZ}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zmat::AMat{TZ})
|
||||
# # @assert size(zmat) == (length(x), length(y))
|
||||
# # if TX <: Number && !issorted(x)
|
||||
# # idx = sortperm(x)
|
||||
# # x, zmat = x[idx], zmat[idx, :]
|
||||
# # end
|
||||
# # if TY <: Number && !issorted(y)
|
||||
# # idx = sortperm(y)
|
||||
# # y, zmat = y[idx], zmat[:, idx]
|
||||
# # end
|
||||
# d[:x], d[:y], d[:z] = x, y, Surface{Matrix{TZ}}(zmat)
|
||||
# if !like_surface(get(d, :seriestype, :none))
|
||||
# d[:seriestype] = :contour
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # surfaces-like... general x, y grid
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, x::AMat{T}, y::AMat{T}, zmat::AMat{T})
|
||||
# @assert size(zmat) == size(x) == size(y)
|
||||
# # d[:x], d[:y], d[:z] = Any[x], Any[y], Surface{Matrix{Float64}}(zmat)
|
||||
# d[:x], d[:y], d[:z] = map(Surface{Matrix{Float64}}, (x, y, zmat))
|
||||
# if !like_surface(get(d, :seriestype, :none))
|
||||
# d[:seriestype] = :contour
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Parametric functions
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # special handling... xmin/xmax with function(s)
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, xmin::Number, xmax::Number)
|
||||
# width = get(plt.attr, :size, (100,))[1]
|
||||
# x = linspace(xmin, xmax, width)
|
||||
# process_inputs(plt, d, x, f)
|
||||
# end
|
||||
#
|
||||
# # special handling... xmin/xmax with parametric function(s)
|
||||
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
|
||||
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
|
||||
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, linspace(umin, umax, numPoints))
|
||||
#
|
||||
# # special handling... 3D parametric function(s)
|
||||
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
|
||||
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
|
||||
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, fz, linspace(umin, umax, numPoints))
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Lists of tuples and FixedSizeArrays
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # if we get an unhandled tuple, just splat it in
|
||||
# function process_inputs(plt::AbstractPlot, d::KW, tup::Tuple)
|
||||
# process_inputs(plt, d, tup...)
|
||||
# end
|
||||
#
|
||||
# # (x,y) tuples
|
||||
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::AVec{Tuple{R1,R2}})
|
||||
# process_inputs(plt, d, unzip(xy)...)
|
||||
# end
|
||||
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::Tuple{R1,R2})
|
||||
# process_inputs(plt, d, [xy[1]], [xy[2]])
|
||||
# end
|
||||
#
|
||||
# # (x,y,z) tuples
|
||||
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{Tuple{R1,R2,R3}})
|
||||
# process_inputs(plt, d, unzip(xyz)...)
|
||||
# end
|
||||
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::Tuple{R1,R2,R3})
|
||||
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
|
||||
# end
|
||||
#
|
||||
# # 2D FixedSizeArrays
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::AVec{FixedSizeArrays.Vec{2,T}})
|
||||
# process_inputs(plt, d, unzip(xy)...)
|
||||
# end
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::FixedSizeArrays.Vec{2,T})
|
||||
# process_inputs(plt, d, [xy[1]], [xy[2]])
|
||||
# end
|
||||
#
|
||||
# # 3D FixedSizeArrays
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{FixedSizeArrays.Vec{3,T}})
|
||||
# process_inputs(plt, d, unzip(xyz)...)
|
||||
# end
|
||||
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::FixedSizeArrays.Vec{3,T})
|
||||
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
|
||||
# end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # handle grouping
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # function process_inputs(plt::AbstractPlot, d::KW, groupby::GroupBy, args...)
|
||||
# # ret = Any[]
|
||||
# # error("unfinished after series reorg")
|
||||
# # for (i,glab) in enumerate(groupby.groupLabels)
|
||||
# # # TODO: don't automatically overwrite labels
|
||||
# # kwlist, xmeta, ymeta = process_inputs(plt, d, args...,
|
||||
# # idxfilter = groupby.groupIds[i],
|
||||
# # label = string(glab),
|
||||
# # numUncounted = length(ret)) # we count the idx from plt.n + numUncounted + i
|
||||
# # append!(ret, kwlist)
|
||||
# # end
|
||||
# # ret, nothing, nothing # TODO: handle passing meta through
|
||||
# # end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# For DataFrame support. Imports DataFrames and defines the necessary methods which support them.
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# function setup_dataframes()
|
||||
# @require DataFrames begin
|
||||
# # @eval begin
|
||||
# # import DataFrames
|
||||
#
|
||||
# DFS = Union{Symbol, AbstractArray{Symbol}}
|
||||
#
|
||||
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, dfs::DFS)
|
||||
# if isa(dfs, Symbol)
|
||||
# get!(d, Symbol(letter * "label"), string(dfs))
|
||||
# collect(df[dfs])
|
||||
# else
|
||||
# get!(d, :label, reshape(dfs, 1, length(dfs)))
|
||||
# Any[collect(df[s]) for s in dfs]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# function handle_group(df::DataFrames.AbstractDataFrame, d::KW)
|
||||
# if haskey(d, :group)
|
||||
# g = d[:group]
|
||||
# if isa(g, Symbol)
|
||||
# d[:group] = collect(df[g])
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @recipe function plot(df::DataFrames.AbstractDataFrame, sy::DFS)
|
||||
# handle_group(df, d)
|
||||
# handle_dfs(df, d, "y", sy)
|
||||
# end
|
||||
#
|
||||
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS)
|
||||
# handle_group(df, d)
|
||||
# x = handle_dfs(df, d, "x", sx)
|
||||
# y = handle_dfs(df, d, "y", sy)
|
||||
# x, y
|
||||
# end
|
||||
#
|
||||
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS, sz::DFS)
|
||||
# handle_group(df, d)
|
||||
# x = handle_dfs(df, d, "x", sx)
|
||||
# y = handle_dfs(df, d, "y", sy)
|
||||
# z = handle_dfs(df, d, "z", sz)
|
||||
# x, y, z
|
||||
# end
|
||||
#
|
||||
# # get_data(df::DataFrames.AbstractDataFrame, arg::Symbol) = df[arg]
|
||||
# # get_data(df::DataFrames.AbstractDataFrame, arg) = arg
|
||||
# #
|
||||
# # function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...)
|
||||
# # # d[:dataframe] = df
|
||||
# # process_inputs(plt, d, map(arg -> get_data(df, arg), args)...)
|
||||
# # end
|
||||
# #
|
||||
# # # expecting the column name of a dataframe that was passed in... anything else should error
|
||||
# # function extractGroupArgs(s::Symbol, df::DataFrames.AbstractDataFrame, args...)
|
||||
# # if haskey(df, s)
|
||||
# # return extractGroupArgs(df[s])
|
||||
# # else
|
||||
# # error("Got a symbol, and expected that to be a key in d[:dataframe]. s=$s d=$d")
|
||||
# # end
|
||||
# # end
|
||||
#
|
||||
# # function getDataFrameFromKW(d::KW)
|
||||
# # get(d, :dataframe) do
|
||||
# # error("Missing dataframe argument!")
|
||||
# # end
|
||||
# # end
|
||||
#
|
||||
# # # the conversion functions for when we pass symbols or vectors of symbols to reference dataframes
|
||||
# # convertToAnyVector(s::Symbol, d::KW) = Any[getDataFrameFromKW(d)[s]], s
|
||||
# # convertToAnyVector(v::AVec{Symbol}, d::KW) = (df = getDataFrameFromKW(d); Any[df[s] for s in v]), v
|
||||
#
|
||||
# end
|
||||
# end
|
||||
@@ -1,416 +0,0 @@
|
||||
|
||||
# we are going to build recipes to do the processing and splitting of the args
|
||||
|
||||
|
||||
function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
|
||||
pkg = plt.backend
|
||||
globalIndex = d[:series_plotindex]
|
||||
|
||||
# add default values to our dictionary, being careful not to delete what we just added!
|
||||
for (k,v) in _series_defaults
|
||||
slice_arg!(d, d, k, v, commandIndex, remove_pair = false)
|
||||
end
|
||||
|
||||
# this is how many series belong to this subplot
|
||||
plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list)
|
||||
if get(d, :primary, true)
|
||||
plotIndex += 1
|
||||
end
|
||||
|
||||
aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex)
|
||||
aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex)
|
||||
|
||||
# update color
|
||||
d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], sp, plotIndex)
|
||||
|
||||
# update colors
|
||||
for csym in (:linecolor, :markercolor, :fillcolor)
|
||||
d[csym] = if d[csym] == :match
|
||||
if has_black_border_for_default(d[:seriestype]) && csym == :linecolor
|
||||
:black
|
||||
else
|
||||
d[:seriescolor]
|
||||
end
|
||||
else
|
||||
getSeriesRGBColor(d[csym], sp, plotIndex)
|
||||
end
|
||||
end
|
||||
|
||||
# update markerstrokecolor
|
||||
c = d[:markerstrokecolor]
|
||||
c = if c == :match
|
||||
sp[:foreground_color_subplot]
|
||||
else
|
||||
getSeriesRGBColor(c, sp, plotIndex)
|
||||
end
|
||||
d[:markerstrokecolor] = c
|
||||
|
||||
# update alphas
|
||||
for asym in (:linealpha, :markeralpha, :fillalpha)
|
||||
if d[asym] == nothing
|
||||
d[asym] = d[:seriesalpha]
|
||||
end
|
||||
end
|
||||
if d[:markerstrokealpha] == nothing
|
||||
d[:markerstrokealpha] = d[:markeralpha]
|
||||
end
|
||||
|
||||
# scatter plots don't have a line, but must have a shape
|
||||
if d[:seriestype] in (:scatter, :scatter3d)
|
||||
d[:linewidth] = 0
|
||||
if d[:markershape] == :none
|
||||
d[:markershape] = :circle
|
||||
end
|
||||
end
|
||||
|
||||
# set label
|
||||
label = d[:label]
|
||||
label = (label == "AUTO" ? "y$globalIndex" : label)
|
||||
d[:label] = label
|
||||
|
||||
_replace_linewidth(d)
|
||||
d
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# instead of process_inputs:
|
||||
|
||||
# ensure we dispatch to the slicer
|
||||
immutable SliceIt end
|
||||
|
||||
# the catch-all recipes
|
||||
@recipe function f(::Type{SliceIt}, x, y, z)
|
||||
# @show "HERE", typeof((x,y,z))
|
||||
xs, _ = convertToAnyVector(x, d)
|
||||
ys, _ = convertToAnyVector(y, d)
|
||||
zs, _ = convertToAnyVector(z, d)
|
||||
|
||||
fr = pop!(d, :fillrange, nothing)
|
||||
fillranges, _ = if typeof(fr) <: Number
|
||||
([fr],nothing)
|
||||
else
|
||||
convertToAnyVector(fr, d)
|
||||
end
|
||||
mf = length(fillranges)
|
||||
|
||||
# @show zs
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
# ret = Any[]
|
||||
for i in 1:max(mx, my, mz)
|
||||
# add a new series
|
||||
di = copy(d)
|
||||
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
|
||||
# @show i, typeof((xi, yi, zi))
|
||||
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
|
||||
# @show i, typeof((di[:x], di[:y], di[:z]))
|
||||
|
||||
# handle fillrange
|
||||
fr = fillranges[mod1(i,mf)]
|
||||
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
|
||||
|
||||
# @show i, di[:x], di[:y], di[:z]
|
||||
push!(series_list, RecipeData(di, ()))
|
||||
end
|
||||
nothing # don't add a series for the main block
|
||||
end
|
||||
|
||||
# this is the default "type recipe"... just pass the object through
|
||||
@recipe f{T<:Any}(::Type{T}, v::T) = v
|
||||
|
||||
# this should catch unhandled "series recipes" and error with a nice message
|
||||
@recipe f{V<:Val}(::Type{V}, x, y, z) = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
|
||||
|
||||
_apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1]
|
||||
|
||||
# handle "type recipes" by converting inputs, and then either re-calling or slicing
|
||||
@recipe function f(x, y, z)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(d, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(d, y)
|
||||
y === newy || (did_replace = true)
|
||||
newz = _apply_type_recipe(d, z)
|
||||
z === newz || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy, newz
|
||||
else
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
end
|
||||
@recipe function f(x, y)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(d, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(d, y)
|
||||
y === newy || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy
|
||||
else
|
||||
SliceIt, x, y, nothing
|
||||
end
|
||||
end
|
||||
@recipe function f(y)
|
||||
newy = _apply_type_recipe(d, y)
|
||||
if y !== newy
|
||||
newy
|
||||
else
|
||||
SliceIt, nothing, y, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if there's more than 3 inputs, it can't be passed directly to SliceIt
|
||||
# so we'll apply_type_recipe to all of them
|
||||
@recipe function f(v1, v2, v3, v4, vrest...)
|
||||
did_replace = false
|
||||
newargs = map(v -> begin
|
||||
newv = _apply_type_recipe(d, v)
|
||||
if newv !== v
|
||||
did_replace = true
|
||||
end
|
||||
newv
|
||||
end, (v1, v2, v3, v4, vrest...))
|
||||
if !did_replace
|
||||
error("Couldn't process recipe args: $(map(typeof, (v1, v2, v3, v4, vrest...)))")
|
||||
end
|
||||
newargs
|
||||
end
|
||||
|
||||
# @recipe f(x, y, z) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), apply_recipe(typeof(z), z)
|
||||
# @recipe f(x, y) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), nothing
|
||||
# @recipe f(y) = SliceIt, nothing, apply_recipe(typeof(y), y), nothing
|
||||
|
||||
# # pass these through to the slicer
|
||||
# @recipe f(x, y, z) = SliceIt, x, y, z
|
||||
# @recipe f(x, y) = SliceIt, x, y, nothing
|
||||
# @recipe f(y) = SliceIt, nothing, y, nothing
|
||||
|
||||
|
||||
# # --------------------------------------------------------------------
|
||||
# # 1 argument
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
@recipe f(n::Integer) = n, n, n
|
||||
|
||||
# return a surface if this is a 3d plot, otherwise let it be sliced up
|
||||
@recipe function f{T<:Number}(mat::AMat{T})
|
||||
if all3D(d)
|
||||
n,m = size(mat)
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
SliceIt, nothing, mat, nothing
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# # images - grays
|
||||
|
||||
@recipe function f{T<:Gray}(mat::AMat{T})
|
||||
if nativeImagesSupported()
|
||||
seriestype := :image
|
||||
n, m = size(mat)
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, 1:m, 1:n, Surface(convert(Matrix{Float64}, mat))
|
||||
end
|
||||
end
|
||||
|
||||
# # images - colors
|
||||
|
||||
@recipe function f{T<:Colorant}(mat::AMat{T})
|
||||
if nativeImagesSupported()
|
||||
seriestype := :image
|
||||
n, m = size(mat)
|
||||
SliceIt, 1:m, 1:n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
z, d[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, 1:m, 1:n, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# # plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype := :shape
|
||||
shape_coords(shape)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AVec{Shape})
|
||||
seriestype := :shape
|
||||
shape_coords(shapes)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AMat{Shape})
|
||||
for j in 1:size(shapes,2)
|
||||
# create one series for each column
|
||||
# @series shape_coords(vec(shapes[:,j]))
|
||||
di = copy(d)
|
||||
push!(series_list, RecipeData(di, shape_coords(vec(shapes[:,j]))))
|
||||
end
|
||||
nothing # don't create a series for the main block
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # function without range... use the current range of the x-axis
|
||||
|
||||
@recipe function f(f::FuncOrFuncs)
|
||||
plt = d[:plot_object]
|
||||
f, xmin(plt), xmax(plt)
|
||||
end
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 2 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # if functions come first, just swap the order (not to be confused with parametric functions...
|
||||
# # as there would be more than one function passed in)
|
||||
|
||||
@recipe function f(f::FuncOrFuncs, x)
|
||||
@assert !(typeof(x) <: FuncOrFuncs) # otherwise we'd hit infinite recursion here
|
||||
x, f
|
||||
end
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 3 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # 3d line or scatter
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, z::AVec)
|
||||
# st = get(d, :seriestype, :none)
|
||||
# if st == :scatter
|
||||
# d[:seriestype] = :scatter3d
|
||||
# elseif !is3d(st)
|
||||
# d[:seriestype] = :path3d
|
||||
# end
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
@recipe function f(x::AMat, y::AMat, z::AMat)
|
||||
# st = get(d, :seriestype, :none)
|
||||
# if size(x) == size(y) == size(z)
|
||||
# if !is3d(st)
|
||||
# seriestype := :path3d
|
||||
# end
|
||||
# end
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... function
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, zf::Function)
|
||||
# x = X <: Number ? sort(x) : x
|
||||
# y = Y <: Number ? sort(y) : y
|
||||
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... matrix grid
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, z::AMat)
|
||||
if !like_surface(get(d, :seriestype, :none))
|
||||
d[:seriestype] = :contour
|
||||
end
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Parametric functions
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# # special handling... xmin/xmax with parametric function(s)
|
||||
@recipe f(f::FuncOrFuncs, xmin::Number, xmax::Number) = linspace(xmin, xmax, 100), f
|
||||
@recipe f(fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec) = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
|
||||
@recipe f(fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, n = 200) = fx, fy, linspace(umin, umax, n)
|
||||
|
||||
#
|
||||
# # special handling... 3D parametric function(s)
|
||||
@recipe function f(fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec)
|
||||
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
|
||||
end
|
||||
@recipe function f(fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints = 200)
|
||||
fx, fy, fz, linspace(umin, umax, numPoints)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Lists of tuples and FixedSizeArrays
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
# # if we get an unhandled tuple, just splat it in
|
||||
@recipe f(tup::Tuple) = tup
|
||||
|
||||
#
|
||||
# # (x,y) tuples
|
||||
@recipe f{R1<:Number,R2<:Number}(xy::AVec{Tuple{R1,R2}}) = unzip(xy)
|
||||
@recipe f{R1<:Number,R2<:Number}(xy::Tuple{R1,R2}) = [xy[1]], [xy[2]]
|
||||
|
||||
#
|
||||
# # (x,y,z) tuples
|
||||
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::AVec{Tuple{R1,R2,R3}}) = unzip(xyz)
|
||||
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::Tuple{R1,R2,R3}) = [xyz[1]], [xyz[2]], [xyz[3]]
|
||||
|
||||
# these might be points+velocity, or OHLC or something else
|
||||
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::AVec{Tuple{R1,R2,R3,R4}}) = get(d,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
|
||||
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::Tuple{R1,R2,R3,R4}) = [xyuv[1]], [xyuv[2]], [xyuv[3]], [xyuv[4]]
|
||||
|
||||
|
||||
#
|
||||
# # 2D FixedSizeArrays
|
||||
@recipe f{T<:Number}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = unzip(xy)
|
||||
@recipe f{T<:Number}(xy::FixedSizeArrays.Vec{2,T}) = [xy[1]], [xy[2]]
|
||||
|
||||
#
|
||||
# # 3D FixedSizeArrays
|
||||
@recipe f{T<:Number}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = unzip(xyz)
|
||||
@recipe f{T<:Number}(xyz::FixedSizeArrays.Vec{3,T}) = [xyz[1]], [xyz[2]], [xyz[3]]
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # handle grouping
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# @recipe function f(groupby::GroupBy, args...)
|
||||
# for (i,glab) in enumerate(groupby.groupLabels)
|
||||
# # create a new series, with the label of the group, and an idxfilter (to be applied in slice_and_dice)
|
||||
# # TODO: use @series instead
|
||||
# @show i, glab, groupby.groupIds[i]
|
||||
# di = copy(d)
|
||||
# get!(di, :label, string(glab))
|
||||
# get!(di, :idxfilter, groupby.groupIds[i])
|
||||
# push!(series_list, RecipeData(di, args))
|
||||
# end
|
||||
# nothing
|
||||
# end
|
||||
|
||||
# split the group into 1 series per group, and set the label and idxfilter for each
|
||||
@recipe function f(groupby::GroupBy, args...)
|
||||
for (i,glab) in enumerate(groupby.groupLabels)
|
||||
@series begin
|
||||
label --> string(glab)
|
||||
idxfilter --> groupby.groupIds[i]
|
||||
args
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+12
-6
@@ -1,8 +1,9 @@
|
||||
|
||||
|
||||
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
|
||||
function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
|
||||
Subplot{T}(
|
||||
parent,
|
||||
Series[],
|
||||
(20mm, 5mm, 2mm, 10mm),
|
||||
defaultbox,
|
||||
defaultbox,
|
||||
@@ -12,6 +13,11 @@ function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
|
||||
)
|
||||
end
|
||||
|
||||
"""
|
||||
plotarea(subplot)
|
||||
|
||||
Return the bounding box of a subplot
|
||||
"""
|
||||
plotarea(sp::Subplot) = sp.plotarea
|
||||
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
|
||||
|
||||
@@ -30,16 +36,16 @@ get_subplot(plt::Plot, i::Integer) = plt.subplots[i]
|
||||
get_subplot(plt::Plot, k) = plt.spmap[k]
|
||||
get_subplot(series::Series) = series.d[:subplot]
|
||||
|
||||
get_subplot_index(plt::Plot, idx::Integer) = idx
|
||||
get_subplot_index(plt::Plot, sp::Subplot) = findfirst(_ -> _ === sp, plt.subplots)
|
||||
get_subplot_index(plt::Plot, idx::Integer) = Int(idx)
|
||||
get_subplot_index(plt::Plot, sp::Subplot) = findfirst(x -> x === sp, plt.subplots)
|
||||
|
||||
series_list(sp::Subplot) = filter(series -> series.d[:subplot] === sp, sp.plt.series_list)
|
||||
series_list(sp::Subplot) = sp.series_list # filter(series -> series.d[:subplot] === sp, sp.plt.series_list)
|
||||
|
||||
function should_add_to_legend(series::Series)
|
||||
series.d[:primary] && series.d[:label] != "" &&
|
||||
!(series.d[:seriestype] in (
|
||||
:hexbin,:histogram2d,:hline,:vline,
|
||||
:contour,:contour3d,:surface,:wireframe,
|
||||
:hexbin,:bins2d,:histogram2d,:hline,:vline,
|
||||
:contour,:contourf,:contour3d,:surface,:wireframe,
|
||||
:heatmap, :pie, :image
|
||||
))
|
||||
end
|
||||
|
||||
+163
-60
@@ -1,65 +1,168 @@
|
||||
"""
|
||||
theme(s::Symbol)
|
||||
|
||||
const _invisible = RGBA(0,0,0,0)
|
||||
Specify the colour theme for plots.
|
||||
"""
|
||||
function theme(s::Symbol; kw...)
|
||||
defaults = _get_defaults(s)
|
||||
_theme(s, defaults; kw...)
|
||||
end
|
||||
|
||||
const _themes = KW(
|
||||
:default => KW(
|
||||
:bg => :white,
|
||||
:bglegend => :match,
|
||||
:bginside => :match,
|
||||
:bgoutside => :match,
|
||||
:fg => :auto,
|
||||
:fglegend => :match,
|
||||
:fggrid => :match,
|
||||
:fgaxis => :match,
|
||||
:fgtext => :match,
|
||||
:fgborder => :match,
|
||||
:fgguide => :match,
|
||||
)
|
||||
function _get_defaults(s::Symbol)
|
||||
thm = PlotThemes._themes[s]
|
||||
if :defaults in fieldnames(typeof(thm))
|
||||
return thm.defaults
|
||||
else # old PlotTheme type
|
||||
defaults = KW(
|
||||
:bg => thm.bg_secondary,
|
||||
:bginside => thm.bg_primary,
|
||||
:fg => thm.lines,
|
||||
:fgtext => thm.text,
|
||||
:fgguide => thm.text,
|
||||
:fglegend => thm.text,
|
||||
:palette => thm.palette,
|
||||
)
|
||||
if thm.gradient != nothing
|
||||
push!(defaults, :gradient => thm.gradient)
|
||||
end
|
||||
return defaults
|
||||
end
|
||||
end
|
||||
|
||||
function _theme(s::Symbol, defaults::KW; kw...)
|
||||
# Reset to defaults to overwrite active theme
|
||||
reset_defaults()
|
||||
|
||||
# Set the theme's gradient as default
|
||||
if haskey(defaults, :gradient)
|
||||
PlotUtils.clibrary(:misc)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = PlotThemes.gradient_name(s))
|
||||
else
|
||||
PlotUtils.clibrary(:Plots)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = :inferno)
|
||||
end
|
||||
|
||||
# maybe overwrite the theme's gradient
|
||||
kw = KW(kw)
|
||||
if haskey(kw, :gradient)
|
||||
kwgrad = pop!(kw, :gradient)
|
||||
for clib in clibraries()
|
||||
if kwgrad in cgradients(clib)
|
||||
PlotUtils.clibrary(clib)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = kwgrad)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set the theme's defaults
|
||||
default(; defaults..., kw...)
|
||||
return
|
||||
end
|
||||
|
||||
@deprecate set_theme(s) theme(s)
|
||||
|
||||
@userplot ShowTheme
|
||||
|
||||
_color_functions = KW(
|
||||
:protanopic => protanopic,
|
||||
:deuteranopic => deuteranopic,
|
||||
:tritanopic => tritanopic,
|
||||
)
|
||||
_get_showtheme_args(thm::Symbol) = thm, identity
|
||||
_get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func, identity)
|
||||
|
||||
@recipe function showtheme(st::ShowTheme)
|
||||
thm, cfunc = _get_showtheme_args(st.args...)
|
||||
defaults = _get_defaults(thm)
|
||||
|
||||
# get the gradient
|
||||
gradient_colors = get(defaults, :gradient, cgrad(:inferno).colors)
|
||||
gradient = cgrad(cfunc.(RGB.(gradient_colors)))
|
||||
|
||||
# get the palette
|
||||
palette = get(defaults, :palette, get_color_palette(:auto, plot_color(:white), 17))
|
||||
palette = cfunc.(RGB.(palette))
|
||||
|
||||
# apply the theme
|
||||
for k in keys(defaults)
|
||||
k in (:gradient, :palette) && continue
|
||||
def = defaults[k]
|
||||
arg = get(_keyAliases, k, k)
|
||||
plotattributes[arg] = if typeof(def) <: Colorant
|
||||
cfunc(RGB(def))
|
||||
elseif eltype(def) <: Colorant
|
||||
cfunc.(RGB.(def))
|
||||
elseif occursin("color", string(arg))
|
||||
cfunc.(RGB.(plot_color.(def)))
|
||||
else
|
||||
def
|
||||
end
|
||||
end
|
||||
|
||||
srand(1)
|
||||
|
||||
label := ""
|
||||
colorbar := false
|
||||
layout := (2, 3)
|
||||
|
||||
for j in 1:4
|
||||
@series begin
|
||||
subplot := 1
|
||||
palette := palette
|
||||
seriestype := :path
|
||||
cumsum(randn(50))
|
||||
end
|
||||
|
||||
@series begin
|
||||
subplot := 2
|
||||
seriestype := :scatter
|
||||
palette := palette
|
||||
marker := (:circle, :diamond, :star5, :square)[j]
|
||||
randn(10), randn(10)
|
||||
end
|
||||
end
|
||||
|
||||
@series begin
|
||||
subplot := 3
|
||||
seriestype := :histogram
|
||||
palette := palette
|
||||
randn(1000) .+ (0:2:4)'
|
||||
end
|
||||
|
||||
f(r) = sin(r) / r
|
||||
_norm(x, y) = norm([x, y])
|
||||
x = y = range(-3π, stop = 3π, length = 30)
|
||||
z = f.(_norm.(x, y'))
|
||||
wi = 2:3:30
|
||||
|
||||
@series begin
|
||||
subplot := 4
|
||||
seriestype := :heatmap
|
||||
seriescolor := gradient
|
||||
ticks := -5:5:5
|
||||
x, y, z
|
||||
end
|
||||
|
||||
@series begin
|
||||
subplot := 5
|
||||
seriestype := :surface
|
||||
seriescolor := gradient
|
||||
x, y, z
|
||||
end
|
||||
|
||||
n = 100
|
||||
ts = range(0, stop = 10π, length = n)
|
||||
x = ts .* cos.(ts)
|
||||
y = (0.1ts) .* sin.(ts)
|
||||
z = 1:n
|
||||
|
||||
@series begin
|
||||
subplot := 6
|
||||
seriescolor := gradient
|
||||
linewidth := 3
|
||||
line_z := z
|
||||
x, y, z
|
||||
end
|
||||
|
||||
function add_theme(sym::Symbol, theme::KW)
|
||||
_themes[sym] = theme
|
||||
end
|
||||
|
||||
# add a new theme, using an existing theme as the base
|
||||
function add_theme(sym::Symbol;
|
||||
base = :default, # start with this theme
|
||||
bg = _themes[base][:bg],
|
||||
bglegend = _themes[base][:bglegend],
|
||||
bginside = _themes[base][:bginside],
|
||||
bgoutside = _themes[base][:bgoutside],
|
||||
fg = _themes[base][:fg],
|
||||
fglegend = _themes[base][:fglegend],
|
||||
fggrid = _themes[base][:fggrid],
|
||||
fgaxis = _themes[base][:fgaxis],
|
||||
fgtext = _themes[base][:fgtext],
|
||||
fgborder = _themes[base][:fgborder],
|
||||
fgguide = _themes[base][:fgguide],
|
||||
kw...)
|
||||
_themes[sym] = merge(KW(
|
||||
:bg => bg,
|
||||
:bglegend => bglegend,
|
||||
:bginside => bginside,
|
||||
:bgoutside => bgoutside,
|
||||
:fg => fg,
|
||||
:fglegend => fglegend,
|
||||
:fggrid => fggrid,
|
||||
:fgaxis => fgaxis,
|
||||
:fgtext => fgtext,
|
||||
:fgborder => fgborder,
|
||||
:fgguide => fgguide,
|
||||
), KW(kw))
|
||||
end
|
||||
|
||||
add_theme(:ggplot2,
|
||||
bglegend = _invisible,
|
||||
bginside = :lightgray,
|
||||
fg = :white,
|
||||
fglegend = _invisible,
|
||||
fgtext = :gray,
|
||||
fgguide = :black
|
||||
)
|
||||
|
||||
function set_theme(sym::Symbol)
|
||||
default(; _themes[sym]...)
|
||||
end
|
||||
|
||||
+59
-49
@@ -2,59 +2,25 @@
|
||||
# TODO: I declare lots of types here because of the lacking ability to do forward declarations in current Julia
|
||||
# I should move these to the relevant files when something like "extern" is implemented
|
||||
|
||||
typealias AVec AbstractVector
|
||||
typealias AMat AbstractMatrix
|
||||
typealias KW Dict{Symbol,Any}
|
||||
const AVec = AbstractVector
|
||||
const AMat = AbstractMatrix
|
||||
const KW = Dict{Symbol,Any}
|
||||
const TicksArgs = Union{AVec{T}, Tuple{AVec{T}, AVec{S}}, Symbol} where {T<:Real, S<:AbstractString}
|
||||
|
||||
immutable PlotsDisplay <: Display end
|
||||
|
||||
abstract AbstractBackend
|
||||
abstract AbstractPlot{T<:AbstractBackend}
|
||||
abstract AbstractLayout
|
||||
struct PlotsDisplay <: AbstractDisplay end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
immutable InputWrapper{T}
|
||||
struct InputWrapper{T}
|
||||
obj::T
|
||||
end
|
||||
wrap{T}(obj::T) = InputWrapper{T}(obj)
|
||||
wrap(obj::T) where {T} = InputWrapper{T}(obj)
|
||||
Base.isempty(wrapper::InputWrapper) = false
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# a single subplot
|
||||
type Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea::BoundingBox # the part where the data goes
|
||||
attr::KW # args specific to this subplot
|
||||
o # can store backend-specific data... like a pyplot ax
|
||||
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
|
||||
type Axis
|
||||
sp::Subplot
|
||||
d::KW
|
||||
end
|
||||
|
||||
type Extrema
|
||||
emin::Float64
|
||||
emax::Float64
|
||||
end
|
||||
Extrema() = Extrema(Inf, -Inf)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
typealias SubplotMap Dict{Any, Subplot}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Series
|
||||
mutable struct Series
|
||||
d::KW
|
||||
end
|
||||
|
||||
@@ -63,7 +29,42 @@ attr!(series::Series, v, k::Symbol) = (series.d[k] = v)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
# a single subplot
|
||||
mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
series_list::Vector{Series} # arguments for each series
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea::BoundingBox # the part where the data goes
|
||||
attr::KW # args specific to this subplot
|
||||
o # can store backend-specific data... like a pyplot ax
|
||||
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
|
||||
end
|
||||
|
||||
Base.show(io::IO, sp::Subplot) = print(io, "Subplot{$(sp[:subplot_index])}")
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
|
||||
mutable struct Axis
|
||||
sps::Vector{Subplot}
|
||||
d::KW
|
||||
end
|
||||
|
||||
mutable struct Extrema
|
||||
emin::Float64
|
||||
emax::Float64
|
||||
end
|
||||
Extrema() = Extrema(Inf, -Inf)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
const SubplotMap = Dict{Any, Subplot}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
|
||||
mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
backend::T # the backend type
|
||||
n::Int # number of series
|
||||
attr::KW # arguments for the whole plot
|
||||
@@ -83,12 +84,21 @@ function Plot()
|
||||
Subplot[], false)
|
||||
end
|
||||
|
||||
# TODO: make a decision... should plt[1] return the first subplot or the first series??
|
||||
# Base.getindex(plt::Plot, i::Integer) = plt.subplots[i]
|
||||
Base.getindex(plt::Plot, s::Symbol) = plt.spmap[s]
|
||||
Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c]
|
||||
attr(plt::Plot, k::Symbol) = plt.attr[k]
|
||||
attr!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v)
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Base.getindex(plt::Plot, i::Integer) = plt.subplots[i]
|
||||
Base.length(plt::Plot) = length(plt.subplots)
|
||||
Base.lastindex(plt::Plot) = length(plt)
|
||||
|
||||
Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c]
|
||||
Base.size(plt::Plot) = size(plt.layout)
|
||||
Base.size(plt::Plot, i::Integer) = size(plt.layout)[i]
|
||||
Base.ndims(plt::Plot) = 2
|
||||
|
||||
# attr(plt::Plot, k::Symbol) = plt.attr[k]
|
||||
# attr!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v)
|
||||
|
||||
Base.getindex(sp::Subplot, i::Integer) = series_list(sp)[i]
|
||||
Base.lastindex(sp::Subplot) = length(series_list(sp))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
+581
-107
@@ -3,8 +3,8 @@ calcMidpoints(edges::AbstractVector) = Float64[0.5 * (edges[i] + edges[i+1]) for
|
||||
|
||||
"Make histogram-like bins of data"
|
||||
function binData(data, nbins)
|
||||
lo, hi = extrema(data)
|
||||
edges = collect(linspace(lo, hi, nbins+1))
|
||||
lo, hi = ignorenan_extrema(data)
|
||||
edges = collect(range(lo, stop=hi, length=nbins+1))
|
||||
midpoints = calcMidpoints(edges)
|
||||
buckets = Int[max(2, min(searchsortedfirst(edges, x), length(edges)))-1 for x in data]
|
||||
counts = zeros(Int, length(midpoints))
|
||||
@@ -109,17 +109,17 @@ function regressionXY(x, y)
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
|
||||
# make a line segment
|
||||
regx = [minimum(x), maximum(x)]
|
||||
regx = [ignorenan_minimum(x), ignorenan_maximum(x)]
|
||||
regy = β * regx + α
|
||||
regx, regy
|
||||
end
|
||||
|
||||
function replace_image_with_heatmap{T<:Colorant}(z::Array{T})
|
||||
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
|
||||
@show T, size(z)
|
||||
n, m = size(z)
|
||||
# idx = 0
|
||||
colors = ColorGradient(vec(z))
|
||||
newz = reshape(linspace(0, 1, n*m), n, m)
|
||||
newz = reshape(range(0, stop=1, length=n*m), n, m)
|
||||
newz, colors
|
||||
# newz = zeros(n, m)
|
||||
# for i=1:n, j=1:m
|
||||
@@ -131,39 +131,101 @@ function replace_image_with_heatmap{T<:Colorant}(z::Array{T})
|
||||
end
|
||||
|
||||
function imageHack(d::KW)
|
||||
:heatmap in supported_types() || error("Neither :image or :heatmap are supported!")
|
||||
is_seriestype_supported(:heatmap) || error("Neither :image or :heatmap are supported!")
|
||||
d[:seriestype] = :heatmap
|
||||
d[:z], d[:fillcolor] = replace_image_with_heatmap(d[:z].surf)
|
||||
end
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
"Build line segments for plotting"
|
||||
mutable struct Segments{T}
|
||||
pts::Vector{T}
|
||||
end
|
||||
|
||||
# Segments() = Segments{Float64}(zeros(0))
|
||||
|
||||
Segments() = Segments(Float64)
|
||||
Segments(::Type{T}) where {T} = Segments(T[])
|
||||
Segments(p::Int) = Segments(NTuple{2,Float64}[])
|
||||
|
||||
|
||||
# Segments() = Segments(zeros(0))
|
||||
|
||||
to_nan(::Type{Float64}) = NaN
|
||||
to_nan(::Type{NTuple{2,Float64}}) = (NaN, NaN)
|
||||
|
||||
coords(segs::Segments{Float64}) = segs.pts
|
||||
coords(segs::Segments{NTuple{2,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts]
|
||||
|
||||
function Base.push!(segments::Segments{T}, vs...) where T
|
||||
if !isempty(segments.pts)
|
||||
push!(segments.pts, to_nan(T))
|
||||
end
|
||||
for v in vs
|
||||
push!(segments.pts, convert(T,v))
|
||||
end
|
||||
segments
|
||||
end
|
||||
|
||||
function Base.push!(segments::Segments{T}, vs::AVec) where T
|
||||
if !isempty(segments.pts)
|
||||
push!(segments.pts, to_nan(T))
|
||||
end
|
||||
for v in vs
|
||||
push!(segments.pts, convert(T,v))
|
||||
end
|
||||
segments
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# helper to manage NaN-separated segments
|
||||
|
||||
type SegmentsIterator
|
||||
mutable struct SegmentsIterator
|
||||
args::Tuple
|
||||
nextidx::Int
|
||||
n::Int
|
||||
end
|
||||
|
||||
function iter_segments(args...)
|
||||
tup = Plots.wraptuple(args)
|
||||
n = maximum(map(length, tup))
|
||||
SegmentsIterator(tup, 0, n)
|
||||
SegmentsIterator(tup, n)
|
||||
end
|
||||
|
||||
function iter_segments(series::Series)
|
||||
x, y, z = series[:x], series[:y], series[:z]
|
||||
if has_attribute_segments(series)
|
||||
if series[:seriestype] in (:scatter, :scatter3d)
|
||||
return [[i] for i in 1:length(y)]
|
||||
else
|
||||
return [i:(i + 1) for i in 1:(length(y) - 1)]
|
||||
end
|
||||
else
|
||||
segs = UnitRange{Int}[]
|
||||
args = is3d(series) ? (x, y, z) : (x, y)
|
||||
for seg in iter_segments(args...)
|
||||
push!(segs, seg)
|
||||
end
|
||||
return segs
|
||||
end
|
||||
end
|
||||
|
||||
# helpers to figure out if there are NaN values in a list of array types
|
||||
anynan(i::Int, args...) = any(a -> !isfinite(cycle(a,i)), args)
|
||||
anynan(istart::Int, iend::Int, args...) = any(i -> anynan(i, args...), istart:iend)
|
||||
allnan(istart::Int, iend::Int, args...) = all(i -> anynan(i, args...), istart:iend)
|
||||
anynan(i::Int, args::Tuple) = any(a -> try isnan(_cycle(a,i)) catch MethodError false end, args)
|
||||
anynan(istart::Int, iend::Int, args::Tuple) = any(i -> anynan(i, args), istart:iend)
|
||||
allnan(istart::Int, iend::Int, args::Tuple) = all(i -> anynan(i, args), istart:iend)
|
||||
|
||||
Base.start(itr::SegmentsIterator) = (itr.nextidx = 1) #resets
|
||||
Base.done(itr::SegmentsIterator, unused::Int) = itr.nextidx > itr.n
|
||||
function Base.next(itr::SegmentsIterator, unused::Int)
|
||||
i = istart = iend = itr.nextidx
|
||||
function Base.iterate(itr::SegmentsIterator, nextidx::Int = 1)
|
||||
nextidx > itr.n && return nothing
|
||||
if nextidx == 1 && !any(isempty,itr.args) && anynan(1, itr.args)
|
||||
nextidx = 2
|
||||
end
|
||||
|
||||
i = istart = iend = nextidx
|
||||
|
||||
# find the next NaN, and iend is the one before
|
||||
while i <= itr.n + 1
|
||||
if i > itr.n || anynan(i, itr.args...)
|
||||
if i > itr.n || anynan(i, itr.args)
|
||||
# done... array end or found NaN
|
||||
iend = i-1
|
||||
break
|
||||
@@ -171,67 +233,83 @@ function Base.next(itr::SegmentsIterator, unused::Int)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# find the next non-NaN, and set itr.nextidx
|
||||
# find the next non-NaN, and set nextidx
|
||||
while i <= itr.n
|
||||
if !anynan(i, itr.args...)
|
||||
if !anynan(i, itr.args)
|
||||
break
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
itr.nextidx = i
|
||||
istart:iend, 0
|
||||
istart:iend, i
|
||||
end
|
||||
|
||||
# Find minimal type that can contain NaN and x
|
||||
# To allow use of NaN separated segments with categorical x axis
|
||||
|
||||
float_extended_type(x::AbstractArray{T}) where {T} = Union{T,Float64}
|
||||
float_extended_type(x::AbstractArray{T}) where {T<:Real} = Float64
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
nop() = nothing
|
||||
notimpl() = error("This has not been implemented yet")
|
||||
|
||||
Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
|
||||
Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
|
||||
Base.cycle(v, idx::Int) = v
|
||||
isnothing(x::Nothing) = true
|
||||
isnothing(x) = false
|
||||
|
||||
Base.cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
|
||||
Base.cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
|
||||
Base.cycle(v, idx::AVec{Int}) = v
|
||||
_cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
|
||||
_cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
|
||||
|
||||
_cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
|
||||
_cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
|
||||
_cycle(v, idx::Int) = v
|
||||
|
||||
_cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
|
||||
_cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
|
||||
_cycle(v, indices::AVec{Int}) = fill(v, length(indices))
|
||||
|
||||
_cycle(grad::ColorGradient, idx::Int) = _cycle(grad.colors, idx)
|
||||
_cycle(grad::ColorGradient, indices::AVec{Int}) = _cycle(grad.colors, indices)
|
||||
|
||||
makevec(v::AVec) = v
|
||||
makevec{T}(v::T) = T[v]
|
||||
makevec(v::T) where {T} = T[v]
|
||||
|
||||
"duplicate a single value, or pass the 2-tuple through"
|
||||
maketuple(x::Real) = (x,x)
|
||||
maketuple{T,S}(x::@compat(Tuple{T,S})) = x
|
||||
maketuple(x::Tuple{T,S}) where {T,S} = x
|
||||
|
||||
mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
|
||||
mapFuncOrFuncs(fs::AVec{Function}, u::AVec) = [map(f, u) for f in fs]
|
||||
mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
|
||||
|
||||
unzip{X,Y}(xy::AVec{Tuple{X,Y}}) = [t[1] for t in xy], [t[2] for t in xy]
|
||||
unzip{X,Y,Z}(xyz::AVec{Tuple{X,Y,Z}}) = [t[1] for t in xyz], [t[2] for t in xyz], [t[3] for t in xyz]
|
||||
unzip{X,Y,U,V}(xyuv::AVec{Tuple{X,Y,U,V}}) = [t[1] for t in xyuv], [t[2] for t in xyuv], [t[3] for t in xyuv], [t[4] for t in xyuv]
|
||||
unzip(xy::AVec{Tuple{X,Y}}) where {X,Y} = [t[1] for t in xy], [t[2] for t in xy]
|
||||
unzip(xyz::AVec{Tuple{X,Y,Z}}) where {X,Y,Z} = [t[1] for t in xyz], [t[2] for t in xyz], [t[3] for t in xyz]
|
||||
unzip(xyuv::AVec{Tuple{X,Y,U,V}}) where {X,Y,U,V} = [t[1] for t in xyuv], [t[2] for t in xyuv], [t[3] for t in xyuv], [t[4] for t in xyuv]
|
||||
|
||||
unzip{T}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = T[t[1] for t in xy], T[t[2] for t in xy]
|
||||
unzip{T}(xy::FixedSizeArrays.Vec{2,T}) = T[xy[1]], T[xy[2]]
|
||||
unzip(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T} = T[t[1] for t in xy], T[t[2] for t in xy]
|
||||
unzip(xy::FixedSizeArrays.Vec{2,T}) where {T} = T[xy[1]], T[xy[2]]
|
||||
|
||||
unzip{T}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = T[t[1] for t in xyz], T[t[2] for t in xyz], T[t[3] for t in xyz]
|
||||
unzip{T}(xyz::FixedSizeArrays.Vec{3,T}) = T[xyz[1]], T[xyz[2]], T[xyz[3]]
|
||||
unzip(xyz::AVec{FixedSizeArrays.Vec{3,T}}) where {T} = T[t[1] for t in xyz], T[t[2] for t in xyz], T[t[3] for t in xyz]
|
||||
unzip(xyz::FixedSizeArrays.Vec{3,T}) where {T} = T[xyz[1]], T[xyz[2]], T[xyz[3]]
|
||||
|
||||
unzip{T}(xyuv::AVec{FixedSizeArrays.Vec{4,T}}) = T[t[1] for t in xyuv], T[t[2] for t in xyuv], T[t[3] for t in xyuv], T[t[4] for t in xyuv]
|
||||
unzip{T}(xyuv::FixedSizeArrays.Vec{4,T}) = T[xyuv[1]], T[xyuv[2]], T[xyuv[3]], T[xyuv[4]]
|
||||
unzip(xyuv::AVec{FixedSizeArrays.Vec{4,T}}) where {T} = T[t[1] for t in xyuv], T[t[2] for t in xyuv], T[t[3] for t in xyuv], T[t[4] for t in xyuv]
|
||||
unzip(xyuv::FixedSizeArrays.Vec{4,T}) where {T} = T[xyuv[1]], T[xyuv[2]], T[xyuv[3]], T[xyuv[4]]
|
||||
|
||||
# given 2-element lims and a vector of data x, widen lims to account for the extrema of x
|
||||
function _expand_limits(lims, x)
|
||||
try
|
||||
e1, e2 = extrema(x)
|
||||
lims[1] = min(lims[1], e1)
|
||||
lims[2] = max(lims[2], e2)
|
||||
e1, e2 = ignorenan_extrema(x)
|
||||
lims[1] = NaNMath.min(lims[1], e1)
|
||||
lims[2] = NaNMath.max(lims[2], e2)
|
||||
# catch err
|
||||
# warn(err)
|
||||
# @warn(err)
|
||||
catch
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n]
|
||||
|
||||
# if the type exists in a list, replace the first occurence. otherwise add it to the end
|
||||
function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
|
||||
@@ -250,24 +328,20 @@ function replaceType(vec, val)
|
||||
push!(vec, val)
|
||||
end
|
||||
|
||||
function replaceAlias!(d::KW, k::Symbol, aliases::KW)
|
||||
function replaceAlias!(d::KW, k::Symbol, aliases::Dict{Symbol,Symbol})
|
||||
if haskey(aliases, k)
|
||||
d[aliases[k]] = pop!(d, k)
|
||||
end
|
||||
end
|
||||
|
||||
function replaceAliases!(d::KW, aliases::KW)
|
||||
function replaceAliases!(d::KW, aliases::Dict{Symbol,Symbol})
|
||||
ks = collect(keys(d))
|
||||
for k in ks
|
||||
replaceAlias!(d, k, aliases)
|
||||
# if haskey(aliases, k)
|
||||
# d[aliases[k]] = d[k]
|
||||
# delete!(d, k)
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
createSegments(z) = collect(repmat(z',2,1))[2:end]
|
||||
createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end]
|
||||
|
||||
Base.first(c::Colorant) = c
|
||||
Base.first(x::Symbol) = x
|
||||
@@ -275,32 +349,55 @@ Base.first(x::Symbol) = x
|
||||
|
||||
sortedkeys(d::Dict) = sort(collect(keys(d)))
|
||||
|
||||
"create an (n+1) list of the outsides of heatmap rectangles"
|
||||
function heatmap_edges(v::AVec)
|
||||
vmin, vmax = extrema(v)
|
||||
extra = 0.5 * (vmax-vmin) / (length(v)-1)
|
||||
vcat(vmin-extra, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra)
|
||||
|
||||
const _scale_base = Dict{Symbol, Real}(
|
||||
:log10 => 10,
|
||||
:log2 => 2,
|
||||
:ln => ℯ,
|
||||
)
|
||||
|
||||
function _heatmap_edges(v::AVec)
|
||||
vmin, vmax = ignorenan_extrema(v)
|
||||
extra_min = (v[2] - v[1]) / 2
|
||||
extra_max = (v[end] - v[end - 1]) / 2
|
||||
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max)
|
||||
end
|
||||
|
||||
"create an (n+1) list of the outsides of heatmap rectangles"
|
||||
function heatmap_edges(v::AVec, scale::Symbol = :identity)
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
map(invf, _heatmap_edges(map(f,v)))
|
||||
end
|
||||
|
||||
function calc_r_extrema(x, y)
|
||||
xmin, xmax = extrema(x)
|
||||
ymin, ymax = extrema(y)
|
||||
r = 0.5 * min(xmax - xmin, ymax - ymin)
|
||||
extrema(r)
|
||||
xmin, xmax = ignorenan_extrema(x)
|
||||
ymin, ymax = ignorenan_extrema(y)
|
||||
r = 0.5 * NaNMath.min(xmax - xmin, ymax - ymin)
|
||||
ignorenan_extrema(r)
|
||||
end
|
||||
|
||||
function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y))
|
||||
rmin, rmax = r_extrema
|
||||
phi, r = x, y
|
||||
r = 0.5 * (r - rmin) / (rmax - rmin)
|
||||
n = max(length(phi), length(r))
|
||||
x = zeros(n)
|
||||
y = zeros(n)
|
||||
theta, r = filter_radial_data(x, y, r_extrema)
|
||||
r = (r .- rmin) ./ (rmax .- rmin)
|
||||
x = r.*cos.(theta)
|
||||
y = r.*sin.(theta)
|
||||
x, y
|
||||
end
|
||||
|
||||
# Filters radial data for points within the axis limits
|
||||
function filter_radial_data(theta, r, r_extrema::Tuple{Real, Real})
|
||||
n = max(length(theta), length(r))
|
||||
rmin, rmax = r_extrema
|
||||
x, y = zeros(n), zeros(n)
|
||||
for i in 1:n
|
||||
x[i] = cycle(r,i) * cos(cycle(phi,i))
|
||||
y[i] = cycle(r,i) * sin(cycle(phi,i))
|
||||
x[i] = _cycle(theta, i)
|
||||
y[i] = _cycle(r, i)
|
||||
end
|
||||
points = map((a, b) -> (a, b), x, y)
|
||||
filter!(a -> a[2] >= rmin && a[2] <= rmax, points)
|
||||
x = map(a -> a[1], points)
|
||||
y = map(a -> a[2], points)
|
||||
x, y
|
||||
end
|
||||
|
||||
@@ -312,12 +409,12 @@ function fakedata(sz...)
|
||||
y
|
||||
end
|
||||
|
||||
isijulia() = isdefined(Main, :IJulia) && Main.IJulia.inited
|
||||
isatom() = isdefined(Main, :Atom) && Main.Atom.isconnected()
|
||||
isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules)))
|
||||
isatom() = :Atom in nameof.(collect(values(Base.loaded_modules)))
|
||||
|
||||
function is_installed(pkgstr::AbstractString)
|
||||
try
|
||||
Pkg.installed(pkgstr) === nothing ? false: true
|
||||
Pkg.installed(pkgstr) === nothing ? false : true
|
||||
catch
|
||||
false
|
||||
end
|
||||
@@ -336,23 +433,23 @@ is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2
|
||||
|
||||
|
||||
isvertical(d::KW) = get(d, :orientation, :vertical) in (:vertical, :v, :vert)
|
||||
isvertical(series::Series) = isvertical(series.d)
|
||||
|
||||
|
||||
# ticksType{T<:Real,S<:Real}(ticks::@compat(Tuple{T,S})) = :limits
|
||||
ticksType{T<:Real}(ticks::AVec{T}) = :ticks
|
||||
ticksType{T<:AbstractString}(ticks::AVec{T}) = :labels
|
||||
ticksType{T<:AVec,S<:AVec}(ticks::@compat(Tuple{T,S})) = :ticks_and_labels
|
||||
ticksType(ticks::AVec{T}) where {T<:Real} = :ticks
|
||||
ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels
|
||||
ticksType(ticks::Tuple{T,S}) where {T<:AVec,S<:AVec} = :ticks_and_labels
|
||||
ticksType(ticks) = :invalid
|
||||
|
||||
limsType{T<:Real,S<:Real}(lims::@compat(Tuple{T,S})) = :limits
|
||||
limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
|
||||
limsType(lims::Symbol) = lims == :auto ? :auto : :invalid
|
||||
limsType(lims) = :invalid
|
||||
|
||||
# axis_Symbol(letter, postfix) = Symbol(letter * postfix)
|
||||
# axis_symbols(letter, postfix...) = map(s -> axis_Symbol(letter, s), postfix)
|
||||
|
||||
Base.convert{T<:Real}(::Type{Vector{T}}, rng::Range{T}) = T[x for x in rng]
|
||||
Base.convert{T<:Real,S<:Real}(::Type{Vector{T}}, rng::Range{S}) = T[x for x in rng]
|
||||
Base.convert(::Type{Vector{T}}, rng::AbstractRange{T}) where {T<:Real} = T[x for x in rng]
|
||||
Base.convert(::Type{Vector{T}}, rng::AbstractRange{S}) where {T<:Real,S<:Real} = T[x for x in rng]
|
||||
|
||||
Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b)))
|
||||
|
||||
@@ -395,9 +492,10 @@ end
|
||||
# this is a helper function to determine whether we need to transpose a surface matrix.
|
||||
# it depends on whether the backend matches rows to x (transpose_on_match == true) or vice versa
|
||||
# for example: PyPlot sends rows to y, so transpose_on_match should be true
|
||||
function transpose_z(d::KW, z, transpose_on_match::Bool = true)
|
||||
function transpose_z(d, z, transpose_on_match::Bool = true)
|
||||
if d[:match_dimensions] == transpose_on_match
|
||||
z'
|
||||
# z'
|
||||
permutedims(z, [2,1])
|
||||
else
|
||||
z
|
||||
end
|
||||
@@ -411,7 +509,7 @@ ok(tup::Tuple) = ok(tup...)
|
||||
# compute one side of a fill range from a ribbon
|
||||
function make_fillrange_side(y, rib)
|
||||
frs = zeros(length(y))
|
||||
for (i, (yi, ri)) in enumerate(zip(y, cycle(rib)))
|
||||
for (i, (yi, ri)) in enumerate(zip(y, Base.Iterators.cycle(rib)))
|
||||
frs[i] = yi + ri
|
||||
end
|
||||
frs
|
||||
@@ -422,13 +520,186 @@ function make_fillrange_from_ribbon(kw::KW)
|
||||
y, rib = kw[:y], kw[:ribbon]
|
||||
rib = wraptuple(rib)
|
||||
rib1, rib2 = -first(rib), last(rib)
|
||||
kw[:ribbon] = nothing
|
||||
# kw[:ribbon] = nothing
|
||||
kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2)
|
||||
(get(kw, :fillalpha, nothing) == nothing) && (kw[:fillalpha] = 0.5)
|
||||
end
|
||||
|
||||
#turn tuple of fillranges to one path
|
||||
function concatenate_fillrange(x,y::Tuple)
|
||||
rib1, rib2 = first(y), last(y)
|
||||
yline = vcat(rib1,(rib2)[end:-1:1])
|
||||
xline = vcat(x,x[end:-1:1])
|
||||
return xline, yline
|
||||
end
|
||||
|
||||
function get_sp_lims(sp::Subplot, letter::Symbol)
|
||||
axis_limits(sp[Symbol(letter, :axis)])
|
||||
end
|
||||
|
||||
"""
|
||||
xlims([plt])
|
||||
|
||||
Returns the x axis limits of the current plot or subplot
|
||||
"""
|
||||
xlims(sp::Subplot) = get_sp_lims(sp, :x)
|
||||
|
||||
"""
|
||||
ylims([plt])
|
||||
|
||||
Returns the y axis limits of the current plot or subplot
|
||||
"""
|
||||
ylims(sp::Subplot) = get_sp_lims(sp, :y)
|
||||
|
||||
"""
|
||||
zlims([plt])
|
||||
|
||||
Returns the z axis limits of the current plot or subplot
|
||||
"""
|
||||
zlims(sp::Subplot) = get_sp_lims(sp, :z)
|
||||
|
||||
xlims(plt::Plot, sp_idx::Int = 1) = xlims(plt[sp_idx])
|
||||
ylims(plt::Plot, sp_idx::Int = 1) = ylims(plt[sp_idx])
|
||||
zlims(plt::Plot, sp_idx::Int = 1) = zlims(plt[sp_idx])
|
||||
xlims(sp_idx::Int = 1) = xlims(current(), sp_idx)
|
||||
ylims(sp_idx::Int = 1) = ylims(current(), sp_idx)
|
||||
zlims(sp_idx::Int = 1) = zlims(current(), sp_idx)
|
||||
|
||||
|
||||
function get_clims(sp::Subplot)
|
||||
zmin, zmax = Inf, -Inf
|
||||
z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface)
|
||||
for series in series_list(sp)
|
||||
for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z])
|
||||
if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Real)
|
||||
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals.surf)...)
|
||||
elseif (vals != nothing) && (eltype(vals) <: Real)
|
||||
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals)...)
|
||||
end
|
||||
end
|
||||
end
|
||||
clims = sp[:clims]
|
||||
if is_2tuple(clims)
|
||||
isfinite(clims[1]) && (zmin = clims[1])
|
||||
isfinite(clims[2]) && (zmax = clims[2])
|
||||
end
|
||||
return zmin < zmax ? (zmin, zmax) : (-0.1, 0.1)
|
||||
end
|
||||
|
||||
_update_clims(zmin, zmax, emin, emax) = min(zmin, emin), max(zmax, emax)
|
||||
|
||||
function hascolorbar(series::Series)
|
||||
st = series[:seriestype]
|
||||
hascbar = st == :heatmap
|
||||
if st == :contour
|
||||
hascbar = (isscalar(series[:levels]) ? (series[:levels] > 1) : (length(series[:levels]) > 1)) && (length(unique(Array(series[:z]))) > 1)
|
||||
end
|
||||
if series[:marker_z] != nothing || series[:line_z] != nothing || series[:fill_z] != nothing
|
||||
hascbar = true
|
||||
end
|
||||
# no colorbar if we are creating a surface LightSource
|
||||
if xor(st == :surface, series[:fill_z] != nothing)
|
||||
hascbar = true
|
||||
end
|
||||
return hascbar
|
||||
end
|
||||
|
||||
function hascolorbar(sp::Subplot)
|
||||
cbar = sp[:colorbar]
|
||||
hascbar = false
|
||||
if cbar != :none
|
||||
for series in series_list(sp)
|
||||
if hascolorbar(series)
|
||||
hascbar = true
|
||||
end
|
||||
end
|
||||
end
|
||||
hascbar
|
||||
end
|
||||
|
||||
function get_linecolor(series, i::Int = 1)
|
||||
lc = series[:linecolor]
|
||||
lz = series[:line_z]
|
||||
if lz == nothing
|
||||
isa(lc, ColorGradient) ? lc : plot_color(_cycle(lc, i))
|
||||
else
|
||||
cmin, cmax = get_clims(series[:subplot])
|
||||
grad = isa(lc, ColorGradient) ? lc : cgrad()
|
||||
grad[clamp((_cycle(lz, i) - cmin) / (cmax - cmin), 0, 1)]
|
||||
end
|
||||
end
|
||||
|
||||
function get_linealpha(series, i::Int = 1)
|
||||
_cycle(series[:linealpha], i)
|
||||
end
|
||||
|
||||
function get_linewidth(series, i::Int = 1)
|
||||
_cycle(series[:linewidth], i)
|
||||
end
|
||||
|
||||
function get_linestyle(series, i::Int = 1)
|
||||
_cycle(series[:linestyle], i)
|
||||
end
|
||||
|
||||
function get_fillcolor(series, i::Int = 1)
|
||||
fc = series[:fillcolor]
|
||||
fz = series[:fill_z]
|
||||
if fz == nothing
|
||||
isa(fc, ColorGradient) ? fc : plot_color(_cycle(fc, i))
|
||||
else
|
||||
cmin, cmax = get_clims(series[:subplot])
|
||||
grad = isa(fc, ColorGradient) ? fc : cgrad()
|
||||
grad[clamp((_cycle(fz, i) - cmin) / (cmax - cmin), 0, 1)]
|
||||
end
|
||||
end
|
||||
|
||||
function get_fillalpha(series, i::Int = 1)
|
||||
_cycle(series[:fillalpha], i)
|
||||
end
|
||||
|
||||
function get_markercolor(series, i::Int = 1)
|
||||
mc = series[:markercolor]
|
||||
mz = series[:marker_z]
|
||||
if mz == nothing
|
||||
isa(mc, ColorGradient) ? mc : plot_color(_cycle(mc, i))
|
||||
else
|
||||
cmin, cmax = get_clims(series[:subplot])
|
||||
grad = isa(mc, ColorGradient) ? mc : cgrad()
|
||||
grad[clamp((_cycle(mz, i) - cmin) / (cmax - cmin), 0, 1)]
|
||||
end
|
||||
end
|
||||
|
||||
function get_markeralpha(series, i::Int = 1)
|
||||
_cycle(series[:markeralpha], i)
|
||||
end
|
||||
|
||||
function get_markerstrokecolor(series, i::Int = 1)
|
||||
msc = series[:markerstrokecolor]
|
||||
isa(msc, ColorGradient) ? msc : _cycle(msc, i)
|
||||
end
|
||||
|
||||
function get_markerstrokealpha(series, i::Int = 1)
|
||||
_cycle(series[:markerstrokealpha], i)
|
||||
end
|
||||
|
||||
function has_attribute_segments(series::Series)
|
||||
# we want to check if a series needs to be split into segments just because
|
||||
# of its attributes
|
||||
for letter in (:x, :y, :z)
|
||||
# If we have NaNs in the data they define the segments and
|
||||
# SegmentsIterator is used
|
||||
series[letter] != nothing && NaN in collect(series[letter]) && return false
|
||||
end
|
||||
series[:seriestype] == :shape && return false
|
||||
# ... else we check relevant attributes if they have multiple inputs
|
||||
return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray{<:Real} for attr in (:line_z, :fill_z, :marker_z))
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
wraptuple(x::@compat(Tuple)) = x
|
||||
makekw(; kw...) = KW(kw)
|
||||
|
||||
wraptuple(x::Tuple) = x
|
||||
wraptuple(x) = (x,)
|
||||
|
||||
trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x)
|
||||
@@ -436,7 +707,7 @@ trueOrAllTrue(f::Function, x) = f(x)
|
||||
|
||||
allLineTypes(arg) = trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg)
|
||||
allStyles(arg) = trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg)
|
||||
allShapes(arg) = trueOrAllTrue(a -> get(_markerAliases, a, a) in _allMarkers, arg) ||
|
||||
allShapes(arg) = trueOrAllTrue(a -> is_marker_supported(get(_markerAliases, a, a)), arg) ||
|
||||
trueOrAllTrue(a -> isa(a, Shape), arg)
|
||||
allAlphas(arg) = trueOrAllTrue(a -> (typeof(a) <: Real && a > 0 && a < 1) ||
|
||||
(typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))), arg)
|
||||
@@ -450,7 +721,7 @@ allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg)
|
||||
"""
|
||||
Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example:
|
||||
```
|
||||
with(:gadfly, size=(400,400), type=:histogram) do
|
||||
with(:gr, size=(400,400), type=:histogram) do
|
||||
plot(rand(10))
|
||||
plot(rand(10))
|
||||
end
|
||||
@@ -535,7 +806,7 @@ end
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
type DebugMode
|
||||
mutable struct DebugMode
|
||||
on::Bool
|
||||
end
|
||||
const _debugMode = DebugMode(false)
|
||||
@@ -572,18 +843,18 @@ end
|
||||
# used in updating an existing series
|
||||
|
||||
extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n)
|
||||
extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + maximum(v))
|
||||
extendSeriesData{T}(v::Range{T}, z::Real) = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData{T}(v::Range{T}, z::AVec) = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData{T}(v::AVec{T}, z::Real) = (push!(v, convert(T, z)); v)
|
||||
extendSeriesData{T}(v::AVec{T}, z::AVec) = (append!(v, convert(Vector{T}, z)); v)
|
||||
extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + ignorenan_maximum(v))
|
||||
extendSeriesData(v::AbstractRange{T}, z::Real) where {T} = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData(v::AbstractRange{T}, z::AVec) where {T} = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData(v::AVec{T}, z::Real) where {T} = (push!(v, convert(T, z)); v)
|
||||
extendSeriesData(v::AVec{T}, z::AVec) where {T} = (append!(v, convert(Vector{T}, z)); v)
|
||||
|
||||
|
||||
# -------------------------------------------------------
|
||||
# NOTE: backends should implement the following methods to get/set the x/y/z data objects
|
||||
|
||||
tovec(v::AbstractVector) = v
|
||||
tovec(v::Void) = zeros(0)
|
||||
tovec(v::Nothing) = zeros(0)
|
||||
|
||||
function getxy(plt::Plot, i::Integer)
|
||||
d = plt.series_list[i].d
|
||||
@@ -594,30 +865,90 @@ function getxyz(plt::Plot, i::Integer)
|
||||
tovec(d[:x]), tovec(d[:y]), tovec(d[:z])
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer)
|
||||
function setxy!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y}
|
||||
series = plt.series_list[i]
|
||||
series.d[:x], series.d[:y] = xy
|
||||
sp = series.d[:subplot]
|
||||
expand_extrema!(sp.attr[:xaxis], xy[1])
|
||||
expand_extrema!(sp.attr[:yaxis], xy[2])
|
||||
reset_extrema!(sp)
|
||||
_series_updated(plt, series)
|
||||
end
|
||||
function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer)
|
||||
function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z}
|
||||
series = plt.series_list[i]
|
||||
series.d[:x], series.d[:y], series.d[:z] = xyz
|
||||
sp = series.d[:subplot]
|
||||
expand_extrema!(sp.attr[:xaxis], xyz[1])
|
||||
expand_extrema!(sp.attr[:yaxis], xyz[2])
|
||||
expand_extrema!(sp.attr[:zaxis], xyz[3])
|
||||
reset_extrema!(sp)
|
||||
_series_updated(plt, series)
|
||||
end
|
||||
|
||||
function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix}
|
||||
setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i)
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------------------------------
|
||||
# indexing notation
|
||||
|
||||
Base.getindex(plt::Plot, i::Integer) = getxy(plt, i)
|
||||
Base.setindex!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) = setxy!(plt, xy, i)
|
||||
Base.setindex!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) = setxyz!(plt, xyz, i)
|
||||
# Base.getindex(plt::Plot, i::Integer) = getxy(plt, i)
|
||||
Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} = (setxy!(plt, xy, i); plt)
|
||||
Base.setindex!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z} = (setxyz!(plt, xyz, i); plt)
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
# operate on individual series
|
||||
|
||||
function push_x!(series::Series, xi)
|
||||
push!(series[:x], xi)
|
||||
expand_extrema!(series[:subplot][:xaxis], xi)
|
||||
return
|
||||
end
|
||||
function push_y!(series::Series, yi)
|
||||
push!(series[:y], yi)
|
||||
expand_extrema!(series[:subplot][:yaxis], yi)
|
||||
return
|
||||
end
|
||||
function push_z!(series::Series, zi)
|
||||
push!(series[:z], zi)
|
||||
expand_extrema!(series[:subplot][:zaxis], zi)
|
||||
return
|
||||
end
|
||||
|
||||
function Base.push!(series::Series, yi)
|
||||
x = extendSeriesByOne(series[:x])
|
||||
expand_extrema!(series[:subplot][:xaxis], x[end])
|
||||
series[:x] = x
|
||||
push_y!(series, yi)
|
||||
end
|
||||
Base.push!(series::Series, xi, yi) = (push_x!(series,xi); push_y!(series,yi))
|
||||
Base.push!(series::Series, xi, yi, zi) = (push_x!(series,xi); push_y!(series,yi); push_z!(series,zi))
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
function attr!(series::Series; kw...)
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
for (k,v) in d
|
||||
if haskey(_series_defaults, k)
|
||||
series[k] = v
|
||||
else
|
||||
@warn("unused key $k in series attr")
|
||||
end
|
||||
end
|
||||
_series_updated(series[:subplot].plt, series)
|
||||
series
|
||||
end
|
||||
|
||||
function attr!(sp::Subplot; kw...)
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
for (k,v) in d
|
||||
if haskey(_subplot_defaults, k)
|
||||
sp[k] = v
|
||||
else
|
||||
@warn("unused key $k in subplot attr")
|
||||
end
|
||||
end
|
||||
sp
|
||||
end
|
||||
|
||||
# -------------------------------------------------------
|
||||
# push/append for one series
|
||||
@@ -671,10 +1002,10 @@ function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec, z::AVec)
|
||||
end
|
||||
|
||||
# tuples
|
||||
Base.push!{X,Y}(plt::Plot, xy::Tuple{X,Y}) = push!(plt, 1, xy...)
|
||||
Base.push!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}) = push!(plt, 1, xyz...)
|
||||
Base.push!{X,Y}(plt::Plot, i::Integer, xy::Tuple{X,Y}) = push!(plt, i, xy...)
|
||||
Base.push!{X,Y,Z}(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) = push!(plt, i, xyz...)
|
||||
Base.push!(plt::Plot, xy::Tuple{X,Y}) where {X,Y} = push!(plt, 1, xy...)
|
||||
Base.push!(plt::Plot, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, 1, xyz...)
|
||||
Base.push!(plt::Plot, i::Integer, xy::Tuple{X,Y}) where {X,Y} = push!(plt, i, xy...)
|
||||
Base.push!(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, i, xyz...)
|
||||
|
||||
# -------------------------------------------------------
|
||||
# push/append for all series
|
||||
@@ -738,9 +1069,152 @@ mm2px(mm::Real) = float(px / MM_PER_PX)
|
||||
|
||||
|
||||
"Smallest x in plot"
|
||||
xmin(plt::Plot) = minimum([minimum(series.d[:x]) for series in plt.series_list])
|
||||
xmin(plt::Plot) = ignorenan_minimum([ignorenan_minimum(series.d[:x]) for series in plt.series_list])
|
||||
"Largest x in plot"
|
||||
xmax(plt::Plot) = maximum([maximum(series.d[:x]) for series in plt.series_list])
|
||||
xmax(plt::Plot) = ignorenan_maximum([ignorenan_maximum(series.d[:x]) for series in plt.series_list])
|
||||
|
||||
"Extrema of x-values in plot"
|
||||
Base.extrema(plt::Plot) = (xmin(plt), xmax(plt))
|
||||
ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# get fonts from objects:
|
||||
|
||||
titlefont(sp::Subplot) = font(
|
||||
sp[:titlefontfamily],
|
||||
sp[:titlefontsize],
|
||||
sp[:titlefontvalign],
|
||||
sp[:titlefonthalign],
|
||||
sp[:titlefontrotation],
|
||||
sp[:titlefontcolor],
|
||||
)
|
||||
|
||||
legendfont(sp::Subplot) = font(
|
||||
sp[:legendfontfamily],
|
||||
sp[:legendfontsize],
|
||||
sp[:legendfontvalign],
|
||||
sp[:legendfonthalign],
|
||||
sp[:legendfontrotation],
|
||||
sp[:legendfontcolor],
|
||||
)
|
||||
|
||||
tickfont(ax::Axis) = font(
|
||||
ax[:tickfontfamily],
|
||||
ax[:tickfontsize],
|
||||
ax[:tickfontvalign],
|
||||
ax[:tickfonthalign],
|
||||
ax[:tickfontrotation],
|
||||
ax[:tickfontcolor],
|
||||
)
|
||||
|
||||
guidefont(ax::Axis) = font(
|
||||
ax[:guidefontfamily],
|
||||
ax[:guidefontsize],
|
||||
ax[:guidefontvalign],
|
||||
ax[:guidefonthalign],
|
||||
ax[:guidefontrotation],
|
||||
ax[:guidefontcolor],
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# converts unicode scientific notation unsupported by pgfplots and gr
|
||||
# into a format that works
|
||||
|
||||
function convert_sci_unicode(label::AbstractString)
|
||||
unicode_dict = Dict(
|
||||
'⁰' => "0",
|
||||
'¹' => "1",
|
||||
'²' => "2",
|
||||
'³' => "3",
|
||||
'⁴' => "4",
|
||||
'⁵' => "5",
|
||||
'⁶' => "6",
|
||||
'⁷' => "7",
|
||||
'⁸' => "8",
|
||||
'⁹' => "9",
|
||||
'⁻' => "-",
|
||||
"×10" => "×10^{",
|
||||
)
|
||||
for key in keys(unicode_dict)
|
||||
label = replace(label, key => unicode_dict[key])
|
||||
end
|
||||
if occursin("10^{", label)
|
||||
label = string(label, "}")
|
||||
end
|
||||
label
|
||||
end
|
||||
|
||||
function straightline_data(series)
|
||||
sp = series[:subplot]
|
||||
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
|
||||
x, y = series[:x], series[:y]
|
||||
n = length(x)
|
||||
if n == 2
|
||||
return straightline_data(xl, yl, x, y)
|
||||
else
|
||||
k, r = divrem(n, 3)
|
||||
if r == 0
|
||||
xdata, ydata = fill(NaN, n), fill(NaN, n)
|
||||
for i in 1:k
|
||||
inds = (3 * i - 2):(3 * i - 1)
|
||||
xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds])
|
||||
end
|
||||
return xdata, ydata
|
||||
else
|
||||
error("Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function straightline_data(xl, yl, x, y)
|
||||
x_vals, y_vals = if y[1] == y[2]
|
||||
if x[1] == x[2]
|
||||
error("Two identical points cannot be used to describe a straight line.")
|
||||
else
|
||||
[xl[1], xl[2]], [y[1], y[2]]
|
||||
end
|
||||
elseif x[1] == x[2]
|
||||
[x[1], x[2]], [yl[1], yl[2]]
|
||||
else
|
||||
# get a and b from the line y = a * x + b through the points given by
|
||||
# the coordinates x and x
|
||||
b = y[1] - (y[1] - y[2]) * x[1] / (x[1] - x[2])
|
||||
a = (y[1] - y[2]) / (x[1] - x[2])
|
||||
# get the data values
|
||||
xdata = [clamp(x[1] + (x[1] - x[2]) * (ylim - y[1]) / (y[1] - y[2]), xl...) for ylim in yl]
|
||||
|
||||
xdata, a .* xdata .+ b
|
||||
end
|
||||
# expand the data outside the axis limits, by a certain factor too improve
|
||||
# plotly(js) and interactive behaviour
|
||||
factor = 100
|
||||
x_vals = x_vals .+ (x_vals[2] - x_vals[1]) .* factor .* [-1, 1]
|
||||
y_vals = y_vals .+ (y_vals[2] - y_vals[1]) .* factor .* [-1, 1]
|
||||
return x_vals, y_vals
|
||||
end
|
||||
|
||||
function shape_data(series)
|
||||
sp = series[:subplot]
|
||||
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
|
||||
x, y = series[:x], series[:y]
|
||||
factor = 100
|
||||
for i in eachindex(x)
|
||||
if x[i] == -Inf
|
||||
x[i] = xl[1] - factor * (xl[2] - xl[1])
|
||||
elseif x[i] == Inf
|
||||
x[i] = xl[2] + factor * (xl[2] - xl[1])
|
||||
end
|
||||
end
|
||||
for i in eachindex(y)
|
||||
if y[i] == -Inf
|
||||
y[i] = yl[1] - factor * (yl[2] - yl[1])
|
||||
elseif y[i] == Inf
|
||||
y[i] = yl[2] + factor * (yl[2] - yl[1])
|
||||
end
|
||||
end
|
||||
return x, y
|
||||
end
|
||||
|
||||
function construct_categorical_data(x::AbstractArray, axis::Axis)
|
||||
map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x)
|
||||
end
|
||||
|
||||
+4
-11
@@ -1,16 +1,9 @@
|
||||
julia 0.4
|
||||
|
||||
RecipesBase
|
||||
Colors
|
||||
Reexport
|
||||
Measures
|
||||
FactCheck
|
||||
StatPlots
|
||||
Images
|
||||
PyPlot
|
||||
ImageMagick
|
||||
@osx QuartzImageIO
|
||||
GR
|
||||
DataFrames
|
||||
GR 0.31.0
|
||||
RDatasets
|
||||
VisualRegressionTests
|
||||
UnicodePlots
|
||||
Glob
|
||||
LaTeXStrings
|
||||
|
||||
+21
-22
@@ -2,19 +2,20 @@
|
||||
using VisualRegressionTests
|
||||
# using ExamplePlots
|
||||
|
||||
import DataFrames, RDatasets
|
||||
# import DataFrames, RDatasets
|
||||
|
||||
# don't let pyplot use a gui... it'll crash
|
||||
# note: Agg will set gui -> :none in PyPlot
|
||||
ENV["MPLBACKEND"] = "Agg"
|
||||
try
|
||||
@eval import PyPlot
|
||||
info("Matplotlib version: $(PyPlot.matplotlib[:__version__])")
|
||||
end
|
||||
# ENV["MPLBACKEND"] = "Agg"
|
||||
# try
|
||||
# @eval import PyPlot
|
||||
# @info("Matplotlib version: $(PyPlot.matplotlib[:__version__])")
|
||||
# end
|
||||
|
||||
|
||||
using Plots, FactCheck
|
||||
using Glob
|
||||
using Plots
|
||||
# using StatPlots
|
||||
using Test
|
||||
|
||||
default(size=(500,300))
|
||||
|
||||
@@ -22,18 +23,18 @@ default(size=(500,300))
|
||||
# TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that
|
||||
# is referenced in a button press callback (the button clicked callback will call notify() on that condition)
|
||||
|
||||
const _current_plots_version = v"0.7.2"
|
||||
const _current_plots_version = v"0.17.4"
|
||||
|
||||
|
||||
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
|
||||
Plots._debugMode.on = debug
|
||||
example = Plots._examples[idx]
|
||||
info("Testing plot: $pkg:$idx:$(example.header)")
|
||||
@info("Testing plot: $pkg:$idx:$(example.header)")
|
||||
backend(pkg)
|
||||
backend()
|
||||
|
||||
# ensure consistent results
|
||||
srand(1234)
|
||||
Random.seed!(1234)
|
||||
|
||||
# reference image directory setup
|
||||
# refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg))
|
||||
@@ -41,10 +42,8 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is
|
||||
fn = "ref$idx.png"
|
||||
|
||||
# firgure out version info
|
||||
G = glob(relpath(refdir) * "/*")
|
||||
# @show refdir fn G
|
||||
versions = map(fn -> VersionNumber(split(fn,"/")[end]), G)
|
||||
versions = reverse(sort(versions))
|
||||
vns = filter(x->x[1] != '.', readdir(refdir))
|
||||
versions = sort(VersionNumber.(vns), rev = true)
|
||||
versions = filter(v -> v <= _current_plots_version, versions)
|
||||
# @show refdir fn versions
|
||||
|
||||
@@ -75,12 +74,12 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is
|
||||
png(fn)
|
||||
end
|
||||
|
||||
try
|
||||
run(`mkdir -p $newdir`)
|
||||
catch err
|
||||
display(err)
|
||||
end
|
||||
# reffn = joinpath(refdir, "ref$idx.png")
|
||||
# try
|
||||
# run(`mkdir -p $newdir`)
|
||||
# catch err
|
||||
# display(err)
|
||||
# end
|
||||
# # reffn = joinpath(refdir, "ref$idx.png")
|
||||
|
||||
# the test
|
||||
vtest = VisualTest(func, reffn, idx)
|
||||
@@ -96,7 +95,7 @@ function image_comparison_facts(pkg::Symbol;
|
||||
for i in 1:length(Plots._examples)
|
||||
i in skip && continue
|
||||
if only == nothing || i in only
|
||||
@fact image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success --> true
|
||||
@test image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,9 +5,11 @@ set -ex
|
||||
sudo apt-get -qq update
|
||||
# sudo apt-get install -y wkhtmltopdf
|
||||
|
||||
sudo apt-get install -y xfonts-75dpi
|
||||
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2/wkhtmltox-0.12.2_linux-trusty-amd64.deb
|
||||
sudo dpkg -i wkhtmltox-0.12.2_linux-trusty-amd64.deb
|
||||
sudo apt-get install -y xfonts-75dpi xfonts-base
|
||||
wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.2.1/wkhtmltox-0.12.2.1_linux-precise-amd64.deb
|
||||
sudo dpkg -i wkhtmltox-0.12.2.1_linux-precise-amd64.deb
|
||||
# wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2/wkhtmltox-0.12.2_linux-trusty-amd64.deb
|
||||
# sudo dpkg -i wkhtmltox-0.12.2_linux-trusty-amd64.deb
|
||||
wkhtmltoimage http://www.google.com test.png
|
||||
ls
|
||||
|
||||
|
||||
+136
-51
@@ -3,89 +3,174 @@ module PlotsTests
|
||||
include("imgcomp.jl")
|
||||
|
||||
# don't actually show the plots
|
||||
srand(1234)
|
||||
Random.seed!(1234)
|
||||
default(show=false, reuse=true)
|
||||
img_eps = 5e-2
|
||||
img_eps = isinteractive() ? 1e-2 : 10e-2
|
||||
|
||||
# facts("Gadfly") do
|
||||
# @fact gadfly() --> Plots.GadflyBackend()
|
||||
# @fact backend() --> Plots.GadflyBackend()
|
||||
@testset "GR" begin
|
||||
ENV["PLOTS_TEST"] = "true"
|
||||
ENV["GKSwstype"] = "100"
|
||||
@test gr() == Plots.GRBackend()
|
||||
@test backend() == Plots.GRBackend()
|
||||
|
||||
image_comparison_facts(:gr, eps=img_eps, skip = [25, 30])
|
||||
end
|
||||
|
||||
|
||||
#@testset "PyPlot" begin
|
||||
# @test pyplot() == Plots.PyPlotBackend()
|
||||
# @test backend() == Plots.PyPlotBackend()
|
||||
#
|
||||
# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyBackend}
|
||||
# @fact plot(Int[1,2,3], rand(3)) --> not(nothing)
|
||||
# @fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing)
|
||||
# @fact plot!(rand(10,3), rand(10,3)) --> not(nothing)
|
||||
# image_comparison_facts(:pyplot, eps=img_eps)
|
||||
#end
|
||||
|
||||
@testset "UnicodePlots" begin
|
||||
@test unicodeplots() == Plots.UnicodePlotsBackend()
|
||||
@test backend() == Plots.UnicodePlotsBackend()
|
||||
|
||||
# lets just make sure it runs without error
|
||||
@test isa(plot(rand(10)), Plots.Plot) == true
|
||||
end
|
||||
|
||||
# The plotlyjs testimages return a connection error on travis:
|
||||
# connect: connection refused (ECONNREFUSED)
|
||||
|
||||
# @testset "PlotlyJS" begin
|
||||
# @test plotlyjs() == Plots.PlotlyJSBackend()
|
||||
# @test backend() == Plots.PlotlyJSBackend()
|
||||
#
|
||||
# image_comparison_facts(:gadfly, skip=[4,6,23,24,27], eps=img_eps)
|
||||
# if Sys.islinux() && isinteractive()
|
||||
# image_comparison_facts(:plotlyjs,
|
||||
# skip=[
|
||||
# 2, # animation (skipped for speed)
|
||||
# 27, # (polar plots) takes very long / not working
|
||||
# 31, # animation (skipped for speed)
|
||||
# ],
|
||||
# eps=img_eps)
|
||||
# end
|
||||
# end
|
||||
|
||||
facts("PyPlot") do
|
||||
@fact pyplot() --> Plots.PyPlotBackend()
|
||||
@fact backend() --> Plots.PyPlotBackend()
|
||||
|
||||
image_comparison_facts(:pyplot, skip=[30], eps=img_eps)
|
||||
end
|
||||
# InspectDR returns that error on travis:
|
||||
# ERROR: LoadError: InitError: Cannot open display:
|
||||
# in Gtk.GLib.GError(::Gtk.##229#230) at /home/travis/.julia/v0.5/Gtk/src/GLib/gerror.jl:17
|
||||
|
||||
facts("GR") do
|
||||
@fact gr() --> Plots.GRBackend()
|
||||
@fact backend() --> Plots.GRBackend()
|
||||
|
||||
@linux_only image_comparison_facts(:gr, skip=[30], eps=img_eps)
|
||||
end
|
||||
|
||||
facts("Plotly") do
|
||||
@fact plotly() --> Plots.PlotlyBackend()
|
||||
@fact backend() --> Plots.PlotlyBackend()
|
||||
|
||||
# # until png generation is reliable on OSX, just test on linux
|
||||
# @linux_only image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], eps=img_eps)
|
||||
end
|
||||
# @testset "InspectDR" begin
|
||||
# @test inspectdr() == Plots.InspectDRBackend()
|
||||
# @test backend() == Plots.InspectDRBackend()
|
||||
#
|
||||
# image_comparison_facts(:inspectdr,
|
||||
# skip=[
|
||||
# 2, # animation
|
||||
# 6, # heatmap not defined
|
||||
# 10, # heatmap not defined
|
||||
# 22, # contour not defined
|
||||
# 23, # pie not defined
|
||||
# 27, # polar plot not working
|
||||
# 28, # heatmap not defined
|
||||
# 31, # animation
|
||||
# ],
|
||||
# eps=img_eps)
|
||||
# end
|
||||
|
||||
|
||||
# facts("Immerse") do
|
||||
# @fact immerse() --> Plots.ImmerseBackend()
|
||||
# @fact backend() --> Plots.ImmerseBackend()
|
||||
# @testset "Plotly" begin
|
||||
# @test plotly() == Plots.PlotlyBackend()
|
||||
# @test backend() == Plots.PlotlyBackend()
|
||||
#
|
||||
# # # until png generation is reliable on OSX, just test on linux
|
||||
# # @static Sys.islinux() && image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], eps=img_eps)
|
||||
# end
|
||||
|
||||
|
||||
# @testset "Immerse" begin
|
||||
# @test immerse() == Plots.ImmerseBackend()
|
||||
# @test backend() == Plots.ImmerseBackend()
|
||||
#
|
||||
# # as long as we can plot anything without error, it should be the same as Gadfly
|
||||
# image_comparison_facts(:immerse, only=[1], eps=img_eps)
|
||||
# end
|
||||
|
||||
|
||||
# facts("PlotlyJS") do
|
||||
# @fact plotlyjs() --> Plots.PlotlyJSBackend()
|
||||
# @fact backend() --> Plots.PlotlyJSBackend()
|
||||
# @testset "PlotlyJS" begin
|
||||
# @test plotlyjs() == Plots.PlotlyJSBackend()
|
||||
# @test backend() == Plots.PlotlyJSBackend()
|
||||
#
|
||||
# # as long as we can plot anything without error, it should be the same as Plotly
|
||||
# image_comparison_facts(:plotlyjs, only=[1], eps=img_eps)
|
||||
# end
|
||||
|
||||
|
||||
facts("UnicodePlots") do
|
||||
@fact unicodeplots() --> Plots.UnicodePlotsBackend()
|
||||
@fact backend() --> Plots.UnicodePlotsBackend()
|
||||
|
||||
# lets just make sure it runs without error
|
||||
@fact isa(plot(rand(10)), Plot) --> true
|
||||
end
|
||||
# @testset "Gadfly" begin
|
||||
# @test gadfly() == Plots.GadflyBackend()
|
||||
# @test backend() == Plots.GadflyBackend()
|
||||
#
|
||||
# @test typeof(plot(1:10)) == Plots.Plot{Plots.GadflyBackend}
|
||||
# @test plot(Int[1,2,3], rand(3)) == not(nothing)
|
||||
# @test plot(sort(rand(10)), rand(Int, 10, 3)) == not(nothing)
|
||||
# @test plot!(rand(10,3), rand(10,3)) == not(nothing)
|
||||
#
|
||||
# image_comparison_facts(:gadfly, skip=[4,6,23,24,27], eps=img_eps)
|
||||
# end
|
||||
|
||||
|
||||
|
||||
facts("Axes") do
|
||||
|
||||
@testset "Axes" begin
|
||||
p = plot()
|
||||
axis = p.subplots[1][:xaxis]
|
||||
@fact typeof(axis) --> Axis
|
||||
@fact Plots.discrete_value!(axis, "HI") --> (0.5, 1)
|
||||
@fact Plots.discrete_value!(axis, :yo) --> (1.5, 2)
|
||||
@fact extrema(axis) --> (0.5,1.5)
|
||||
@fact axis[:discrete_map] --> Dict{Any,Any}(:yo => 2, "HI" => 1)
|
||||
@test typeof(axis) == Plots.Axis
|
||||
@test Plots.discrete_value!(axis, "HI") == (0.5, 1)
|
||||
@test Plots.discrete_value!(axis, :yo) == (1.5, 2)
|
||||
@test Plots.ignorenan_extrema(axis) == (0.5,1.5)
|
||||
@test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1)
|
||||
|
||||
Plots.discrete_value!(axis, ["x$i" for i=1:5])
|
||||
Plots.discrete_value!(axis, ["x$i" for i=0:2])
|
||||
@fact extrema(axis) --> (0.5, 7.5)
|
||||
@test Plots.ignorenan_extrema(axis) == (0.5, 7.5)
|
||||
end
|
||||
|
||||
@testset "NoFail" begin
|
||||
histogram([1, 0, 0, 0, 0, 0])
|
||||
end
|
||||
|
||||
# tests for preprocessing recipes
|
||||
|
||||
# @testset "recipes" begin
|
||||
|
||||
# user recipe
|
||||
|
||||
# type T end
|
||||
# @recipe function f(::T)
|
||||
# line := (3,0.3,:red)
|
||||
# marker := (20,0.5,:blue,:o)
|
||||
# bg := :yellow
|
||||
# rand(10)
|
||||
# end
|
||||
# plot(T())
|
||||
|
||||
# plot recipe
|
||||
|
||||
# @recipe function f(::Type{Val{:hiplt}},plt::Plot)
|
||||
# line := (3,0.3,:red)
|
||||
# marker := (20,0.5,:blue,:o)
|
||||
# t := :path
|
||||
# bg:=:green
|
||||
# ()
|
||||
# end
|
||||
# plot(rand(10),t=:hiplt)
|
||||
|
||||
# series recipe
|
||||
|
||||
# @recipe function f(::Type{Val{:hi}},x,y,z)
|
||||
# line := (3,0.3,:red)
|
||||
# marker := (20,0.5,:blue,:o)
|
||||
# t := :path
|
||||
# ()
|
||||
# end
|
||||
# plot(rand(10),t=:hiplt)
|
||||
|
||||
# end
|
||||
|
||||
|
||||
|
||||
FactCheck.exitstatus()
|
||||
end # module
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import SnoopCompile
|
||||
|
||||
### Log the compiles
|
||||
# This only needs to be run once (to generate "/tmp/plots_compiles.csv")
|
||||
|
||||
# SnoopCompile.@snoop "/tmp/plots_compiles.csv" begin
|
||||
# include(joinpath(dirname(@__FILE__), "runtests.jl"))
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
### Parse the compiles and generate precompilation scripts
|
||||
# This can be run repeatedly to tweak the scripts
|
||||
|
||||
# IMPORTANT: we must have the module(s) defined for the parcelation
|
||||
# step, otherwise we will get no precompiles for the Plots module
|
||||
using Plots
|
||||
|
||||
data = SnoopCompile.read("/tmp/plots_compiles.csv")
|
||||
|
||||
# The Plots tests are run inside a module PlotsTest, so all
|
||||
# the precompiles get credited to PlotsTest. Credit them to Plots instead.
|
||||
subst = Dict("PlotsTests"=>"Plots")
|
||||
|
||||
# Blacklist helps fix problems:
|
||||
# - MIME uses type-parameters with symbols like :image/png, which is
|
||||
# not parseable
|
||||
blacklist = ["MIME"]
|
||||
|
||||
# Use these two lines if you want to create precompile functions for
|
||||
# individual packages
|
||||
pc, discards = SnoopCompile.parcel(data[end:-1:1,2], subst=subst, blacklist=blacklist)
|
||||
SnoopCompile.write("/tmp/precompile", pc)
|
||||
|
||||
pdir = joinpath(dirname(@__FILE__), "..")
|
||||
run(`cp /tmp/precompile/precompile_Plots.jl $pdir/src/precompile.jl`)
|
||||
+22
-15
@@ -1,27 +1,34 @@
|
||||
# Pkg.clone(pwd())
|
||||
# Pkg.build("Plots")
|
||||
using Pkg
|
||||
|
||||
Pkg.clone("ImageMagick")
|
||||
Pkg.add("ImageMagick")
|
||||
Pkg.build("ImageMagick")
|
||||
|
||||
Pkg.clone("GR")
|
||||
Pkg.build("GR")
|
||||
# Pkg.clone("GR")
|
||||
# Pkg.build("GR")
|
||||
|
||||
# Pkg.clone("https://github.com/JuliaPlots/RecipesBase.jl.git")
|
||||
# Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");
|
||||
Pkg.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git")
|
||||
Pkg.checkout("https://github.com/JuliaPlots/PlotReferenceImages.jl.git")
|
||||
|
||||
# Pkg.clone("https://github.com/JunoLab/Blink.jl.git")
|
||||
# Pkg.clone("https://github.com/JuliaStats/KernelDensity.jl.git")
|
||||
|
||||
# Pkg.add("StatPlots")
|
||||
Pkg.checkout("PlotUtils")
|
||||
Pkg.checkout("RecipesBase")
|
||||
|
||||
# Pkg.clone("Blink")
|
||||
# Pkg.build("Blink")
|
||||
# import Blink
|
||||
# Blink.AtomShell.install()
|
||||
# Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git")
|
||||
# Pkg.add("Rsvg")
|
||||
# Pkg.add("PlotlyJS")
|
||||
|
||||
Pkg.checkout("RecipesBase")
|
||||
Pkg.clone("VisualRegressionTests")
|
||||
# Pkg.checkout("RecipesBase")
|
||||
# Pkg.clone("VisualRegressionTests")
|
||||
|
||||
ENV["PYTHON"] = ""
|
||||
Pkg.add("PyPlot")
|
||||
Pkg.build("PyPlot")
|
||||
# need this to use Conda
|
||||
# ENV["PYTHON"] = ""
|
||||
# Pkg.add("PyPlot")
|
||||
# Pkg.build("PyPlot")
|
||||
|
||||
# Pkg.add("InspectDR")
|
||||
|
||||
Pkg.test("Plots"; coverage=false)
|
||||
|
||||
Reference in New Issue
Block a user