Compare commits
669 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00f3d450f9 | |||
| e676387c16 | |||
| 2e2e8a2230 | |||
| 8c8ede361e | |||
| 672bf05856 | |||
| 67ec31c75a | |||
| 59d13a7810 | |||
| 010a25f4cb | |||
| a0ac7b4a1e | |||
| ab7eb89466 | |||
| 649d362b85 | |||
| cb80860d6f | |||
| a8ef9e66fc | |||
| d27212e0b7 | |||
| 65650a7ae5 | |||
| 6ab5b8eb68 | |||
| c618e0577a | |||
| 0e84c91451 | |||
| 005ce11313 | |||
| db118d4f5a | |||
| c92b30a741 | |||
| d67aec6fec | |||
| ce58f25488 | |||
| e8d77a3c61 | |||
| d6d7fc1edd | |||
| 7ce7f8bfce | |||
| f0f46e5272 | |||
| f9473cc8bc | |||
| 5c7d31157b | |||
| 982a512ebd | |||
| 5dfd3d2f5d | |||
| 8d6974f67d | |||
| 8b569e3cd8 | |||
| b9d20142a2 | |||
| 6c9e818abd | |||
| d2a287d43c | |||
| d6ce2e364f | |||
| 36ca485a74 | |||
| b3230c5671 | |||
| ebf490838c | |||
| 7921cf18b6 | |||
| 0e0450ced5 | |||
| 096064ac09 | |||
| 77c2d7d846 | |||
| 40a121198d | |||
| 44079a7bf7 | |||
| 50fa013801 | |||
| d4ccf0ec62 | |||
| 04842974e7 | |||
| 6161b08f82 | |||
| b9818a4480 | |||
| e2ae22421b | |||
| b64677e14a | |||
| c910445634 | |||
| c4683a754e | |||
| 1da8fdb588 | |||
| 4bf8e342dc | |||
| 18fc50fcbf | |||
| 2fb10b7076 | |||
| 967291c2ef | |||
| 392ae871d5 | |||
| 27b3070480 | |||
| 325f59e03d | |||
| 217ffb36e6 | |||
| 4afad93ff6 | |||
| e8858cb1b7 | |||
| 52de31c9a6 | |||
| 60660a1f1a | |||
| a2ea9db90e | |||
| 236a7dadc6 | |||
| 82d2cc943d | |||
| d70f462899 | |||
| 0a636b5899 | |||
| e0b2749188 | |||
| 2fc973245d | |||
| 9bfcb64542 | |||
| 61e34b7ef8 | |||
| 3c3078875a | |||
| 5293e2c13d | |||
| fdf3b3f581 | |||
| f3f29fb54f | |||
| e8ed79a6bb | |||
| cca730d927 | |||
| 0693cb7840 | |||
| 3f1958cce1 | |||
| fb708bed90 | |||
| 1636587a82 | |||
| 1f1ccd81bb | |||
| 5efb0eb582 | |||
| be5f71c123 | |||
| a0df6bfc12 | |||
| 2ab0dc20d7 | |||
| 175ce3613a | |||
| eecb5c3754 | |||
| b34360901f | |||
| 21bbd21ae9 | |||
| 0b403a4c5d | |||
| 1d1e1beca5 | |||
| 3d4d844e72 | |||
| 1f9aa72bdb | |||
| c29f68e35d | |||
| c7c10e769d | |||
| 7264c01b85 | |||
| 97faa7413f | |||
| 7531c48e38 | |||
| 16799d8b75 | |||
| 901115ed10 | |||
| 596ca08446 | |||
| 4d864d8cad | |||
| c458a35670 | |||
| c203bfe5ad | |||
| 8b2a838388 | |||
| b08212e2f9 | |||
| b5f08838b3 | |||
| 5501605a1f | |||
| 7a5197df63 | |||
| 09d45aa64b | |||
| 552810359e | |||
| a68a6885d0 | |||
| b32589f7e7 | |||
| 2e6b8a067d | |||
| 581fdfdfe0 | |||
| 8e4ff39738 | |||
| f568803af2 | |||
| 6db06857ba | |||
| fd4dd13270 | |||
| ded9f332f6 | |||
| d09ca46141 | |||
| b28cd7f8bd | |||
| a4054aa500 | |||
| 3332bfcee1 | |||
| cc798a0c0a | |||
| fe2030b7e3 | |||
| ed1cce86ef | |||
| 9a544b0ff3 | |||
| 82f7b29836 | |||
| 076acfb242 | |||
| ed336bc0ff | |||
| 5699446d69 | |||
| a43d32949e | |||
| 02a1e648bc | |||
| 9d3e0651e2 | |||
| cdff5a9039 | |||
| ba0b5500ca | |||
| 1439a7e289 | |||
| 9761ede5c4 | |||
| e342805752 | |||
| d08672aa71 | |||
| a3d4e05b0e | |||
| 51eeed3d50 | |||
| 51c45456c3 | |||
| 074ba63bf5 | |||
| f3d552d8a0 | |||
| c5fb4a9228 | |||
| 55d7e910e4 | |||
| ea29af8d3d | |||
| 88b9d71bd7 | |||
| 165c84c246 | |||
| 347820867d | |||
| 26d0dfbf3d | |||
| 6aa4849266 | |||
| 335b3802b8 | |||
| 73bd3fa60c | |||
| 6b61c5900c | |||
| 9da4083096 | |||
| 86fe244d95 | |||
| 732f2846de | |||
| 4f238caf5c | |||
| c9388e9f56 | |||
| 2a9fa9539f | |||
| b8d136588c | |||
| 92c9e82dee | |||
| 0fdb48bda3 | |||
| e8d4fd7aac | |||
| 6a4a78a26a | |||
| 6c6769ca98 | |||
| 3e49af9093 | |||
| 5f8ecfa3fa | |||
| aca03d2609 | |||
| 5db9493d2b | |||
| ac3c041d6d | |||
| a319c0c94a | |||
| f74d619748 | |||
| eef1981f20 | |||
| ade9d86696 | |||
| 935c1cfeab | |||
| 5706770a11 | |||
| 2643417c0b | |||
| d388036601 | |||
| 96df75cc05 | |||
| c62d8d6753 | |||
| 1562c96d4c | |||
| 94c87d436f | |||
| b9c8ffb5cf | |||
| a37a23bcfc | |||
| 863ad22b48 | |||
| bce865ff9f | |||
| 1963fe208e | |||
| 5c4526725b | |||
| 3f42e9c5bd | |||
| b1c9d0a0af | |||
| adcbfb4b9e | |||
| b3ebccab36 | |||
| d5797054fb | |||
| aab0ec22b9 | |||
| f7babcc1d2 | |||
| d077307721 | |||
| 72e8e10bd6 | |||
| 5ea9e8a0d7 | |||
| 87b050fad2 | |||
| 0c8cd74e28 | |||
| 445d970784 | |||
| 125a1eb9fc | |||
| a2a578d04d | |||
| 11e5c0aa9d | |||
| 14b856b0cf | |||
| 2e8dd73822 | |||
| da9fdb356c | |||
| 054a99ca38 | |||
| baf33609af | |||
| b4eb0b6b41 | |||
| 3e9a47a683 | |||
| d71c3ecd86 | |||
| e5a864a760 | |||
| 81b59f9bc9 | |||
| 2c2a36ce34 | |||
| cb0020c629 | |||
| 003d39c8cd | |||
| ca866183f1 | |||
| b6e627369b | |||
| da96cfc158 | |||
| 0abbd53e07 | |||
| 6fc0f858c7 | |||
| b3613b68cd | |||
| 2b6619b3c4 | |||
| a33161aa9c | |||
| 5c3c5b999f | |||
| 7acca18313 | |||
| 7b71d9a448 | |||
| 64ffa3e70f | |||
| fd88972380 | |||
| dd7b09552a | |||
| 0b23aa9e7e | |||
| 0caa31bd6b | |||
| cafac135a0 | |||
| 6ac312bd49 | |||
| 489877d6bf | |||
| b0b2e695e1 | |||
| c3808c0c5b | |||
| c09545de64 | |||
| 7566b0159c | |||
| ed7cc9b89e | |||
| 55501e68fb | |||
| eabe670ae8 | |||
| 9fffef6f53 | |||
| 368429b757 | |||
| 49f774eb0d | |||
| 0487d7a058 | |||
| 5943e77563 | |||
| b10f0fb945 | |||
| c299891fb0 | |||
| fe0f22dfc0 | |||
| 002603c388 | |||
| 30ef8456c3 | |||
| bee1ce401a | |||
| e18c4a370a | |||
| 2a60a929f1 | |||
| 939295ab04 | |||
| f6a0ef6a61 | |||
| b3bce314e9 | |||
| e28b85d8b6 | |||
| a0af2fc997 | |||
| 7f7f5c6f12 | |||
| d21fbfc9be | |||
| 366dc1cca9 | |||
| e9e3913bb7 | |||
| 5491f6eff7 | |||
| 32e36ba324 | |||
| 9d4c456461 | |||
| cdc7920dff | |||
| cdb4901d91 | |||
| c7f3520ea8 | |||
| f0e728c316 | |||
| 4b1f127eaf | |||
| 1db2d8d8d8 | |||
| 87bb45d01b | |||
| 6beadcece8 | |||
| fc6f0f3a86 | |||
| ef7370f56d | |||
| 52ba7cdbff | |||
| b0c9dd44e9 | |||
| 92ec093ed9 | |||
| 6d2a1aafd2 | |||
| f3a7734584 | |||
| 9cf92f37f5 | |||
| 34831d42a4 | |||
| 607d5d7841 | |||
| 75adfd2e9d | |||
| e92b5b2d35 | |||
| a8c40a5493 | |||
| be5c3fb55c | |||
| e6e4a63022 | |||
| 1cdc8928ba | |||
| ad111a0163 | |||
| 7091a4899c | |||
| 3bd9e70fa9 | |||
| 54bd2c620e | |||
| 121640b1b3 | |||
| 55e388513c | |||
| b153dcbd63 | |||
| 242cbe5ecb | |||
| f1cca13796 | |||
| 918ba72107 | |||
| e596f27666 | |||
| a40d0000c5 | |||
| fb82da74a1 | |||
| b0991a0624 | |||
| 6236acea02 | |||
| 391c7e8570 | |||
| 58041cc64e | |||
| 8c7d3a6e92 | |||
| ae1982369c | |||
| 5f85ac01f7 | |||
| cca263fb2b | |||
| 809ed02b85 | |||
| 9eab1083ab | |||
| 60523bef80 | |||
| 46b959ff3c | |||
| 42c3827ded | |||
| 26e4b4efda | |||
| 2169ab3bb8 | |||
| 85dad8db88 | |||
| 8120c34064 | |||
| 1f1d1f70a1 | |||
| 373d868a17 | |||
| 834ba9bc61 | |||
| 3ffec5aed9 | |||
| ef20c1d684 | |||
| 697a59568c | |||
| e58fb935b0 | |||
| 044d23f8a5 | |||
| 1604d867c3 | |||
| 011db481c3 | |||
| c404cdc38a | |||
| 20689af7dd | |||
| 9ea0585d71 | |||
| 614c5f694b | |||
| fa36aae068 | |||
| 3e85232310 | |||
| 894928d7ca | |||
| afb1486f86 | |||
| 2799bd18e1 | |||
| 43c2a885b9 | |||
| be7feb0eaa | |||
| e3d653a8d9 | |||
| b9f9d8160d | |||
| 141b5c604d | |||
| 8a0fc00435 | |||
| a3a8cb9368 | |||
| 96c66b33a2 | |||
| 62fad3724f | |||
| ca2b6c87ef | |||
| 20348645a3 | |||
| a9b39fad64 | |||
| 1c556ade57 | |||
| e08957f5ec | |||
| e76ded69e6 | |||
| 7e740861d8 | |||
| f28e65a26c | |||
| a29f0c820b | |||
| b2bc30cfed | |||
| 6ce235bb68 | |||
| 479ab989df | |||
| 8d490b9e52 | |||
| 949f11a8c9 | |||
| d4896e1978 | |||
| c4020080b3 | |||
| 5a68003d16 | |||
| 9fcc3cdc24 | |||
| ee80ce5ae6 | |||
| 456c4019c4 | |||
| f65627a505 | |||
| 38d1fc15fe | |||
| 5eead5b0fa | |||
| 4223bd57f1 | |||
| 5bfa653858 | |||
| 5342764caf | |||
| 04a44a82c9 | |||
| c28820f39c | |||
| 546955a78b | |||
| d1e9cc5800 | |||
| 4ef2fb9933 | |||
| d7bc8adf21 | |||
| b5f44efc69 | |||
| 5e8abea222 | |||
| f291454ccb | |||
| bac6637b46 | |||
| 866ac9fb72 | |||
| 8208f6947f | |||
| dcd50a7c62 | |||
| 781aa86568 | |||
| 3facc116c8 | |||
| 9178c3b087 | |||
| 5c6c85a22f | |||
| b8adb4d229 | |||
| 093118da8b | |||
| 0aa760119f | |||
| 60b3a1583d | |||
| 593c47b938 | |||
| 0f6bb7ce6f | |||
| 048c2541e6 | |||
| f3e7d1a43b | |||
| 5df24d6683 | |||
| 73779898b7 | |||
| 7200b6adc6 | |||
| d48f15ab4d | |||
| 2747671935 | |||
| d86f24dd35 | |||
| 60edccade5 | |||
| 540460cae9 | |||
| df44d86f46 | |||
| c287b446c9 | |||
| 1db36c05a8 | |||
| 2c884ab5ab | |||
| cd0dfc956d | |||
| 03392a5f03 | |||
| a7da999bd7 | |||
| 47f21b1652 | |||
| 7c91d10c79 | |||
| fac2f5494b | |||
| bfcdb7e840 | |||
| 21d55db3b4 | |||
| 18e49888fe | |||
| 7ba2f61361 | |||
| 407df158ef | |||
| eab46ba9c4 | |||
| 39318e5e61 | |||
| ac1ce03fe5 | |||
| 4fa697ec75 | |||
| 027961bffa | |||
| 7dfcbabe0c | |||
| dfc7658426 | |||
| 04c6726b76 | |||
| 8fdbbe4c16 | |||
| 09fceab75b | |||
| 8df4c27285 | |||
| e38820b74a | |||
| c052e731f8 | |||
| 3636860262 | |||
| ce7421bef4 | |||
| 23b51c4c3e | |||
| 48d802fc40 | |||
| 51e8f3ff6e | |||
| 7917397bf3 | |||
| e0c0052b63 | |||
| 15a4b50ce4 | |||
| a51a8abf27 | |||
| 45747e3635 | |||
| 64b8e15918 | |||
| f4b716b255 | |||
| 1115ff83bb | |||
| c24550c756 | |||
| b5cdfe30ea | |||
| a10e594da1 | |||
| 63aee387f0 | |||
| ab9de1e199 | |||
| e60c463328 | |||
| 537d74196b | |||
| a8bbbe8582 | |||
| 29471a7aac | |||
| 894a4a5567 | |||
| f7cd5276f0 | |||
| 95b8c71083 | |||
| 52c82e6fe2 | |||
| cef1abd6aa | |||
| 91da261128 | |||
| 1c70346a61 | |||
| 2de017086d | |||
| d6253c72d4 | |||
| e5007b6c87 | |||
| bbc18549b3 | |||
| b6855b6fba | |||
| 70db97d576 | |||
| f632f2f39e | |||
| b23f968d57 | |||
| ea88877a11 | |||
| 1de3decede | |||
| 78045fd2ab | |||
| 7c8898158b | |||
| 3fc5f87388 | |||
| 623ca1effc | |||
| da05e7fe5e | |||
| f3734d4c64 | |||
| 9ab458e109 | |||
| d4c72e6f78 | |||
| 1f58ef5140 | |||
| 69d574c587 | |||
| a027fdded7 | |||
| 443df0501d | |||
| d68fe5f80d | |||
| ebf868bae3 | |||
| 8a9b3286e9 | |||
| a0455561b6 | |||
| 518ccf5a74 | |||
| 54f09c58cb | |||
| 029ad1045a | |||
| 2f55fb4d84 | |||
| add724eee9 | |||
| 07324ae892 | |||
| ffc05e971f | |||
| 30f42fd1d4 | |||
| f284857438 | |||
| 06ab514472 | |||
| 85b65acbe0 | |||
| 4d1d9de22f | |||
| a68ec9b191 | |||
| e386636d66 | |||
| cc3311b8eb | |||
| d3d1181769 | |||
| 001abf684c | |||
| 92a65296b5 | |||
| 3a7887d003 | |||
| 3334c4a87a | |||
| 228ce768e0 | |||
| eafc1a784c | |||
| fd3a4d0a0e | |||
| 77d679b63b | |||
| 730d764c6e | |||
| 6f1ef82e89 | |||
| d4da910f19 | |||
| a0a0773bd2 | |||
| 9ce8d138d5 | |||
| 0041b034e0 | |||
| d781c437de | |||
| 2fe21b8547 | |||
| dbc90c9417 | |||
| 431d60a58a | |||
| fcfcc9a94a | |||
| 870fb4e2df | |||
| e3612b973f | |||
| 75f4ca928f | |||
| 22fd25b30f | |||
| 610ddb09ff | |||
| 33602aca11 | |||
| a207856e99 | |||
| 80e8b5b5e4 | |||
| 273996aa91 | |||
| 646b88c5be | |||
| 88cfd82a25 | |||
| 2f8436be87 | |||
| a6237cdda3 | |||
| eb93a767fc | |||
| 41852412ca | |||
| 224b2b7323 | |||
| ec939f9f9a | |||
| eced15b712 | |||
| f8fb473981 | |||
| f2bcb77cfb | |||
| dc5da28176 | |||
| be43e64743 | |||
| 454630dace | |||
| c9df244333 | |||
| 2649d97d69 | |||
| bcb9ac5501 | |||
| 76d1e26f7d | |||
| ba13ab9926 | |||
| c43b34b67f | |||
| 3739c2881b | |||
| bc13b81351 | |||
| 86a56e487a | |||
| 031058cd0c | |||
| 85040d6104 | |||
| efbf74b44c | |||
| 774fc42e8e | |||
| 692cfe4f52 | |||
| 8b34d4417b | |||
| c20a7b3c69 | |||
| ff6e83c55e | |||
| 6aa56fd30f | |||
| f5a6f129dd | |||
| 5405c1d240 | |||
| 455f85e230 | |||
| fa899a67a7 | |||
| 56cf25eebf | |||
| 1a490551e6 | |||
| 4514802e35 | |||
| a9062b0611 | |||
| eb6b30aab1 | |||
| 922abd7ad3 | |||
| c4596dda75 | |||
| 168b77326b | |||
| f674bba861 | |||
| 6b2e65e738 | |||
| 51021c799d | |||
| 01cb036976 | |||
| ea7867429f | |||
| c5300cd7be | |||
| ea34c7d226 | |||
| 537e04989f | |||
| d447c5dc25 | |||
| 73944fc9bf | |||
| 01e994e0f1 | |||
| af3f3f436c | |||
| 56e90a266a | |||
| 1fc59ed522 | |||
| 3a3b1a6c17 | |||
| 242ce807df | |||
| 734967152c | |||
| 8023e15bb9 | |||
| ceb58a6fe1 | |||
| ae48bd1abb | |||
| d27fe2fbad | |||
| 970686efaf | |||
| 8df46ec0e9 | |||
| 864057a6ed | |||
| 92ca4cb1d1 | |||
| aabf811fe7 | |||
| 9b0a645a05 | |||
| 24b5e7ff09 | |||
| 48969ecee3 | |||
| 6ae0912e0e | |||
| e8062d544d | |||
| 07332aa4ac | |||
| 1e2b3559f2 | |||
| 8bfaa14edb | |||
| d4e87d3fe8 | |||
| 460348c0b6 | |||
| 264e8c735e | |||
| c4b04e2327 | |||
| dc1389bd1a | |||
| 51e38c80d8 | |||
| a33f5fabd1 | |||
| e6c8344970 | |||
| 2207ae6617 | |||
| cdc6707ad3 | |||
| 448fdaecbd | |||
| 4aa79aed6f | |||
| c8fcc373f8 | |||
| 1410141b46 | |||
| 76efabb5f3 | |||
| 36effc6dc4 | |||
| 8d5412a38f | |||
| 465b5d20b4 | |||
| 60a22b1897 | |||
| 09a685e977 | |||
| 31da90865c | |||
| 9ea98bcb56 | |||
| 4f9804d24e | |||
| 0a88518188 | |||
| ccae6373be | |||
| 31e5d07a45 | |||
| 056fee4b28 | |||
| 5a47864d73 | |||
| 1c7da432f1 | |||
| 3d3ceacaab | |||
| 35df39a44b | |||
| c23d6d62e7 | |||
| 243d4a7ac3 | |||
| 50254d06d7 | |||
| 475b46f516 | |||
| f4573f7e87 | |||
| 7fb1a4ec0f | |||
| 4299aa0ff8 | |||
| 4eb7e3810e | |||
| 3e7e7e9ac0 | |||
| 9e1058ec0a | |||
| 426a9eb6d3 | |||
| f322b9ac1c | |||
| e13fec08ca |
@@ -1,3 +1,6 @@
|
||||
*.jl.cov
|
||||
*.jl.*.cov
|
||||
*.jl.mem
|
||||
.DS_Store
|
||||
examples/.ipynb_checkpoints/*
|
||||
examples/meetup/.ipynb_checkpoints/*
|
||||
|
||||
@@ -5,9 +5,54 @@ os:
|
||||
- osx
|
||||
julia:
|
||||
- 0.4
|
||||
#- nightly
|
||||
|
||||
|
||||
# # before install:
|
||||
# # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
|
||||
# # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install wkhtmltopdf; fi
|
||||
|
||||
# ref: http://askubuntu.com/a/556672 for the wkhtmltopdf apt repository info
|
||||
|
||||
sudo: required
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./test/install_wkhtmltoimage.sh ; fi
|
||||
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:pov/wkhtmltopdf ; fi
|
||||
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y wkhtmltopdf ; fi
|
||||
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wkhtmltopdf -V ; fi
|
||||
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wkhtmltoimage -V ; fi
|
||||
# echo 'exec xvfb-run -a -s "-screen 0 640x480x16" wkhtmltopdf "$@"' | sudo tee /usr/local/bin/wkhtmltopdf.sh >/dev/null
|
||||
# sudo chmod a+x /usr/local/bin/wkhtmltopdf.sh
|
||||
|
||||
# # borrowed from Blink.jl's travis file
|
||||
# matrix:
|
||||
# include:
|
||||
# - os: linux
|
||||
# julia: 0.4
|
||||
# env: TESTCMD="xvfb-run julia"
|
||||
# - os: osx
|
||||
# julia: 0.4
|
||||
# env: TESTCMD="julia"
|
||||
|
||||
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
email: true
|
||||
# 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"); Pkg.add("Gadfly"); Pkg.test("Plots"; coverage=true)'
|
||||
- julia -e 'Pkg.clone(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")'
|
||||
# # - julia -e 'Pkg.clone("https://github.com/tbreloff/ImageMagick.jl.git"); Pkg.checkout("ImageMagick","tb_write"); Pkg.build("ImageMagick")'
|
||||
# - julia -e 'Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");'
|
||||
# # - julia -e 'Pkg.clone("https://github.com/JunoLab/Blink.jl.git"); Pkg.build("Blink"); import Blink; Blink.AtomShell.install()'
|
||||
# # - julia -e 'Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git")'
|
||||
# - julia -e 'ENV["PYTHON"] = ""; Pkg.add("PyPlot"); Pkg.build("PyPlot")'
|
||||
#
|
||||
# # - $TESTCMD -e 'Pkg.test("Plots"; coverage=false)'
|
||||
# - julia -e 'Pkg.test("Plots"; coverage=false)'
|
||||
# # - julia -e 'cd(Pkg.dir("Plots")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder()); Codecov.submit(process_folder())'
|
||||
|
||||
@@ -1,224 +1,35 @@
|
||||
# Plots
|
||||
|
||||
[](https://travis-ci.org/tbreloff/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) -->
|
||||
|
||||
Plotting interface and wrapper for several plotting packages.
|
||||
#### Author: Thomas Breloff (@tbreloff)
|
||||
|
||||
Please add wishlist items, bugs, or any other comments/questions to the issues list.
|
||||
Plots is a plotting API and toolset. My goals with the package are:
|
||||
|
||||
## Examples
|
||||
- **Powerful**. Do more with less. Complex visualizations become easy.
|
||||
- **Intuitive**. Start generating plots without reading volumes of 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.
|
||||
|
||||
- [Qwt.jl](docs/qwt_examples.md)
|
||||
- [Gadfly.jl](docs/gadfly_examples.md)
|
||||
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.
|
||||
|
||||
## Installation
|
||||
|
||||
First, clone the package, and get any plotting packages you need:
|
||||
|
||||
```
|
||||
Pkg.clone("https://github.com/tbreloff/Plots.jl.git")
|
||||
Pkg.add("Gadfly") # [optional]
|
||||
Pkg.clone("https://github.com/tbreloff/Qwt.jl.git") # [optional] requires pyqt and pyqwt
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
Load it in. The underlying plotting backends are not imported until `plotter()` is called (which happens
|
||||
on your first call to `plot`). This means that you don't need any backends to be installed when you call `using Plots`.
|
||||
For now, the default backend is Gadfly.
|
||||
|
||||
```
|
||||
```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
|
||||
```
|
||||
|
||||
Do a plot in Gadfly, then save a png:
|
||||
|
||||
```
|
||||
plot(rand(10,2); marker = :rect)
|
||||
savepng(Plots.IMG_DIR * "gadfly1.png")
|
||||
```
|
||||
|
||||
which saves:
|
||||
|
||||

|
||||
|
||||
|
||||
Do a plot in Qwt, then save a png:
|
||||
|
||||
```
|
||||
plotter!(:qwt) # switches the backend to Qwt
|
||||
plot(rand(10,2); marker = :rect)
|
||||
savepng(Plots.IMG_DIR * "qwt1.png")
|
||||
```
|
||||
|
||||
which saves:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## plot and plotter! interface (WIP)
|
||||
|
||||
The main plot command. Call `plotter!(:module)` to set the current plotting backend.
|
||||
Commands are converted into the relevant plotting commands for that package:
|
||||
|
||||
```
|
||||
plotter!(:gadfly)
|
||||
plot(1:10) # this effectively calls `y = 1:10; Gadfly.plot(x=1:length(y), y=y)`
|
||||
plotter!(:qwt)
|
||||
plot(1:10) # this effectively calls `Qwt.plot(1:10)`
|
||||
```
|
||||
|
||||
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 `currentPlot`
|
||||
plot!(args...; kw...) # adds to the `currentPlot`
|
||||
plot!(plotobj, args...; kw...) # adds to the plot `plotobj`
|
||||
```
|
||||
|
||||
Now that you know which plot object you're updating (new, current, or other), I'll leave it off for simplicity.
|
||||
Here are some various args to supply, and the implicit mapping (AVec == AbstractVector and AMat == AbstractMatrix):
|
||||
|
||||
```
|
||||
plot(y::AVec; kw...) # one line... x = 1:length(y)
|
||||
plot(x::AVec, y::AVec; kw...) # one line (will assert length(x) == length(y))
|
||||
plot(y::AMat; kw...) # multiple lines (one per column of x), all sharing x = 1:size(y,1)
|
||||
plot(x::AVec, y::AMat; kw...) # multiple lines (one per column of x), all sharing x (will assert length(x) == size(y,1))
|
||||
plot(x::AMat, y::AMat; kw...) # multiple lines (one per column of x/y... will assert size(x) == size(y))
|
||||
plot(x::AVec, f::Function; kw...) # one line, y = f(x)
|
||||
plot(x::AMat, f::Function; kw...) # multiple lines, yᵢⱼ = f(xᵢⱼ)
|
||||
plot(x::AVec, fs::AVec{Function}; kw...) # multiple lines, yᵢⱼ = fⱼ(xᵢ)
|
||||
plot(y::AVec{AVec}; kw...) # multiple lines, each with x = 1:length(y[i])
|
||||
plot(x::AVec, y::AVec{AVec}; kw...) # multiple lines, will assert length(x) == length(y[i])
|
||||
plot(x::AVec{AVec}, y::AVec{AVec}; kw...) # multiple lines, will assert length(x[i]) == length(y[i])
|
||||
plot(n::Integer; kw...) # n lines, all empty (for updating plots)
|
||||
|
||||
# TODO: how do we handle NA values in dataframes?
|
||||
plot(df::DataFrame; kw...) # one line per DataFrame column, labels == names(df)
|
||||
plot(df::DataFrame, columns; kw...) # one line per column, but on a subset of column names
|
||||
```
|
||||
|
||||
[TODO] You can swap out `plot` for `subplot`. Each line will go into a separate plot. Use the layout keyword:
|
||||
|
||||
```
|
||||
y = rand(100,3)
|
||||
subplot(y; layout=(2,2), kw...) # creates 3 lines going into 3 separate plots, laid out on a 2x2 grid (last row is filled with plot #3)
|
||||
subplot(y; layout=(1,3), kw...) # again 3 plots, all in the same row
|
||||
subplot(y; layout=[1,[2,3]]) # pass a nested Array to fully specify the layout. here the first plot will take up the first row,
|
||||
# and the others will share the second row
|
||||
```
|
||||
|
||||
Shorthands:
|
||||
|
||||
```
|
||||
scatter(args...; kw...) = plot(args...; kw..., linetype = :none, marker = :hexagon)
|
||||
scatter!(args...; kw...) = plot!(args...; kw..., linetype = :none, marker = :hexagon)
|
||||
bar(args...; kw...) = plot(args...; kw..., linetype = :bar)
|
||||
bar!(args...; kw...) = plot!(args...; kw..., linetype = :bar)
|
||||
histogram(args...; kw...) = plot(args...; kw..., linetype = :hist)
|
||||
histogram!(args...; kw...) = plot!(args...; kw..., linetype = :hist)
|
||||
heatmap(args...; kw...) = plot(args...; kw..., linetype = :heatmap)
|
||||
heatmap!(args...; kw...) = plot!(args...; kw..., linetype = :heatmap)
|
||||
```
|
||||
|
||||
Some keyword arguments you can set:
|
||||
|
||||
```
|
||||
axis # :left or :right
|
||||
color # can be a string ("red") or a symbol (:red) or a ColorsTypes.jl Colorant (RGB(1,0,0)) or :auto (which lets the package pick)
|
||||
label # string or symbol, applies to that line, may go in a legend
|
||||
width # width of a line
|
||||
linetype # :line, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar
|
||||
linestyle # :solid, :dash, :dot, :dashdot, :dashdotdot
|
||||
marker # :none, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :star2, :hexagon
|
||||
markercolor # same choices as `color`, or :match will set the color to be the same as `color`
|
||||
markersize # size of the marker
|
||||
nbins # number of bins for heatmap/hexbin and histograms
|
||||
heatmap_c # color cutoffs for Qwt heatmaps
|
||||
fillto # fillto value for area plots
|
||||
title # string or symbol, title of the plot
|
||||
xlabel # string or symbol, label on the bottom (x) axis
|
||||
ylabel # string or symbol, label on the left (y) axis
|
||||
yrightlabel # string or symbol, label on the right (y) axis
|
||||
reg # true or false, add a regression line for each line
|
||||
size # (Int,Int), resize the enclosing window
|
||||
pos # (Int,Int), move the enclosing window to this position
|
||||
windowtitle # string or symbol, set the title of the enclosing windowtitle
|
||||
screen # Integer, move enclosing window to this screen number (for multiscreen desktops)
|
||||
show # true or false, show the plot (in case you don't want the window to pop up right away)
|
||||
```
|
||||
|
||||
If you don't include a keyword argument, these are the defaults:
|
||||
|
||||
```
|
||||
axis = :left
|
||||
color = :auto
|
||||
label = automatically generated (y1, y2, ...., or y1 (R), y2 (R) for the right axis)
|
||||
width = 1
|
||||
linetype = :line
|
||||
linestype = :solid
|
||||
marker = :none
|
||||
markercolor = :match
|
||||
markersize = 3
|
||||
nbins = 100
|
||||
heatmap_c = (0.15, 0.5)
|
||||
fillto = nothing
|
||||
title = ""
|
||||
xlabel = ""
|
||||
ylabel = ""
|
||||
yrightlabel = ""
|
||||
reg = false
|
||||
size = (800,600)
|
||||
pos = (0,0)
|
||||
windowtitle = ""
|
||||
screen = 1
|
||||
show = true
|
||||
```
|
||||
|
||||
When plotting multiple lines, you can give every line the same trait by using the singular, or add an "s" to pluralize.
|
||||
(yes I know it's not gramatically correct, but it's easy to use and implement)
|
||||
|
||||
```
|
||||
plot(rand(100,2); colors = [:red, RGB(.5,.5,0)], axiss = [:left, :right], width = 5) # note the width=5 is applied to both lines
|
||||
```
|
||||
|
||||
# TODO
|
||||
|
||||
- [x] Plot vectors/matrices/functions
|
||||
- [ ] Plot DataFrames
|
||||
- [ ] Subplots
|
||||
- [ ] Histograms
|
||||
- [ ] 3D plotting
|
||||
- [ ] Scenes/Drawing
|
||||
- [ ] Graphs
|
||||
- [ ] Interactivity (GUIs)
|
||||
- [x] Gadfly.jl
|
||||
- [ ] PyPlot.jl
|
||||
- [ ] Winston.jl
|
||||
- [ ] Gaston.jl
|
||||
- [ ] GLPlot.jl
|
||||
- [x] Qwt.jl
|
||||
- [ ] Bokeh.jl
|
||||
- [ ] Plotly.jl
|
||||
- [ ] GoogleCharts.jl
|
||||
- [ ] Vega.jl
|
||||
- [ ] PLplot.jl
|
||||
- [ ] TextPlots.jl
|
||||
- [ ] ASCIIPlots.jl
|
||||
- [ ] Sparklines.jl
|
||||
- [ ] UnicodePlots.jl
|
||||
- [ ] Hinton.jl
|
||||
- [ ] ImageTerm.jl
|
||||
- [ ] GraphViz.jl
|
||||
- [ ] TikzGraphs.jl
|
||||
- [ ] GraphLayout.jl
|
||||
|
||||
# Backends
|
||||
|
||||
See the wiki at: https://github.com/JuliaPlot/juliaplot_docs/wiki
|
||||
|
||||
# Author
|
||||
|
||||
Thomas Breloff (@tbreloff)
|
||||

|
||||
|
||||
View the [full documentation](http://plots.readthedocs.io).
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
julia 0.4-
|
||||
julia 0.4
|
||||
|
||||
Colors
|
||||
FactCheck
|
||||
Reexport
|
||||
Compat
|
||||
Requires
|
||||
FixedSizeArrays
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
module PlotExamples
|
||||
|
||||
using Plots
|
||||
using Colors
|
||||
|
||||
const DOCDIR = Pkg.dir("Plots") * "/docs"
|
||||
const IMGDIR = Pkg.dir("Plots") * "/img"
|
||||
|
||||
doc"""
|
||||
Holds all data needed for a documentation example... header, description, and plotting expression (Expr)
|
||||
"""
|
||||
type PlotExample
|
||||
header::String
|
||||
desc::String
|
||||
exprs::Vector{Expr}
|
||||
end
|
||||
|
||||
|
||||
# the examples we'll run for each
|
||||
const examples = PlotExample[
|
||||
PlotExample("Lines",
|
||||
"A simple line plot of the 3 columns.",
|
||||
[:(plot(rand(100,3)))]),
|
||||
PlotExample("Functions",
|
||||
"Plot multiple functions",
|
||||
[:(plot(0:0.01:4π, [sin,cos]))]),
|
||||
PlotExample("Global",
|
||||
"Change the guides/background without a separate call.",
|
||||
[:(plot(rand(10); title="TITLE", xlabel="XLABEL", ylabel="YLABEL", background_color = RGB(0.5,0.5,0.5)))]),
|
||||
PlotExample("Two-axis",
|
||||
"Use the `axis` or `axiss` arguments.\n\nNote: This is only supported with Qwt right now",
|
||||
[:(plot(Vector[randn(100), randn(100)*100]; axiss = [:left,:right]))]),
|
||||
PlotExample("Vectors w/ pluralized args",
|
||||
"Plot multiple series with different numbers of points. Mix arguments that apply to all series (singular... see `marker`) with arguments unique to each series (pluralized... see `colors`).",
|
||||
[:(plot(Vector[rand(10), rand(20)]; marker=:ellipse, markersize=8, colors=[:red,:blue]))]),
|
||||
PlotExample("Build plot in pieces",
|
||||
"Start with a base plot...",
|
||||
[:(plot(rand(100)/3; reg=true, fillto=0))]),
|
||||
PlotExample("",
|
||||
"and add to it later.",
|
||||
[:(scatter!(rand(100); markersize=6, color=:blue))]),
|
||||
PlotExample("Heatmaps",
|
||||
"",
|
||||
[:(heatmap(randn(10000),randn(10000); nbins=200))]),
|
||||
PlotExample("Lots of line types",
|
||||
"Options: (:line, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar) \nNote: some may not work with all backends",
|
||||
[:(plot(rand(20,4); linetypes=[:line, :step, :sticks, :dots]))]),
|
||||
PlotExample("Bar",
|
||||
"x is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)",
|
||||
[:(bar(randn(1000)))]),
|
||||
PlotExample("Histogram",
|
||||
"note: fillto isn't supported on all backends",
|
||||
[:(histogram(randn(1000); nbins=50, fillto=20))]),
|
||||
PlotExample("Subplots",
|
||||
"""
|
||||
subplot and subplot! are distinct commands which create many plots and add series to them in a circular fashion.
|
||||
You can define the layout with keyword params... either set the number of plots `n` (and optionally number of rows `nr` or
|
||||
number of columns `nc`), or you can set the layout directly with `layout`.
|
||||
|
||||
Note: Gadfly is not very friendly here, and although you can create a plot and save a PNG, I haven't been able to actually display it.
|
||||
""",
|
||||
[:(subplot(randn(100,5); layout=[1,1,3], linetypes=[:line,:hist,:dots,:step,:bar], nbins=10, legend=false))]),
|
||||
PlotExample("Adding to subplots",
|
||||
"Note here the automatic grid layout, as well as the order in which new series are added to the plots.",
|
||||
[:(subplot(randn(100,5); n=4))]),
|
||||
PlotExample("",
|
||||
"",
|
||||
[:(subplot!(randn(100,3)))]),
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
function generate_markdown(pkgname::Symbol)
|
||||
|
||||
# set up the plotter, and don't show the plots by default
|
||||
plotter!(pkgname)
|
||||
plotDefault!(:show, false)
|
||||
|
||||
# open the markdown file
|
||||
md = open("$DOCDIR/$(pkgname)_examples.md", "w")
|
||||
|
||||
for (i,example) in enumerate(examples)
|
||||
|
||||
try
|
||||
|
||||
# run the code
|
||||
map(eval, example.exprs)
|
||||
|
||||
# save the png
|
||||
imgname = "$(pkgname)_example_$i.png"
|
||||
savepng("$IMGDIR/$imgname")
|
||||
|
||||
# write out the header, description, code block, and image link
|
||||
write(md, "### $(example.header)\n\n")
|
||||
write(md, "$(example.desc)\n\n")
|
||||
write(md, "```julia\n$(join(map(string, example.exprs), "\n"))\n```\n\n")
|
||||
write(md, "\n\n")
|
||||
|
||||
catch ex
|
||||
# TODO: put error info into markdown?
|
||||
warn("Example $pkgname:$i failed with: $ex")
|
||||
end
|
||||
|
||||
#
|
||||
end
|
||||
|
||||
close(md)
|
||||
|
||||
end
|
||||
|
||||
# run it!
|
||||
map(generate_markdown, (:qwt, :gadfly))
|
||||
|
||||
|
||||
end # module
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
### Lines
|
||||
|
||||
A simple line plot of the 3 columns.
|
||||
|
||||
```julia
|
||||
plot(rand(100,3))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Functions
|
||||
|
||||
Plot multiple functions
|
||||
|
||||
```julia
|
||||
plot(0:0.01:4π,[sin,cos])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Global
|
||||
|
||||
Change the guides/background without a separate call.
|
||||
|
||||
```julia
|
||||
plot(rand(10); title="TITLE",xlabel="XLABEL",ylabel="YLABEL",background_color=RGB(0.5,0.5,0.5))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Two-axis
|
||||
|
||||
Use the `axis` or `axiss` arguments.
|
||||
|
||||
Note: This is only supported with Qwt right now
|
||||
|
||||
```julia
|
||||
plot(Vector[randn(100),randn(100) * 100]; axiss=[:left,:right])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Vectors w/ pluralized args
|
||||
|
||||
Plot multiple series with different numbers of points. Mix arguments that apply to all series (singular... see `marker`) with arguments unique to each series (pluralized... see `colors`).
|
||||
|
||||
```julia
|
||||
plot(Vector[rand(10),rand(20)]; marker=:ellipse,markersize=8,colors=[:red,:blue])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Build plot in pieces
|
||||
|
||||
Start with a base plot...
|
||||
|
||||
```julia
|
||||
plot(rand(100) / 3; reg=true,fillto=0)
|
||||
```
|
||||
|
||||

|
||||
|
||||
###
|
||||
|
||||
and add to it later.
|
||||
|
||||
```julia
|
||||
scatter!(rand(100); markersize=6,color=:blue)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Heatmaps
|
||||
|
||||
|
||||
|
||||
```julia
|
||||
heatmap(randn(10000),randn(10000); nbins=200)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Lots of line types
|
||||
|
||||
Options: (:line, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar)
|
||||
Note: some may not work with all backends
|
||||
|
||||
```julia
|
||||
plot(rand(20,4); linetypes=[:line,:step,:sticks,:dots])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Bar
|
||||
|
||||
x is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)
|
||||
|
||||
```julia
|
||||
bar(randn(1000))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Histogram
|
||||
|
||||
note: fillto isn't supported on all backends
|
||||
|
||||
```julia
|
||||
histogram(randn(1000); nbins=50,fillto=20)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Subplots
|
||||
|
||||
subplot and subplot! are distinct commands which create many plots and add series to them in a circular fashion.
|
||||
You can define the layout with keyword params... either set the number of plots `n` (and optionally number of rows `nr` or
|
||||
number of columns `nc`), or you can set the layout directly with `layout`.
|
||||
|
||||
Note: Gadfly is not very friendly here, and although you can create a plot and save a PNG, I haven't been able to actually display it.
|
||||
|
||||
|
||||
```julia
|
||||
subplot(randn(100,5); layout=[1,1,3],linetypes=[:line,:hist,:dots,:step,:bar],nbins=10,legend=false)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Adding to subplots
|
||||
|
||||
Note here the automatic grid layout, as well as the order in which new series are added to the plots.
|
||||
|
||||
```julia
|
||||
subplot(randn(100,5); n=4)
|
||||
```
|
||||
|
||||

|
||||
|
||||
###
|
||||
|
||||
|
||||
|
||||
```julia
|
||||
subplot!(randn(100,3))
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
### Lines
|
||||
|
||||
A simple line plot of the 3 columns.
|
||||
|
||||
```julia
|
||||
plot(rand(100,3))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Functions
|
||||
|
||||
Plot multiple functions
|
||||
|
||||
```julia
|
||||
plot(0:0.01:4π,[sin,cos])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Global
|
||||
|
||||
Change the guides/background without a separate call.
|
||||
|
||||
```julia
|
||||
plot(rand(10); title="TITLE",xlabel="XLABEL",ylabel="YLABEL",background_color=RGB(0.5,0.5,0.5))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Two-axis
|
||||
|
||||
Use the `axis` or `axiss` arguments.
|
||||
|
||||
Note: This is only supported with Qwt right now
|
||||
|
||||
```julia
|
||||
plot(Vector[randn(100),randn(100) * 100]; axiss=[:left,:right])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Vectors w/ pluralized args
|
||||
|
||||
Plot multiple series with different numbers of points. Mix arguments that apply to all series (singular... see `marker`) with arguments unique to each series (pluralized... see `colors`).
|
||||
|
||||
```julia
|
||||
plot(Vector[rand(10),rand(20)]; marker=:ellipse,markersize=8,colors=[:red,:blue])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Build plot in pieces
|
||||
|
||||
Start with a base plot...
|
||||
|
||||
```julia
|
||||
plot(rand(100) / 3; reg=true,fillto=0)
|
||||
```
|
||||
|
||||

|
||||
|
||||
###
|
||||
|
||||
and add to it later.
|
||||
|
||||
```julia
|
||||
scatter!(rand(100); markersize=6,color=:blue)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Heatmaps
|
||||
|
||||
|
||||
|
||||
```julia
|
||||
heatmap(randn(10000),randn(10000); nbins=200)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Lots of line types
|
||||
|
||||
Options: (:line, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar)
|
||||
Note: some may not work with all backends
|
||||
|
||||
```julia
|
||||
plot(rand(20,4); linetypes=[:line,:step,:sticks,:dots])
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Bar
|
||||
|
||||
x is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)
|
||||
|
||||
```julia
|
||||
bar(randn(1000))
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Histogram
|
||||
|
||||
note: fillto isn't supported on all backends
|
||||
|
||||
```julia
|
||||
histogram(randn(1000); nbins=50,fillto=20)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Subplots
|
||||
|
||||
subplot and subplot! are distinct commands which create many plots and add series to them in a circular fashion.
|
||||
You can define the layout with keyword params... either set the number of plots `n` (and optionally number of rows `nr` or
|
||||
number of columns `nc`), or you can set the layout directly with `layout`.
|
||||
|
||||
Note: Gadfly is not very friendly here, and although you can create a plot and save a PNG, I haven't been able to actually display it.
|
||||
|
||||
|
||||
```julia
|
||||
subplot(randn(100,5); layout=[1,1,3],linetypes=[:line,:hist,:dots,:step,:bar],nbins=10,legend=false)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Adding to subplots
|
||||
|
||||
Note here the automatic grid layout, as well as the order in which new series are added to the plots.
|
||||
|
||||
```julia
|
||||
subplot(randn(100,5); n=4)
|
||||
```
|
||||
|
||||

|
||||
|
||||
###
|
||||
|
||||
|
||||
|
||||
```julia
|
||||
subplot!(randn(100,3))
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,4 @@
|
||||
git checkout master
|
||||
git merge --ff-only dev
|
||||
git push origin master
|
||||
git checkout dev
|
||||
@@ -1,76 +1,288 @@
|
||||
__precompile__()
|
||||
|
||||
__precompile__()
|
||||
|
||||
module Plots
|
||||
|
||||
using Colors
|
||||
using Compat
|
||||
using Reexport
|
||||
@reexport using Colors
|
||||
using Requires
|
||||
using FixedSizeArrays
|
||||
|
||||
export
|
||||
plotter,
|
||||
plot,
|
||||
subplot,
|
||||
AbstractPlot,
|
||||
Plot,
|
||||
Subplot,
|
||||
SubplotLayout,
|
||||
GridLayout,
|
||||
RowsLayout,
|
||||
FlexLayout,
|
||||
AVec,
|
||||
AMat,
|
||||
KW,
|
||||
|
||||
plotter!,
|
||||
plot!,
|
||||
subplot!,
|
||||
wrap,
|
||||
set_theme,
|
||||
add_theme,
|
||||
|
||||
currentPlot,
|
||||
plotDefault,
|
||||
scatter,
|
||||
bar,
|
||||
histogram,
|
||||
heatmap,
|
||||
plot,
|
||||
plot!,
|
||||
subplot,
|
||||
subplot!,
|
||||
|
||||
currentPlot!,
|
||||
plotDefault!,
|
||||
scatter!,
|
||||
bar!,
|
||||
histogram!,
|
||||
heatmap!,
|
||||
current,
|
||||
default,
|
||||
with,
|
||||
|
||||
savepng
|
||||
scatter,
|
||||
scatter!,
|
||||
bar,
|
||||
bar!,
|
||||
barh,
|
||||
barh!,
|
||||
histogram,
|
||||
histogram!,
|
||||
histogram2d,
|
||||
histogram2d!,
|
||||
density,
|
||||
density!,
|
||||
heatmap,
|
||||
heatmap!,
|
||||
hexbin,
|
||||
hexbin!,
|
||||
sticks,
|
||||
sticks!,
|
||||
hline,
|
||||
hline!,
|
||||
vline,
|
||||
vline!,
|
||||
ohlc,
|
||||
ohlc!,
|
||||
pie,
|
||||
pie!,
|
||||
contour,
|
||||
contour!,
|
||||
contour3d,
|
||||
contour3d!,
|
||||
surface,
|
||||
surface!,
|
||||
wireframe,
|
||||
wireframe!,
|
||||
path3d,
|
||||
path3d!,
|
||||
plot3d,
|
||||
plot3d!,
|
||||
scatter3d,
|
||||
scatter3d!,
|
||||
abline!,
|
||||
boxplot,
|
||||
boxplot!,
|
||||
violin,
|
||||
violin!,
|
||||
quiver,
|
||||
quiver!,
|
||||
|
||||
# ---------------------------------------------------------
|
||||
title!,
|
||||
xlabel!,
|
||||
ylabel!,
|
||||
xlims!,
|
||||
ylims!,
|
||||
zlims!,
|
||||
xticks!,
|
||||
yticks!,
|
||||
annotate!,
|
||||
xflip!,
|
||||
yflip!,
|
||||
xaxis!,
|
||||
yaxis!,
|
||||
|
||||
savefig,
|
||||
png,
|
||||
gui,
|
||||
|
||||
const IMG_DIR = Pkg.dir("Plots") * "/img/"
|
||||
backend,
|
||||
backends,
|
||||
backend_name,
|
||||
aliases,
|
||||
dataframes,
|
||||
|
||||
Shape,
|
||||
text,
|
||||
font,
|
||||
stroke,
|
||||
brush,
|
||||
Surface,
|
||||
OHLC,
|
||||
|
||||
colorscheme,
|
||||
ColorScheme,
|
||||
ColorGradient,
|
||||
ColorVector,
|
||||
ColorWrapper,
|
||||
ColorFunction,
|
||||
ColorZFunction,
|
||||
getColor,
|
||||
getColorZ,
|
||||
|
||||
debugplots,
|
||||
|
||||
supportedArgs,
|
||||
supportedAxes,
|
||||
supportedTypes,
|
||||
supportedStyles,
|
||||
supportedMarkers,
|
||||
subplotSupported,
|
||||
|
||||
Animation,
|
||||
frame,
|
||||
gif,
|
||||
@animate,
|
||||
@gif,
|
||||
|
||||
PlotRecipe,
|
||||
spy,
|
||||
arcdiagram,
|
||||
chorddiagram,
|
||||
|
||||
translate,
|
||||
translate!,
|
||||
rotate,
|
||||
rotate!,
|
||||
center,
|
||||
P2,
|
||||
P3,
|
||||
BezierCurve,
|
||||
curve_points,
|
||||
directed_curve
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
include("types.jl")
|
||||
include("utils.jl")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
include("qwt.jl")
|
||||
include("gadfly.jl")
|
||||
include("plotter.jl")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
include("colors.jl")
|
||||
include("components.jl")
|
||||
include("backends.jl")
|
||||
include("args.jl")
|
||||
include("plot.jl")
|
||||
include("series_args.jl")
|
||||
include("subplot.jl")
|
||||
include("layouts.jl")
|
||||
include("recipes.jl")
|
||||
include("animation.jl")
|
||||
include("output.jl")
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
scatter(args...; kw...) = plot(args...; kw..., linetype = :none, marker = :hexagon)
|
||||
scatter!(args...; kw...) = plot!(args...; kw..., linetype = :none, marker = :hexagon)
|
||||
scatter(args...; kw...) = plot(args...; kw..., linetype = :scatter)
|
||||
scatter!(args...; kw...) = plot!(args...; kw..., linetype = :scatter)
|
||||
bar(args...; kw...) = plot(args...; kw..., linetype = :bar)
|
||||
bar!(args...; kw...) = plot!(args...; kw..., linetype = :bar)
|
||||
barh(args...; kw...) = plot(args...; kw..., linetype = :barh, orientation = :h)
|
||||
barh!(args...; kw...) = plot!(args...; kw..., linetype = :barh, orientation = :h)
|
||||
histogram(args...; kw...) = plot(args...; kw..., linetype = :hist)
|
||||
histogram!(args...; kw...) = plot!(args...; kw..., linetype = :hist)
|
||||
histogram2d(args...; kw...) = plot(args...; kw..., linetype = :hist2d)
|
||||
histogram2d!(args...; kw...) = plot!(args...; kw..., linetype = :hist2d)
|
||||
density(args...; kw...) = plot(args...; kw..., linetype = :density)
|
||||
density!(args...; kw...) = plot!(args...; kw..., linetype = :density)
|
||||
heatmap(args...; kw...) = plot(args...; kw..., linetype = :heatmap)
|
||||
heatmap!(args...; kw...) = plot!(args...; kw..., linetype = :heatmap)
|
||||
hexbin(args...; kw...) = plot(args...; kw..., linetype = :hexbin)
|
||||
hexbin!(args...; kw...) = plot!(args...; kw..., linetype = :hexbin)
|
||||
sticks(args...; kw...) = plot(args...; kw..., linetype = :sticks, marker = :ellipse)
|
||||
sticks!(args...; kw...) = plot!(args...; kw..., linetype = :sticks, marker = :ellipse)
|
||||
hline(args...; kw...) = plot(args...; kw..., linetype = :hline)
|
||||
hline!(args...; kw...) = plot!(args...; kw..., linetype = :hline)
|
||||
vline(args...; kw...) = plot(args...; kw..., linetype = :vline)
|
||||
vline!(args...; kw...) = plot!(args...; kw..., linetype = :vline)
|
||||
ohlc(args...; kw...) = plot(args...; kw..., linetype = :ohlc)
|
||||
ohlc!(args...; kw...) = plot!(args...; kw..., linetype = :ohlc)
|
||||
pie(args...; kw...) = plot(args...; kw..., linetype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
|
||||
pie!(args...; kw...) = plot!(args...; kw..., linetype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
|
||||
contour(args...; kw...) = plot(args...; kw..., linetype = :contour)
|
||||
contour!(args...; kw...) = plot!(args...; kw..., linetype = :contour)
|
||||
contour3d(args...; kw...) = plot(args...; kw..., linetype = :contour3d)
|
||||
contour3d!(args...; kw...) = plot!(args...; kw..., linetype = :contour3d)
|
||||
surface(args...; kw...) = plot(args...; kw..., linetype = :surface)
|
||||
surface!(args...; kw...) = plot!(args...; kw..., linetype = :surface)
|
||||
wireframe(args...; kw...) = plot(args...; kw..., linetype = :wireframe)
|
||||
wireframe!(args...; kw...) = plot!(args...; kw..., linetype = :wireframe)
|
||||
path3d(args...; kw...) = plot(args...; kw..., linetype = :path3d)
|
||||
path3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d)
|
||||
plot3d(args...; kw...) = plot(args...; kw..., linetype = :path3d)
|
||||
plot3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d)
|
||||
scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d)
|
||||
scatter3d!(args...; kw...) = plot!(args...; kw..., linetype = :scatter3d)
|
||||
boxplot(args...; kw...) = plot(args...; kw..., linetype = :box)
|
||||
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box)
|
||||
violin(args...; kw...) = plot(args...; kw..., linetype = :violin)
|
||||
violin!(args...; kw...) = plot!(args...; kw..., linetype = :violin)
|
||||
quiver(args...; kw...) = plot(args...; kw..., linetype = :quiver)
|
||||
quiver!(args...; kw...) = plot!(args...; kw..., linetype = :quiver)
|
||||
|
||||
|
||||
title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
|
||||
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
|
||||
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...)
|
||||
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...)
|
||||
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
|
||||
annotate!{T<:Tuple}(anns::AVec{T}; kw...) = plot!(; annotation = anns, kw...)
|
||||
xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...)
|
||||
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
|
||||
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
|
||||
yaxis!(args...; kw...) = plot!(; yaxis = 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...)
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const CURRENT_BACKEND = CurrentBackend(:none)
|
||||
|
||||
savepng(args...; kw...) = savepng(currentPlot(), args...; kw...)
|
||||
savepng(plt::PlottingObject, args...; kw...) = savepng(plt.plotter, plt, args...; kw...)
|
||||
function __init__()
|
||||
|
||||
# override IJulia inline display
|
||||
if isijulia()
|
||||
@eval import IJulia
|
||||
IJulia.display_dict(plt::AbstractPlot) = Dict{ASCIIString, ByteString}("text/html" => sprint(writemime, "text/html", plt))
|
||||
end
|
||||
|
||||
setup_dataframes()
|
||||
setup_atom()
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
|
||||
immutable Animation
|
||||
dir::ASCIIString
|
||||
frames::Vector{ASCIIString}
|
||||
end
|
||||
|
||||
function Animation()
|
||||
tmpdir = convert(ASCIIString, mktempdir())
|
||||
Animation(tmpdir, ASCIIString[])
|
||||
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)
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------
|
||||
|
||||
"Wraps the location of an animated gif so that it can be displayed"
|
||||
immutable AnimatedGif
|
||||
filename::ASCIIString
|
||||
end
|
||||
|
||||
function gif(anim::Animation, fn::@compat(AbstractString) = "tmp.gif"; fps::Integer = 20)
|
||||
fn = abspath(fn)
|
||||
|
||||
try
|
||||
|
||||
# high quality
|
||||
speed = round(Int, 100 / fps)
|
||||
run(`convert -delay $speed -loop 0 $(anim.dir)/*.png -alpha off $fn`)
|
||||
|
||||
catch err
|
||||
warn("Tried to create gif using convert (ImageMagick), but got error: $err\nWill 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)
|
||||
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())>\" />")
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------
|
||||
|
||||
function _animate(forloop::Expr, args...; callgif = false)
|
||||
if forloop.head != :for
|
||||
error("@animate macro expects a for-block. got: $(forloop.head)")
|
||||
end
|
||||
|
||||
# add the call to frame to the end of each iteration
|
||||
animsym = gensym("anim")
|
||||
countersym = gensym("counter")
|
||||
block = forloop.args[2]
|
||||
|
||||
# create filter
|
||||
n = length(args)
|
||||
filterexpr = if n == 0
|
||||
# no filter... every iteration gets a frame
|
||||
true
|
||||
|
||||
elseif args[1] == :every
|
||||
# filter every `freq` frames (starting with the first frame)
|
||||
@assert n == 2
|
||||
freq = args[2]
|
||||
@assert isa(freq, Integer) && freq > 0
|
||||
:(mod1($countersym, $freq) == 1)
|
||||
|
||||
elseif args[1] == :when
|
||||
# filter on custom expression
|
||||
@assert n == 2
|
||||
args[2]
|
||||
|
||||
else
|
||||
error("Unsupported animate filter: $args")
|
||||
end
|
||||
|
||||
push!(block.args, :(if $filterexpr; frame($animsym); end))
|
||||
push!(block.args, :($countersym += 1))
|
||||
|
||||
# add a final call to `gif(anim)`?
|
||||
retval = callgif ? :(gif($animsym)) : animsym
|
||||
|
||||
# full expression:
|
||||
esc(quote
|
||||
$animsym = Animation() # init animation object
|
||||
$countersym = 1 # init iteration counter
|
||||
$forloop # for loop, saving a frame after each iteration
|
||||
$retval # return the animation object, or the gif
|
||||
end)
|
||||
end
|
||||
|
||||
"""
|
||||
Builds an `Animation` using one frame per loop iteration, then create an animated GIF.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p = plot(1)
|
||||
@gif for x=0:0.1:5
|
||||
push!(p, 1, sin(x))
|
||||
end
|
||||
```
|
||||
"""
|
||||
macro gif(forloop::Expr, args...)
|
||||
_animate(forloop, args...; callgif = true)
|
||||
end
|
||||
|
||||
"""
|
||||
Collect one frame per for-block iteration and return an `Animation` object.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p = plot(1)
|
||||
anim = @animate for x=0:0.1:5
|
||||
push!(p, 1, sin(x))
|
||||
end
|
||||
gif(anim)
|
||||
```
|
||||
"""
|
||||
macro animate(forloop::Expr, args...)
|
||||
_animate(forloop, args...)
|
||||
end
|
||||
@@ -0,0 +1,160 @@
|
||||
|
||||
immutable 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}()
|
||||
|
||||
backends() = _backends
|
||||
backend_name() = CURRENT_BACKEND.sym
|
||||
_backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")
|
||||
|
||||
macro init_backend(s)
|
||||
str = lowercase(string(s))
|
||||
sym = symbol(str)
|
||||
T = symbol(string(s) * "Backend")
|
||||
esc(quote
|
||||
immutable $T <: AbstractBackend end
|
||||
export $sym
|
||||
$sym(; kw...) = (default(; kw...); backend(symbol($str)))
|
||||
backend_name(::$T) = symbol($str)
|
||||
push!(_backends, symbol($str))
|
||||
_backendType[symbol($str)] = $T
|
||||
_backendSymbol[$T] = symbol($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/supported.jl")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
plot(pkg::AbstractBackend; kw...) = error("plot($pkg; kw...) is not implemented")
|
||||
plot!(pkg::AbstractBackend, plt::Plot; kw...) = error("plot!($pkg, plt; kw...) is not implemented")
|
||||
_update_plot(pkg::AbstractBackend, plt::Plot, d::KW) = error("_update_plot($pkg, plt, d) is not implemented")
|
||||
_update_plot_pos_size{P<:AbstractBackend}(plt::AbstractPlot{P}, d::KW) = nothing
|
||||
subplot(pkg::AbstractBackend; kw...) = error("subplot($pkg; kw...) is not implemented")
|
||||
subplot!(pkg::AbstractBackend, subplt::Subplot; kw...) = error("subplot!($pkg, subplt; kw...) is not implemented")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
type CurrentBackend
|
||||
sym::Symbol
|
||||
pkg::AbstractBackend
|
||||
end
|
||||
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)
|
||||
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"))
|
||||
end
|
||||
catch
|
||||
warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not installed.")
|
||||
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
|
||||
|
||||
# the default if nothing else is installed
|
||||
backend(:plotly)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
"""
|
||||
Returns the current plotting package name. Initializes package on first call.
|
||||
"""
|
||||
function backend()
|
||||
|
||||
global CURRENT_BACKEND
|
||||
if CURRENT_BACKEND.sym == :none
|
||||
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
|
||||
|
||||
"""
|
||||
Set the plot backend.
|
||||
"""
|
||||
function backend(pkg::AbstractBackend)
|
||||
CURRENT_BACKEND.sym = backend_name(pkg)
|
||||
CURRENT_BACKEND.pkg = pkg
|
||||
end
|
||||
|
||||
function backend(modname::Symbol)
|
||||
CURRENT_BACKEND.sym = modname
|
||||
CURRENT_BACKEND.pkg = _backend_instance(modname)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
supportedAxes(::AbstractBackend) = [:left]
|
||||
supportedTypes(::AbstractBackend) = []
|
||||
supportedStyles(::AbstractBackend) = [:solid]
|
||||
supportedMarkers(::AbstractBackend) = [:none]
|
||||
supportedScales(::AbstractBackend) = [:identity]
|
||||
subplotSupported(::AbstractBackend) = false
|
||||
stringsSupported(::AbstractBackend) = false
|
||||
|
||||
supportedAxes() = supportedAxes(backend())
|
||||
supportedTypes() = supportedTypes(backend())
|
||||
supportedStyles() = supportedStyles(backend())
|
||||
supportedMarkers() = supportedMarkers(backend())
|
||||
supportedScales() = supportedScales(backend())
|
||||
subplotSupported() = subplotSupported(backend())
|
||||
stringsSupported() = stringsSupported(backend())
|
||||
|
||||
# ---------------------------------------------------------
|
||||
@@ -0,0 +1,237 @@
|
||||
|
||||
# https://github.com/bokeh/Bokeh.jl
|
||||
|
||||
|
||||
supportedArgs(::BokehBackend) = [
|
||||
# :annotation,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
:linecolor,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# :fillalpha,
|
||||
# :foreground_color,
|
||||
:group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
# :linealpha,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
# :markeralpha,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
# :show,
|
||||
:size,
|
||||
:title,
|
||||
# :windowtitle,
|
||||
:x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# :xticks,
|
||||
:y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
# :tickfont,
|
||||
# :guidefont,
|
||||
# :legendfont,
|
||||
# :grid,
|
||||
# :surface,
|
||||
# :levels,
|
||||
]
|
||||
supportedAxes(::BokehBackend) = [:auto, :left]
|
||||
supportedTypes(::BokehBackend) = [:none, :path, :scatter] #,:steppre, :steppost, :sticks, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour]
|
||||
supportedStyles(::BokehBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::BokehBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape)
|
||||
supportedScales(::BokehBackend) = [:identity, :ln] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
subplotSupported(::BokehBackend) = false
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
function _initialize_backend(::BokehBackend; kw...)
|
||||
@eval begin
|
||||
warn("Bokeh is no longer supported... many features will likely be broken.")
|
||||
import Bokeh
|
||||
export Bokeh
|
||||
end
|
||||
end
|
||||
|
||||
# make255(x) = round(Int, 255 * x)
|
||||
|
||||
# function bokehcolor(c::Colorant)
|
||||
# @sprintf("rgba(%d, %d, %d, %1.3f)", [make255(f(c)) for f in [red,green,blue]]..., alpha(c))
|
||||
# end
|
||||
# bokehcolor(cs::ColorScheme) = bokehcolor(getColor(cs))
|
||||
|
||||
|
||||
const _glyphtypes = KW(
|
||||
:ellipse => :Circle,
|
||||
:rect => :Square,
|
||||
:diamond => :Diamond,
|
||||
:utriangle => :Triangle,
|
||||
:dtriangle => :InvertedTriangle,
|
||||
# :pentagon =>
|
||||
# :hexagon =>
|
||||
# :heptagon =>
|
||||
# :octagon =>
|
||||
:cross => :Cross,
|
||||
:xcross => :X,
|
||||
:star5 => :Asterisk,
|
||||
)
|
||||
|
||||
|
||||
function bokeh_glyph_type(d::KW)
|
||||
lt = d[:linetype]
|
||||
mt = d[:markershape]
|
||||
if lt == :scatter && mt == :none
|
||||
mt = :ellipse
|
||||
end
|
||||
|
||||
# if we have a marker, use that
|
||||
if lt == :scatter || mt != :none
|
||||
return _glyphtypes[mt]
|
||||
end
|
||||
|
||||
# otherwise return a line
|
||||
return :Line
|
||||
end
|
||||
|
||||
function get_stroke_vector(linestyle::Symbol)
|
||||
dash = 12
|
||||
dot = 3
|
||||
gap = 2
|
||||
linestyle == :solid && return Int[]
|
||||
linestyle == :dash && return Int[dash, gap]
|
||||
linestyle == :dot && return Int[dot, gap]
|
||||
linestyle == :dashdot && return Int[dash, gap, dot, gap]
|
||||
linestyle == :dashdotdot && return Int[dash, gap, dot, gap, dot, gap]
|
||||
error("unsupported linestyle: ", linestyle)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _create_plot(pkg::BokehBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
|
||||
datacolumns = Bokeh.BokehDataSet[]
|
||||
tools = Bokeh.tools()
|
||||
filename = tempname() * ".html"
|
||||
title = d[:title]
|
||||
w, h = d[:size]
|
||||
xaxis_type = d[:xscale] == :log10 ? :log : :auto
|
||||
yaxis_type = d[:yscale] == :log10 ? :log : :auto
|
||||
# legend = d[:legend] ? xxxx : nothing
|
||||
legend = nothing
|
||||
extra_args = KW() # TODO: we'll put extra settings (xlim, etc) here
|
||||
bplt = Bokeh.Plot(datacolumns, tools, filename, title, w, h, xaxis_type, yaxis_type, legend) #, extra_args)
|
||||
|
||||
Plot(bplt, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::BokehBackend, plt::Plot, d::KW)
|
||||
bdata = Dict{Symbol, Vector}(:x => collect(d[:x]), :y => collect(d[:y]))
|
||||
|
||||
glyph = Bokeh.Bokehjs.Glyph(
|
||||
glyphtype = bokeh_glyph_type(d),
|
||||
linecolor = webcolor(d[:linecolor]), # shape's stroke or line color
|
||||
linewidth = d[:linewidth], # shape's stroke width or line width
|
||||
fillcolor = webcolor(d[:markercolor]),
|
||||
size = ceil(Int, d[:markersize] * 2.5), # magic number 2.5 to keep in same scale as other backends
|
||||
dash = get_stroke_vector(d[:linestyle])
|
||||
)
|
||||
|
||||
legend = nothing # TODO
|
||||
push!(plt.o.datacolumns, Bokeh.BokehDataSet(bdata, glyph, legend))
|
||||
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{BokehBackend}, d::KW)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{BokehBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# function getxy(plt::Plot{BokehBackend}, i::Int)
|
||||
# series = plt.o.datacolumns[i].data
|
||||
# series[:x], series[:y]
|
||||
# end
|
||||
#
|
||||
# function setxy!(plt::Plot{BokehBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
# series = plt.o.datacolumns[i].data
|
||||
# series[:x], series[:y] = xy
|
||||
# plt
|
||||
# end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{BokehBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
# TODO: add the annotation to the plot
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{BokehBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
|
||||
end
|
||||
|
||||
|
||||
function _expand_limits(lims, plt::Plot{BokehBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{BokehBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{BokehBackend})
|
||||
# TODO: write a png to io
|
||||
warn("mime png not implemented")
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{BokehBackend})
|
||||
Bokeh.showplot(plt.o)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Subplot{BokehBackend})
|
||||
# TODO: display/show the subplot
|
||||
end
|
||||
@@ -0,0 +1,742 @@
|
||||
|
||||
# https://github.com/dcjones/Gadfly.jl
|
||||
|
||||
|
||||
supportedArgs(::GadflyBackend) = [
|
||||
:annotation,
|
||||
:background_color, :foreground_color, :color_palette,
|
||||
:group, :label, :linetype,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor, :linestyle, :linewidth, :linealpha,
|
||||
:markershape, :markercolor, :markersize, :markeralpha,
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins, :n, :nc, :nr, :layout, :smooth,
|
||||
:title, :windowtitle, :show, :size,
|
||||
:x, :xlabel, :xlims, :xticks, :xscale, :xflip,
|
||||
:y, :ylabel, :ylims, :yticks, :yscale, :yflip,
|
||||
# :z, :zlabel, :zlims, :zticks, :zscale, :zflip,
|
||||
:z,
|
||||
:tickfont, :guidefont, :legendfont,
|
||||
:grid, :legend, :colorbar,
|
||||
:marker_z, :levels,
|
||||
:xerror, :yerror,
|
||||
:ribbon, :quiver,
|
||||
:orientation,
|
||||
]
|
||||
supportedAxes(::GadflyBackend) = [:auto, :left]
|
||||
supportedTypes(::GadflyBackend) = [
|
||||
:none, :line, :path, :steppre, :steppost, :sticks,
|
||||
:scatter, :hist2d, :hexbin, :hist,
|
||||
:bar, :box, :violin, :quiver,
|
||||
:hline, :vline, :contour, :shape
|
||||
]
|
||||
supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape)
|
||||
supportedScales(::GadflyBackend) = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
subplotSupported(::GadflyBackend) = true
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::GadflyBackend; kw...)
|
||||
@eval begin
|
||||
import Gadfly, Compose
|
||||
export Gadfly, Compose
|
||||
include(joinpath(Pkg.dir("Plots"), "src", "backends", "gadfly_shapes.jl"))
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# immutable MissingVec <: AbstractVector{Float64} end
|
||||
# Base.size(v::MissingVec) = (1,)
|
||||
# Base.getindex(v::MissingVec, i::Integer) = 0.0
|
||||
|
||||
function createGadflyPlotObject(d::KW)
|
||||
gplt = Gadfly.Plot()
|
||||
gplt.mapping = Dict()
|
||||
gplt.data_source = Gadfly.DataFrames.DataFrame()
|
||||
# gplt.layers = gplt.layers[1:0]
|
||||
gplt.layers = [Gadfly.layer(Gadfly.Geom.point(tag=:remove), x=zeros(1), y=zeros(1));] # x=MissingVec(), y=MissingVec());]
|
||||
gplt.guides = Gadfly.GuideElement[Gadfly.Guide.xlabel(d[:xlabel]),
|
||||
Gadfly.Guide.ylabel(d[:ylabel]),
|
||||
Gadfly.Guide.title(d[:title])]
|
||||
gplt
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
function getLineGeom(d::KW)
|
||||
lt = d[:linetype]
|
||||
xbins, ybins = maketuple(d[:bins])
|
||||
if lt == :hexb
|
||||
Gadfly.Geom.hexbin(xbincount = xbins, ybincount = ybins)
|
||||
elseif lt == :hist2d
|
||||
Gadfly.Geom.histogram2d(xbincount = xbins, ybincount = ybins)
|
||||
elseif lt == :hist
|
||||
Gadfly.Geom.histogram(bincount = xbins,
|
||||
orientation = isvertical(d) ? :vertical : :horizontal,
|
||||
position = d[:bar_position] == :stack ? :stack : :dodge)
|
||||
elseif lt == :path
|
||||
Gadfly.Geom.path
|
||||
elseif lt in (:bar, :sticks)
|
||||
Gadfly.Geom.bar
|
||||
elseif lt == :steppost
|
||||
Gadfly.Geom.step
|
||||
elseif lt == :steppre
|
||||
Gadfly.Geom.step(direction = :vh)
|
||||
elseif lt == :hline
|
||||
Gadfly.Geom.hline
|
||||
elseif lt == :vline
|
||||
Gadfly.Geom.vline
|
||||
elseif lt == :contour
|
||||
Gadfly.Geom.contour(levels = d[:levels])
|
||||
# elseif lt == :shape
|
||||
# Gadfly.Geom.polygon(fill = true, preserve_order = true)
|
||||
else
|
||||
nothing
|
||||
end
|
||||
end
|
||||
|
||||
function get_extra_theme_args(d::KW, k::Symbol)
|
||||
# gracefully handles old Gadfly versions
|
||||
extra_theme_args = KW()
|
||||
try
|
||||
extra_theme_args[:line_style] = Gadfly.get_stroke_vector(d[k])
|
||||
catch err
|
||||
if string(err) == "UndefVarError(:get_stroke_vector)"
|
||||
Base.warn_once("Gadfly.get_stroke_vector failed... do you have an old version of Gadfly?")
|
||||
else
|
||||
rethrow()
|
||||
end
|
||||
end
|
||||
extra_theme_args
|
||||
end
|
||||
|
||||
function getGadflyLineTheme(d::KW)
|
||||
lt = d[:linetype]
|
||||
lc = convertColor(getColor(d[:linecolor]), d[:linealpha])
|
||||
fc = convertColor(getColor(d[:fillcolor]), d[:fillalpha])
|
||||
|
||||
Gadfly.Theme(;
|
||||
default_color = (lt in (:hist,:hist2d,:hexbin,:bar,:sticks) ? fc : lc),
|
||||
line_width = (lt == :sticks ? 1 : d[:linewidth]) * Gadfly.px,
|
||||
# line_style = Gadfly.get_stroke_vector(d[:linestyle]),
|
||||
lowlight_color = x->RGB(fc), # fill/ribbon
|
||||
lowlight_opacity = alpha(fc), # fill/ribbon
|
||||
bar_highlight = RGB(lc), # bars
|
||||
get_extra_theme_args(d, :linestyle)...
|
||||
)
|
||||
end
|
||||
|
||||
# add a line as a new layer
|
||||
function addGadflyLine!(plt::Plot, numlayers::Int, d::KW, geoms...)
|
||||
gplt = getGadflyContext(plt)
|
||||
gfargs = vcat(geoms..., getGadflyLineTheme(d))
|
||||
kwargs = KW()
|
||||
lt = d[:linetype]
|
||||
|
||||
# add a fill?
|
||||
if d[:fillrange] != nothing && lt != :contour
|
||||
fillmin, fillmax = map(makevec, maketuple(d[:fillrange]))
|
||||
nmin, nmax = length(fillmin), length(fillmax)
|
||||
kwargs[:ymin] = Float64[min(y, fillmin[mod1(i, nmin)], fillmax[mod1(i, nmax)]) for (i,y) in enumerate(d[:y])]
|
||||
kwargs[:ymax] = Float64[max(y, fillmin[mod1(i, nmin)], fillmax[mod1(i, nmax)]) for (i,y) in enumerate(d[:y])]
|
||||
push!(gfargs, Gadfly.Geom.ribbon)
|
||||
end
|
||||
|
||||
if lt in (:hline, :vline)
|
||||
kwargs[lt == :hline ? :yintercept : :xintercept] = d[:y]
|
||||
|
||||
else
|
||||
if lt == :sticks
|
||||
w = 0.01 * mean(diff(d[:x]))
|
||||
kwargs[:xmin] = d[:x] - w
|
||||
kwargs[:xmax] = d[:x] + w
|
||||
elseif lt == :contour
|
||||
kwargs[:z] = d[:z].surf
|
||||
addGadflyContColorScale(plt, d[:linecolor])
|
||||
end
|
||||
|
||||
kwargs[:x] = d[lt == :hist ? :y : :x]
|
||||
kwargs[:y] = d[:y]
|
||||
|
||||
end
|
||||
|
||||
# # add the layer
|
||||
Gadfly.layer(gfargs...; order=numlayers, kwargs...)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
get_shape(sym::Symbol) = _shapes[sym]
|
||||
get_shape(shape::Shape) = shape
|
||||
|
||||
# extract the underlying ShapeGeometry object(s)
|
||||
getMarkerGeom(shapes::AVec) = gadflyshape(map(get_shape, shapes))
|
||||
getMarkerGeom(other) = gadflyshape(get_shape(other))
|
||||
|
||||
# getMarkerGeom(shape::Shape) = gadflyshape(shape)
|
||||
# getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape])
|
||||
# getMarkerGeom(shapes::AVec) = gadflyshape(map(gadflyshape, shapes)) # map(getMarkerGeom, shapes)
|
||||
function getMarkerGeom(d::KW)
|
||||
if d[:linetype] == :shape
|
||||
Gadfly.Geom.polygon(fill = true, preserve_order = true)
|
||||
else
|
||||
getMarkerGeom(d[:markershape])
|
||||
end
|
||||
end
|
||||
|
||||
function getGadflyMarkerTheme(d::KW, plotargs::KW)
|
||||
c = getColor(d[:markercolor])
|
||||
α = d[:markeralpha]
|
||||
if α != nothing
|
||||
c = RGBA(RGB(c), α)
|
||||
end
|
||||
|
||||
ms = d[:markersize]
|
||||
ms = if typeof(ms) <: AVec
|
||||
warn("Gadfly doesn't support variable marker sizes... using the average: $(mean(ms))")
|
||||
mean(ms) * Gadfly.px
|
||||
else
|
||||
ms * Gadfly.px
|
||||
end
|
||||
|
||||
Gadfly.Theme(;
|
||||
default_color = c,
|
||||
default_point_size = ms,
|
||||
discrete_highlight_color = c -> RGB(getColor(d[:markerstrokecolor])),
|
||||
highlight_width = d[:markerstrokewidth] * Gadfly.px,
|
||||
line_width = d[:markerstrokewidth] * Gadfly.px,
|
||||
# get_extra_theme_args(d, :markerstrokestyle)...
|
||||
)
|
||||
end
|
||||
|
||||
function addGadflyContColorScale(plt::Plot{GadflyBackend}, c)
|
||||
plt.plotargs[:colorbar] == :none && return
|
||||
if !isa(c, ColorGradient)
|
||||
c = default_gradient()
|
||||
end
|
||||
push!(getGadflyContext(plt).scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(c, p))))
|
||||
end
|
||||
|
||||
function addGadflyMarker!(plt::Plot, numlayers::Int, d::KW, plotargs::KW, geoms...)
|
||||
gfargs = vcat(geoms..., getGadflyMarkerTheme(d, plotargs), getMarkerGeom(d))
|
||||
kwargs = KW()
|
||||
|
||||
# handle continuous color scales for the markers
|
||||
zcolor = d[:marker_z]
|
||||
if zcolor != nothing && typeof(zcolor) <: AVec
|
||||
kwargs[:color] = zcolor
|
||||
addGadflyContColorScale(plt, d[:markercolor])
|
||||
end
|
||||
|
||||
Gadfly.layer(gfargs...; x = d[:x], y = d[:y], order=numlayers, kwargs...)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function addToGadflyLegend(plt::Plot, d::KW)
|
||||
if plt.plotargs[:legend] != :none && d[:label] != ""
|
||||
gplt = getGadflyContext(plt)
|
||||
|
||||
# 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[]))
|
||||
end
|
||||
|
||||
# now add the series to the legend
|
||||
for guide in gplt.guides
|
||||
if isa(guide, Gadfly.Guide.ManualColorKey)
|
||||
# TODO: there's a BUG in gadfly if you pass in the same color more than once,
|
||||
# since gadfly will call unique(colors), but doesn't also merge the rows that match
|
||||
# Should ensure from this side that colors which are the same are merged together
|
||||
|
||||
c = getColor(d[d[:markershape] == :none ? :linecolor : :markercolor])
|
||||
foundit = false
|
||||
|
||||
# extend the label if we found this color
|
||||
for i in 1:length(guide.colors)
|
||||
if RGB(c) == guide.colors[i]
|
||||
guide.labels[i] *= ", " * d[:label]
|
||||
foundit = true
|
||||
end
|
||||
end
|
||||
|
||||
# didn't find the color, so add a new entry into the legend
|
||||
if !foundit
|
||||
push!(guide.labels, d[:label])
|
||||
push!(guide.colors, c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
getGadflySmoothing(smooth::Bool) = smooth ? [Gadfly.Geom.smooth(method=:lm)] : Any[]
|
||||
getGadflySmoothing(smooth::Real) = [Gadfly.Geom.smooth(method=:loess, smoothing=float(smooth))]
|
||||
|
||||
|
||||
function addGadflySeries!(plt::Plot, d::KW)
|
||||
layers = Gadfly.Layer[]
|
||||
gplt = getGadflyContext(plt)
|
||||
|
||||
# add a regression line?
|
||||
# TODO: make more flexible
|
||||
smooth = getGadflySmoothing(d[:smooth])
|
||||
|
||||
# lines
|
||||
geom = getLineGeom(d)
|
||||
if geom != nothing
|
||||
prepend!(layers, addGadflyLine!(plt, length(gplt.layers), d, geom, smooth...))
|
||||
smooth = Any[] # don't add a regression for markers too
|
||||
end
|
||||
|
||||
# special handling for ohlc and scatter
|
||||
lt = d[:linetype]
|
||||
if lt == :ohlc
|
||||
error("Haven't re-implemented after refactoring")
|
||||
elseif lt in (:hist2d, :hexbin) && (isa(d[:fillcolor], ColorGradient) || isa(d[:fillcolor], ColorFunction))
|
||||
push!(gplt.scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(d[:fillcolor], p))))
|
||||
elseif lt == :scatter && d[:markershape] == :none
|
||||
d[:markershape] = :ellipse
|
||||
end
|
||||
|
||||
# markers
|
||||
if d[:markershape] != :none || lt == :shape
|
||||
prepend!(layers, addGadflyMarker!(plt, length(gplt.layers), d, plt.plotargs, smooth...))
|
||||
end
|
||||
|
||||
lt in (:hist2d, :hexbin, :contour) || addToGadflyLegend(plt, d)
|
||||
|
||||
# now save the layers that apply to this series
|
||||
d[:gadflylayers] = layers
|
||||
prepend!(gplt.layers, layers)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# NOTE: I'm leaving this here and commented out just in case I want to implement again... it was hacky code to create multi-colored line segments
|
||||
|
||||
# # colorgroup
|
||||
# z = d[:z]
|
||||
|
||||
# # handle line segments of different colors
|
||||
# cscheme = d[:linecolor]
|
||||
# if isa(cscheme, ColorVector)
|
||||
# # create a color scale, and set the color group to the index of the color
|
||||
# push!(gplt.scales, Gadfly.Scale.color_discrete_manual(cscheme.v...))
|
||||
|
||||
# # this is super weird, but... oh well... for some reason this creates n separate line segments...
|
||||
# # 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]
|
||||
# d[:x], d[:y] = map(createSegments, (d[:x], d[:y]))
|
||||
# colorgroup = [(:linecolor, cs), (:group, grp)]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
function addGadflyTicksGuide(gplt, ticks, isx::Bool)
|
||||
ticks == :auto && return
|
||||
|
||||
# remove the ticks?
|
||||
if ticks in (:none, false, nothing)
|
||||
return addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
|
||||
end
|
||||
|
||||
ttype = ticksType(ticks)
|
||||
|
||||
# just the values... put ticks here, but use standard labels
|
||||
if ttype == :ticks
|
||||
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
|
||||
replaceType(gplt.guides, gtype(ticks = collect(ticks)))
|
||||
|
||||
# set the ticks and the labels
|
||||
# Note: this is pretty convoluted, but I think it works. We set the ticks using Gadfly.Guide,
|
||||
# and then set the label function (wraps a dict lookup) through a continuous Gadfly.Scale.
|
||||
elseif ttype == :ticks_and_labels
|
||||
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
|
||||
replaceType(gplt.guides, gtype(ticks = collect(ticks[1])))
|
||||
|
||||
# # TODO add xtick_label function (given tick, return label??)
|
||||
# # Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
|
||||
# filterGadflyScale(gplt, isx)
|
||||
# gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
|
||||
# labelmap = Dict(zip(ticks...))
|
||||
# labelfunc = val -> labelmap[val]
|
||||
# push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
|
||||
|
||||
filterGadflyScale(gplt, isx)
|
||||
gfunc = isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous
|
||||
labelmap = Dict(zip(ticks...))
|
||||
labelfunc = val -> labelmap[val]
|
||||
push!(gplt.scales, gfunc(labels = labelfunc))
|
||||
|
||||
else
|
||||
error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks)
|
||||
end
|
||||
end
|
||||
|
||||
continuousAndSameAxis(scale, isx::Bool) = isa(scale, Gadfly.Scale.ContinuousScale) && scale.vars[1] == (isx ? :x : :y)
|
||||
filterGadflyScale(gplt, isx::Bool) = filter!(scale -> !continuousAndSameAxis(scale, isx), gplt.scales)
|
||||
|
||||
|
||||
function getGadflyScaleFunction(d::KW, isx::Bool)
|
||||
scalekey = isx ? :xscale : :yscale
|
||||
hasScaleKey = haskey(d, scalekey)
|
||||
if hasScaleKey
|
||||
scale = d[scalekey]
|
||||
scale == :ln && return isx ? Gadfly.Scale.x_log : Gadfly.Scale.y_log, hasScaleKey, log
|
||||
scale == :log2 && return isx ? Gadfly.Scale.x_log2 : Gadfly.Scale.y_log2, hasScaleKey, log2
|
||||
scale == :log10 && return isx ? Gadfly.Scale.x_log10 : Gadfly.Scale.y_log10, hasScaleKey, log10
|
||||
scale == :asinh && return isx ? Gadfly.Scale.x_asinh : Gadfly.Scale.y_asinh, hasScaleKey, asinh
|
||||
scale == :sqrt && return isx ? Gadfly.Scale.x_sqrt : Gadfly.Scale.y_sqrt, hasScaleKey, sqrt
|
||||
end
|
||||
isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous, hasScaleKey, identity
|
||||
end
|
||||
|
||||
|
||||
function addGadflyLimitsScale(gplt, d::KW, isx::Bool)
|
||||
gfunc, hasScaleKey, func = getGadflyScaleFunction(d, isx)
|
||||
|
||||
# do we want to add min/max limits for the axis?
|
||||
limsym = isx ? :xlims : :ylims
|
||||
limargs = Any[]
|
||||
|
||||
# map :auto to nothing, otherwise add to limargs
|
||||
lims = get(d, limsym, :auto)
|
||||
if lims == :auto
|
||||
lims = nothing
|
||||
else
|
||||
if limsType(lims) == :limits
|
||||
push!(limargs, (:minvalue, min(lims...)))
|
||||
push!(limargs, (:maxvalue, max(lims...)))
|
||||
else
|
||||
error("Invalid input for $(isx ? "xlims" : "ylims"): ", lims)
|
||||
end
|
||||
end
|
||||
|
||||
# replace any current scales with this one
|
||||
if hasScaleKey || !isempty(limargs)
|
||||
filterGadflyScale(gplt, isx)
|
||||
push!(gplt.scales, gfunc(; limargs...))
|
||||
end
|
||||
|
||||
lims, func
|
||||
end
|
||||
|
||||
function updateGadflyAxisFlips(gplt, d::KW, xlims, ylims, xfunc, yfunc)
|
||||
if isa(gplt.coord, Gadfly.Coord.Cartesian)
|
||||
gplt.coord = Gadfly.Coord.cartesian(
|
||||
gplt.coord.xvars,
|
||||
gplt.coord.yvars;
|
||||
xmin = xlims == nothing ? gplt.coord.xmin : xfunc(minimum(xlims)),
|
||||
xmax = xlims == nothing ? gplt.coord.xmax : xfunc(maximum(xlims)),
|
||||
ymin = ylims == nothing ? gplt.coord.ymin : yfunc(minimum(ylims)),
|
||||
ymax = ylims == nothing ? gplt.coord.ymax : yfunc(maximum(ylims)),
|
||||
xflip = get(d, :xflip, gplt.coord.xflip),
|
||||
yflip = get(d, :yflip, gplt.coord.yflip),
|
||||
fixed = gplt.coord.fixed,
|
||||
aspect_ratio = gplt.coord.aspect_ratio,
|
||||
raster = gplt.coord.raster
|
||||
)
|
||||
else
|
||||
gplt.coord = Gadfly.Coord.Cartesian(
|
||||
xflip = get(d, :xflip, false),
|
||||
yflip = get(d, :yflip, false)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function findGuideAndSet(gplt, t::DataType, args...; kw...) #s::@compat(AbstractString))
|
||||
for (i,guide) in enumerate(gplt.guides)
|
||||
if isa(guide, t)
|
||||
gplt.guides[i] = t(args...; kw...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function updateGadflyGuides(plt::Plot, d::KW)
|
||||
gplt = getGadflyContext(plt)
|
||||
haskey(d, :title) && findGuideAndSet(gplt, Gadfly.Guide.title, string(d[:title]))
|
||||
haskey(d, :xlabel) && findGuideAndSet(gplt, Gadfly.Guide.xlabel, string(d[:xlabel]))
|
||||
haskey(d, :ylabel) && findGuideAndSet(gplt, Gadfly.Guide.ylabel, string(d[:ylabel]))
|
||||
|
||||
xlims, xfunc = addGadflyLimitsScale(gplt, d, true)
|
||||
ylims, yfunc = addGadflyLimitsScale(gplt, d, false)
|
||||
|
||||
ticks = get(d, :xticks, :auto)
|
||||
if ticks == :none
|
||||
_remove_axis(plt, true)
|
||||
else
|
||||
addGadflyTicksGuide(gplt, ticks, true)
|
||||
end
|
||||
ticks = get(d, :yticks, :auto)
|
||||
if ticks == :none
|
||||
_remove_axis(plt, false)
|
||||
else
|
||||
addGadflyTicksGuide(gplt, ticks, false)
|
||||
end
|
||||
|
||||
updateGadflyAxisFlips(gplt, d, xlims, ylims, xfunc, yfunc)
|
||||
end
|
||||
|
||||
function updateGadflyPlotTheme(plt::Plot, d::KW)
|
||||
kwargs = KW()
|
||||
|
||||
# colors
|
||||
insidecolor, gridcolor, textcolor, guidecolor, legendcolor =
|
||||
map(s -> getColor(d[s]), (
|
||||
:background_color_inside,
|
||||
:foreground_color_grid,
|
||||
:foreground_color_text,
|
||||
:foreground_color_guide,
|
||||
:foreground_color_legend
|
||||
))
|
||||
|
||||
# # hide the legend?
|
||||
leg = d[d[:legend] == :none ? :colorbar : :legend]
|
||||
if leg != :best
|
||||
kwargs[:key_position] = leg == :inside ? :right : leg
|
||||
end
|
||||
|
||||
if !get(d, :grid, true)
|
||||
kwargs[:grid_color] = gridcolor
|
||||
end
|
||||
|
||||
# fonts
|
||||
tfont, gfont, lfont = d[:tickfont], d[:guidefont], d[:legendfont]
|
||||
|
||||
getGadflyContext(plt).theme = Gadfly.Theme(;
|
||||
background_color = insidecolor,
|
||||
minor_label_color = textcolor,
|
||||
minor_label_font = tfont.family,
|
||||
minor_label_font_size = tfont.pointsize * Gadfly.pt,
|
||||
major_label_color = guidecolor,
|
||||
major_label_font = gfont.family,
|
||||
major_label_font_size = gfont.pointsize * Gadfly.pt,
|
||||
key_title_color = guidecolor,
|
||||
key_title_font = gfont.family,
|
||||
key_title_font_size = gfont.pointsize * Gadfly.pt,
|
||||
key_label_color = legendcolor,
|
||||
key_label_font = lfont.family,
|
||||
key_label_font_size = lfont.pointsize * Gadfly.pt,
|
||||
plot_padding = 1 * Gadfly.mm,
|
||||
kwargs...
|
||||
)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
function createGadflyAnnotationObject(x, y, val::@compat(AbstractString))
|
||||
Gadfly.Guide.annotation(Compose.compose(
|
||||
Compose.context(),
|
||||
Compose.text(x, y, val)
|
||||
))
|
||||
end
|
||||
|
||||
function createGadflyAnnotationObject(x, y, txt::PlotText)
|
||||
halign = (txt.font.halign == :hcenter ? Compose.hcenter : (txt.font.halign == :left ? Compose.hleft : Compose.hright))
|
||||
valign = (txt.font.valign == :vcenter ? Compose.vcenter : (txt.font.valign == :top ? Compose.vtop : Compose.vbottom))
|
||||
rotations = (txt.font.rotation == 0.0 ? [] : [Compose.Rotation(txt.font.rotation, Compose.Point(Compose.x_measure(x), Compose.y_measure(y)))])
|
||||
Gadfly.Guide.annotation(Compose.compose(
|
||||
Compose.context(),
|
||||
Compose.text(x, y, txt.str, halign, valign, rotations...),
|
||||
Compose.font(string(txt.font.family)),
|
||||
Compose.fontsize(txt.font.pointsize * Gadfly.pt),
|
||||
Compose.stroke(txt.font.color),
|
||||
Compose.fill(txt.font.color)
|
||||
))
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{GadflyBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
push!(plt.o.guides, createGadflyAnnotationObject(ann...))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# create a blank Gadfly.Plot object
|
||||
function _create_plot(pkg::GadflyBackend, d::KW)
|
||||
gplt = createGadflyPlotObject(d)
|
||||
Plot(gplt, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
# plot one data series
|
||||
function _add_series(::GadflyBackend, plt::Plot, d::KW)
|
||||
# first clear out the temporary layer
|
||||
gplt = getGadflyContext(plt)
|
||||
if gplt.layers[1].geom.tag == :remove
|
||||
gplt.layers = gplt.layers[2:end]
|
||||
end
|
||||
|
||||
addGadflySeries!(plt, d)
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _update_plot(plt::Plot{GadflyBackend}, d::KW)
|
||||
updateGadflyGuides(plt, d)
|
||||
updateGadflyPlotTheme(plt, d)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# TODO: need to save all the layer indices which apply to this series
|
||||
function getGadflyMappings(plt::Plot, i::Integer)
|
||||
@assert i > 0 && i <= plt.n
|
||||
mappings = [l.mapping for l in plt.seriesargs[i][:gadflylayers]]
|
||||
end
|
||||
|
||||
function getxy(plt::Plot{GadflyBackend}, i::Integer)
|
||||
mapping = getGadflyMappings(plt, i)[1]
|
||||
mapping[:x], mapping[:y]
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
for mapping in getGadflyMappings(plt, i)
|
||||
mapping[:x], mapping[:y] = xy
|
||||
end
|
||||
plt
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
# create the underlying object (each backend will do this differently)
|
||||
function _create_subplot(subplt::Subplot{GadflyBackend}, isbefore::Bool)
|
||||
isbefore && return false # wait until after plotting to create the subplots
|
||||
subplt.o = nothing
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
function _remove_axis(plt::Plot{GadflyBackend}, isx::Bool)
|
||||
gplt = getGadflyContext(plt)
|
||||
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
|
||||
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xlabel : Gadfly.Guide.ylabel, "")
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{GadflyBackend}, isx::Bool)
|
||||
for l in getGadflyContext(plt).layers
|
||||
_expand_limits(lims, l.mapping[isx ? :x : :y])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
getGadflyContext(plt::Plot{GadflyBackend}) = plt.o
|
||||
getGadflyContext(subplt::Subplot{GadflyBackend}) = buildGadflySubplotContext(subplt)
|
||||
|
||||
# create my Compose.Context grid by hstacking and vstacking the Gadfly.Plot objects
|
||||
function buildGadflySubplotContext(subplt::Subplot)
|
||||
rows = Any[]
|
||||
row = Any[]
|
||||
for (i,(r,c)) in enumerate(subplt.layout)
|
||||
|
||||
# add the Plot object to the row
|
||||
push!(row, getGadflyContext(subplt.plts[i]))
|
||||
|
||||
# add the row
|
||||
if c == ncols(subplt.layout, r)
|
||||
push!(rows, Gadfly.hstack(row...))
|
||||
row = Any[]
|
||||
end
|
||||
end
|
||||
|
||||
# stack the rows
|
||||
Gadfly.vstack(rows...)
|
||||
end
|
||||
|
||||
setGadflyDisplaySize(w,h) = Compose.set_default_graphic_size(w * Compose.px, h * Compose.px)
|
||||
setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.plotargs[:size]...)
|
||||
setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getplotargs(subplt, 1)[:size]...)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
function dowritemime{P<:Union{GadflyBackend,ImmerseBackend}}(io::IO, func, plt::AbstractPlot{P})
|
||||
gplt = getGadflyContext(plt)
|
||||
setGadflyDisplaySize(plt)
|
||||
Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt)
|
||||
end
|
||||
|
||||
getGadflyWriteFunc(::MIME"image/png") = Gadfly.PNG
|
||||
getGadflyWriteFunc(::MIME"image/svg+xml") = Gadfly.SVG
|
||||
# getGadflyWriteFunc(::MIME"text/html") = Gadfly.SVGJS
|
||||
getGadflyWriteFunc(::MIME"application/pdf") = Gadfly.PDF
|
||||
getGadflyWriteFunc(::MIME"application/postscript") = Gadfly.PS
|
||||
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})
|
||||
func = getGadflyWriteFunc($mime())
|
||||
dowritemime(io, func, plt)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{GadflyBackend})
|
||||
setGadflyDisplaySize(plt.plotargs[:size]...)
|
||||
display(plt.o)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, subplt::Subplot{GadflyBackend})
|
||||
setGadflyDisplaySize(getplotargs(subplt,1)[:size]...)
|
||||
ctx = buildGadflySubplotContext(subplt)
|
||||
|
||||
# taken from Gadfly since I couldn't figure out how to do it directly
|
||||
|
||||
filename = string(Gadfly.tempname(), ".html")
|
||||
output = open(filename, "w")
|
||||
|
||||
plot_output = IOBuffer()
|
||||
Gadfly.draw(Gadfly.SVGJS(plot_output, Compose.default_graphic_width,
|
||||
Compose.default_graphic_height, false), ctx)
|
||||
plotsvg = takebuf_string(plot_output)
|
||||
|
||||
write(output,
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Gadfly Plot</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script charset="utf-8">
|
||||
$(readall(Compose.snapsvgjs))
|
||||
</script>
|
||||
<script charset="utf-8">
|
||||
$(readall(Gadfly.gadflyjs))
|
||||
</script>
|
||||
$(plotsvg)
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
close(output)
|
||||
Gadfly.open_file(filename)
|
||||
end
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
|
||||
# Geometry which displays arbitrary shapes at given (x, y) positions.
|
||||
# note: vertices is a list of shapes
|
||||
immutable ShapeGeometry <: Gadfly.GeometryElement
|
||||
vertices::AbstractVector #{Tuple{Float64,Float64}}
|
||||
tag::Symbol
|
||||
|
||||
function ShapeGeometry(shape; tag::Symbol=Gadfly.Geom.empty_tag)
|
||||
new(shape, tag)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: add for PR
|
||||
# const shape = ShapeGeometry
|
||||
|
||||
|
||||
function Gadfly.element_aesthetics(::ShapeGeometry)
|
||||
[:x, :y, :size, :color]
|
||||
end
|
||||
|
||||
|
||||
# Generate a form for a shape geometry.
|
||||
#
|
||||
# Args:
|
||||
# geom: shape geometry.
|
||||
# theme: the plot's theme.
|
||||
# aes: aesthetics.
|
||||
#
|
||||
# Returns:
|
||||
# A compose Form.
|
||||
#
|
||||
function Gadfly.render(geom::ShapeGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
|
||||
|
||||
# TODO: add for PR
|
||||
# Gadfly.assert_aesthetics_defined("Geom.shape", aes, :x, :y)
|
||||
# Gadfly.assert_aesthetics_equal_length("Geom.shape", aes,
|
||||
# element_aesthetics(geom)...)
|
||||
|
||||
default_aes = Gadfly.Aesthetics()
|
||||
default_aes.color = Gadfly.DataFrames.PooledDataArray(RGBA{Float32}[theme.default_color])
|
||||
default_aes.size = Compose.Measure[theme.default_point_size]
|
||||
aes = Gadfly.inherit(aes, default_aes)
|
||||
|
||||
lw_hover_scale = 10
|
||||
lw_ratio = theme.line_width / aes.size[1]
|
||||
|
||||
aes_x, aes_y = Gadfly.concretize(aes.x, aes.y)
|
||||
|
||||
ctx = Compose.compose!(
|
||||
Compose.context(),
|
||||
make_polygon(geom, aes.x, aes.y, aes.size),
|
||||
Compose.fill(aes.color),
|
||||
Compose.linewidth(theme.highlight_width))
|
||||
|
||||
if aes.color_key_continuous != nothing && aes.color_key_continuous
|
||||
Compose.compose!(ctx,
|
||||
Compose.stroke(map(theme.continuous_highlight_color, aes.color)))
|
||||
else
|
||||
Compose.compose!(ctx,
|
||||
Compose.stroke(map(theme.discrete_highlight_color, aes.color)),
|
||||
Compose.svgclass([Gadfly.svg_color_class_from_label(Gadfly.escape_id(aes.color_label([c])[1]))
|
||||
for c in aes.color]))
|
||||
end
|
||||
|
||||
return Compose.compose!(Compose.context(order=4), Compose.svgclass("geometry"), ctx)
|
||||
end
|
||||
|
||||
function gadflyshape(sv::Shape)
|
||||
ShapeGeometry(Any[vertices(sv)])
|
||||
end
|
||||
|
||||
function gadflyshape(sv::AVec{Shape})
|
||||
ShapeGeometry(Any[vertices(s) for s in sv])
|
||||
end
|
||||
|
||||
|
||||
# create a Compose context given a ShapeGeometry and the xs/ys/sizes
|
||||
function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray, rs::AbstractArray)
|
||||
n = max(length(xs), length(ys), length(rs))
|
||||
T = Tuple{Compose.Measure, Compose.Measure}
|
||||
polys = Array(Vector{T}, n)
|
||||
for i in 1:n
|
||||
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 get_mod(geom.vertices, i)]
|
||||
end
|
||||
Gadfly.polygon(polys, geom.tag)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,171 @@
|
||||
|
||||
|
||||
# [WEBSITE]
|
||||
|
||||
supportedArgs(::GLVisualizeBackend) = [
|
||||
# :annotation,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# :fillalpha,
|
||||
# :foreground_color,
|
||||
# :group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
# :linecolor,
|
||||
# :linestyle,
|
||||
:linetype
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linewidth,
|
||||
# :linealpha,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :markeralpha,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
# :tickfont,
|
||||
# :guidefont,
|
||||
# :legendfont,
|
||||
# :grid,
|
||||
# :surface
|
||||
# :levels,
|
||||
]
|
||||
supportedAxes(::GLVisualizeBackend) = [:auto, :left]
|
||||
supportedTypes(::GLVisualizeBackend) = [:surface] #, :path, :scatter ,:steppre, :steppost, :sticks, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :contour]
|
||||
supportedStyles(::GLVisualizeBackend) = [:auto, :solid] #, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::GLVisualizeBackend) = [:none, :auto, :ellipse] #, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape)
|
||||
supportedScales(::GLVisualizeBackend) = [:identity] #, :log, :log2, :log10, :asinh, :sqrt]
|
||||
subplotSupported(::GLVisualizeBackend) = false
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _initialize_backend(::GLVisualizeBackend; kw...)
|
||||
@eval begin
|
||||
import GLVisualize
|
||||
export GLVisualize
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
immutable GLScreenWrapper
|
||||
window
|
||||
end
|
||||
|
||||
function _create_plot(pkg::GLVisualizeBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
|
||||
# TODO: this should be moved to the display method?
|
||||
w=GLVisualize.glscreen()
|
||||
@async GLVisualize.renderloop(w)
|
||||
Plot(GLScreenWrapper(w), pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::GLVisualizeBackend, plt::Plot, d::KW)
|
||||
# TODO: add one series to the underlying package
|
||||
push!(plt.seriesargs, d)
|
||||
# TODO: this should be moved to the display method?
|
||||
x,y,z=map(Float32,d[:x]), map(Float32,d[:y]), map(Float32,d[:z].surf)
|
||||
GLVisualize.view(GLVisualize.visualize((x*ones(y)', ones(x)*y', z), :surface),plt.o.window)
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{GLVisualizeBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
# TODO: add the annotation to the plot
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{GLVisualizeBackend})
|
||||
end
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{GLVisualizeBackend}, d::KW)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{GLVisualizeBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
function getxy(plt::Plot{GLVisualizeBackend}, i::Int)
|
||||
# TODO:
|
||||
# series = plt.o.lines[i]
|
||||
# series.x, series.y
|
||||
nothing, nothing
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{GLVisualizeBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
# TODO:
|
||||
# series = plt.o.lines[i]
|
||||
# series.x, series.y = xy
|
||||
plt
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{GLVisualizeBackend})
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{GLVisualizeBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{GLVisualizeBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{GLVisualizeBackend})
|
||||
# TODO: write a png to io
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{GLVisualizeBackend})
|
||||
# TODO: display/show the plot
|
||||
|
||||
# NOTE: I think maybe this should be empty? We can start with the assumption that creating
|
||||
# and adding to a plot will automatically open a window and draw to it, then the display
|
||||
# wouldn't actually need to do anything
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Subplot{GLVisualizeBackend})
|
||||
# TODO: display/show the subplot
|
||||
end
|
||||
@@ -0,0 +1,910 @@
|
||||
|
||||
# https://github.com/jheinen/GR.jl
|
||||
|
||||
|
||||
supportedArgs(::GRBackend) = [
|
||||
:annotation,
|
||||
:background_color, :foreground_color, :color_palette,
|
||||
:group,
|
||||
:label,
|
||||
:linetype,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor, :linestyle, :linewidth, :linealpha,
|
||||
:markershape, :markercolor, :markersize, :markeralpha,
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins,
|
||||
:n, :nc, :nr, :layout,
|
||||
:smooth,
|
||||
:title, :windowtitle, :show, :size,
|
||||
:x, :xlabel, :xlims, :xticks, :xscale, :xflip,
|
||||
:y, :ylabel, :ylims, :yticks, :yscale, :yflip,
|
||||
:axis, :yrightlabel,
|
||||
:z, :zlabel, :zlims, :zticks, :zscale, :zflip,
|
||||
:z,
|
||||
:tickfont, :guidefont, :legendfont,
|
||||
:grid, :legend, :colorbar,
|
||||
:marker_z, :levels,
|
||||
:xerror, :yerror,
|
||||
:ribbon, :quiver,
|
||||
:orientation,
|
||||
:overwrite_figure,
|
||||
:polar,
|
||||
]
|
||||
supportedAxes(::GRBackend) = _allAxes
|
||||
supportedTypes(::GRBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
|
||||
:scatter, :hist2d, :hexbin, :hist, :density, :bar,
|
||||
:hline, :vline, :contour, :heatmap, :path3d, :scatter3d, :surface,
|
||||
:wireframe, :ohlc, :pie]
|
||||
supportedStyles(::GRBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::GRBackend) = vcat(_allMarkers, Shape)
|
||||
supportedScales(::GRBackend) = [:identity, :log10]
|
||||
subplotSupported(::GRBackend) = true
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _initialize_backend(::GRBackend; kw...)
|
||||
@eval begin
|
||||
import GR
|
||||
export GR
|
||||
end
|
||||
end
|
||||
|
||||
const gr_linetype = KW(
|
||||
:auto => 1, :solid => 1, :dash => 2, :dot => 3, :dashdot => 4,
|
||||
:dashdotdot => -1 )
|
||||
|
||||
const gr_markertype = KW(
|
||||
:auto => 1, :none => -1, :ellipse => -1, :rect => -7, :diamond => -13,
|
||||
:utriangle => -3, :dtriangle => -5, :pentagon => -21, :hexagon => -22,
|
||||
:heptagon => -23, :octagon => -24, :cross => 2, :xcross => 5,
|
||||
:star4 => -25, :star5 => -26, :star6 => -27, :star7 => -28, :star8 => -29,
|
||||
:vline => -30, :hline => -31 )
|
||||
|
||||
const gr_halign = KW(:left => 1, :hcenter => 2, :right => 3)
|
||||
const gr_valign = KW(:top => 1, :vcenter => 3, :bottom => 5)
|
||||
|
||||
const gr_font_family = Dict(
|
||||
"times" => 1, "helvetica" => 5, "courier" => 9, "bookman" => 14,
|
||||
"newcenturyschlbk" => 18, "avantgarde" => 22, "palatino" => 26)
|
||||
|
||||
function gr_getcolorind(v)
|
||||
c = getColor(v)
|
||||
return convert(Int, GR.inqcolorfromrgb(c.r, c.g, c.b))
|
||||
end
|
||||
|
||||
function gr_getaxisind(p)
|
||||
axis = get(p, :axis, :none)
|
||||
if axis in [:none, :left]
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
end
|
||||
|
||||
function gr_setmarkershape(p)
|
||||
if haskey(p, :markershape)
|
||||
shape = p[:markershape]
|
||||
if isa(shape, Shape)
|
||||
p[:vertices] = vertices(shape)
|
||||
else
|
||||
GR.setmarkertype(gr_markertype[shape])
|
||||
p[:vertices] = :none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gr_polymarker(p, x, y)
|
||||
if p[:vertices] != :none
|
||||
vertices= p[:vertices]
|
||||
dx = Float64[el[1] for el in vertices] * 0.01
|
||||
dy = Float64[el[2] for el in vertices] * 0.01
|
||||
GR.selntran(0)
|
||||
GR.setfillcolorind(gr_getcolorind(p[:markercolor]))
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
for i = 1:length(x)
|
||||
xn, yn = GR.wctondc(x[i], y[i])
|
||||
GR.fillarea(xn + dx, yn + dy)
|
||||
end
|
||||
GR.selntran(1)
|
||||
else
|
||||
GR.polymarker(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function gr_polyline(x, y)
|
||||
if NaN in x || NaN in y
|
||||
i = 1
|
||||
j = 1
|
||||
n = length(x)
|
||||
while i < n
|
||||
while j < n && x[j] != Nan && y[j] != NaN
|
||||
j += 1
|
||||
end
|
||||
if i < j
|
||||
GR.polyline(x[i:j], y[i:j])
|
||||
end
|
||||
i = j + 1
|
||||
end
|
||||
else
|
||||
GR.polyline(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function gr_polaraxes(rmin, rmax)
|
||||
GR.savestate()
|
||||
GR.setlinecolorind(88)
|
||||
tick = 0.5 * GR.tick(rmin, rmax)
|
||||
n = round(Int, (rmax - rmin) / tick + 0.5)
|
||||
for i in 0:n
|
||||
r = float(i) / n
|
||||
if i % 2 == 0
|
||||
GR.setlinecolorind(88)
|
||||
if i > 0
|
||||
GR.drawarc(-r, r, -r, r, 0, 359)
|
||||
end
|
||||
GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF)
|
||||
x, y = GR.wctondc(0.05, r)
|
||||
GR.text(x, y, @sprintf("%g", rmin + i * tick))
|
||||
else
|
||||
GR.setlinecolorind(90)
|
||||
end
|
||||
end
|
||||
for alpha in 0:45:315
|
||||
a = alpha + 90
|
||||
sinf = sin(a * pi / 180)
|
||||
cosf = cos(a * pi / 180)
|
||||
GR.polyline([sinf, 0], [cosf, 0])
|
||||
GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF)
|
||||
x, y = GR.wctondc(1.1 * sinf, 1.1 * cosf)
|
||||
GR.textext(x, y, string(alpha, "^o"))
|
||||
end
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
function gr_display(plt::Plot{GRBackend}, clear=true, update=true,
|
||||
subplot=[0, 1, 0, 1])
|
||||
d = plt.plotargs
|
||||
|
||||
clear && GR.clearws()
|
||||
|
||||
mwidth, mheight, width, height = GR.inqdspsize()
|
||||
w, h = d[:size]
|
||||
viewport = zeros(4)
|
||||
vp = float(subplot)
|
||||
if w > h
|
||||
ratio = float(h) / w
|
||||
msize = mwidth * w / width
|
||||
GR.setwsviewport(0, msize, 0, msize * ratio)
|
||||
GR.setwswindow(0, 1, 0, ratio)
|
||||
vp[3] *= ratio
|
||||
vp[4] *= ratio
|
||||
else
|
||||
ratio = float(w) / h
|
||||
msize = mheight * h / height
|
||||
GR.setwsviewport(0, msize * ratio, 0, msize)
|
||||
GR.setwswindow(0, ratio, 0, 1)
|
||||
vp[1] *= ratio
|
||||
vp[2] *= ratio
|
||||
end
|
||||
viewport[1] = vp[1] + 0.125 * (vp[2] - vp[1])
|
||||
viewport[2] = vp[1] + 0.95 * (vp[2] - vp[1])
|
||||
viewport[3] = vp[3] + 0.125 * (vp[4] - vp[3])
|
||||
if w > h
|
||||
viewport[3] += (1 - (subplot[4] - subplot[3])^2) * 0.02
|
||||
end
|
||||
viewport[4] = vp[3] + 0.95 * (vp[4] - vp[3])
|
||||
|
||||
if haskey(d, :background_color)
|
||||
GR.savestate()
|
||||
GR.selntran(0)
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
GR.setfillcolorind(gr_getcolorind(d[:background_color]))
|
||||
GR.fillrect(vp[1], vp[2], vp[3], vp[4])
|
||||
GR.selntran(1)
|
||||
GR.restorestate()
|
||||
c = getColor(d[:background_color])
|
||||
if 0.21 * c.r + 0.72 * c.g + 0.07 * c.b < 0.9
|
||||
fg = convert(Int, GR.inqcolorfromrgb(1-c.r, 1-c.g, 1-c.b))
|
||||
else
|
||||
fg = 1
|
||||
end
|
||||
else
|
||||
fg = 1
|
||||
end
|
||||
|
||||
extrema = zeros(2, 4)
|
||||
num_axes = 1
|
||||
cmap = false
|
||||
axes_2d = true
|
||||
grid_flag = get(d, :grid, true)
|
||||
outside_ticks = false
|
||||
|
||||
for axis = 1:2
|
||||
xmin = ymin = typemax(Float64)
|
||||
xmax = ymax = typemin(Float64)
|
||||
for p in plt.seriesargs
|
||||
lt = p[:linetype]
|
||||
if get(d, :polar, false)
|
||||
lt = :polar
|
||||
end
|
||||
if axis == gr_getaxisind(p)
|
||||
if axis == 2
|
||||
num_axes = 2
|
||||
end
|
||||
if lt == :bar
|
||||
x, y = 1:length(p[:y]), p[:y]
|
||||
elseif lt == :ohlc
|
||||
x, y = 1:size(p[:y], 1), p[:y]
|
||||
elseif lt in [:hist, :density]
|
||||
x, y = Base.hist(p[:y])
|
||||
elseif lt in [:hist2d, :hexbin]
|
||||
E = zeros(length(p[:x]),2)
|
||||
E[:,1] = p[:x]
|
||||
E[:,2] = p[:y]
|
||||
if isa(p[:bins], Tuple)
|
||||
xbins, ybins = p[:bins]
|
||||
else
|
||||
xbins = ybins = p[:bins]
|
||||
end
|
||||
cmap = true
|
||||
x, y, H = Base.hist2d(E, xbins, ybins)
|
||||
elseif lt in [:pie, :polar]
|
||||
axes_2d = false
|
||||
xmin, xmax, ymin, ymax = 0, 1, 0, 1
|
||||
x, y = p[:x], p[:y]
|
||||
else
|
||||
if lt in [:contour, :surface, :heatmap]
|
||||
cmap = true
|
||||
end
|
||||
if lt in [:surface, :wireframe, :path3d, :scatter3d]
|
||||
axes_2d = false
|
||||
end
|
||||
if lt == :heatmap
|
||||
outside_ticks = true
|
||||
end
|
||||
x, y = p[:x], p[:y]
|
||||
end
|
||||
if !(lt in [:pie, :polar])
|
||||
xmin = min(minimum(x), xmin)
|
||||
xmax = max(maximum(x), xmax)
|
||||
if lt == :ohlc
|
||||
for val in y
|
||||
ymin = min(val.open, val.high, val.low, val.close, ymin)
|
||||
ymax = max(val.open, val.high, val.low, val.close, ymax)
|
||||
end
|
||||
else
|
||||
ymin = min(minimum(y), ymin)
|
||||
ymax = max(maximum(y), ymax)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if d[:xlims] != :auto
|
||||
xmin, xmax = d[:xlims]
|
||||
end
|
||||
if d[:ylims] != :auto
|
||||
ymin, ymax = d[:ylims]
|
||||
end
|
||||
if xmax <= xmin
|
||||
xmax = xmin + 1
|
||||
end
|
||||
if ymax <= ymin
|
||||
ymax = ymin + 1
|
||||
end
|
||||
extrema[axis,:] = [xmin, xmax, ymin, ymax]
|
||||
end
|
||||
|
||||
if num_axes == 2 || !axes_2d
|
||||
viewport[2] -= 0.0525
|
||||
end
|
||||
if cmap
|
||||
viewport[2] -= 0.1
|
||||
end
|
||||
GR.setviewport(viewport[1], viewport[2], viewport[3], viewport[4])
|
||||
|
||||
scale = 0
|
||||
d[:xscale] == :log10 && (scale |= GR.OPTION_X_LOG)
|
||||
d[:yscale] == :log10 && (scale |= GR.OPTION_Y_LOG)
|
||||
get(d, :xflip, false) && (scale |= GR.OPTION_FLIP_X)
|
||||
get(d, :yflip, false) && (scale |= GR.OPTION_FLIP_Y)
|
||||
|
||||
for axis = 1:num_axes
|
||||
xmin, xmax, ymin, ymax = extrema[axis,:]
|
||||
if scale & GR.OPTION_X_LOG == 0
|
||||
xmin, xmax = GR.adjustlimits(xmin, xmax)
|
||||
majorx = 5
|
||||
xtick = GR.tick(xmin, xmax) / majorx
|
||||
else
|
||||
xtick = majorx = 1
|
||||
end
|
||||
if scale & GR.OPTION_Y_LOG == 0
|
||||
ymin, ymax = GR.adjustlimits(ymin, ymax)
|
||||
majory = 5
|
||||
ytick = GR.tick(ymin, ymax) / majory
|
||||
else
|
||||
ytick = majory = 1
|
||||
end
|
||||
if scale & GR.OPTION_FLIP_X == 0
|
||||
xorg = (xmin, xmax)
|
||||
else
|
||||
xorg = (xmax, xmin)
|
||||
end
|
||||
if scale & GR.OPTION_FLIP_Y == 0
|
||||
yorg = (ymin, ymax)
|
||||
else
|
||||
yorg = (ymax, ymin)
|
||||
end
|
||||
|
||||
GR.setwindow(xmin, xmax, ymin, ymax)
|
||||
GR.setscale(scale)
|
||||
|
||||
diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
charheight = max(0.018 * diag, 0.01)
|
||||
GR.setcharheight(charheight)
|
||||
GR.settextcolorind(fg)
|
||||
|
||||
if axes_2d
|
||||
GR.setlinewidth(1)
|
||||
GR.setlinecolorind(fg)
|
||||
ticksize = 0.0075 * diag
|
||||
if outside_ticks
|
||||
ticksize = -ticksize
|
||||
end
|
||||
if grid_flag && fg == 1
|
||||
GR.grid(xtick, ytick, 0, 0, majorx, majory)
|
||||
end
|
||||
if num_axes == 1
|
||||
GR.axes(xtick, ytick, xorg[1], yorg[1], majorx, majory, ticksize)
|
||||
GR.axes(xtick, ytick, xorg[2], yorg[2], -majorx, -majory, -ticksize)
|
||||
elseif axis == 1
|
||||
GR.axes(xtick, ytick, xorg[1], yorg[1], majorx, majory, ticksize)
|
||||
else
|
||||
GR.axes(xtick, ytick, xorg[2], yorg[2], -majorx, majory, -ticksize)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if get(d, :title, "") != ""
|
||||
GR.savestate()
|
||||
GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP)
|
||||
GR.settextcolorind(fg)
|
||||
GR.text(0.5 * (viewport[1] + viewport[2]), vp[4], d[:title])
|
||||
GR.restorestate()
|
||||
end
|
||||
if get(d, :xlabel, "") != ""
|
||||
GR.savestate()
|
||||
GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM)
|
||||
GR.settextcolorind(fg)
|
||||
GR.text(0.5 * (viewport[1] + viewport[2]), vp[3], d[:xlabel])
|
||||
GR.restorestate()
|
||||
end
|
||||
if get(d, :ylabel, "") != ""
|
||||
GR.savestate()
|
||||
GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP)
|
||||
GR.setcharup(-1, 0)
|
||||
GR.settextcolorind(fg)
|
||||
GR.text(vp[1], 0.5 * (viewport[3] + viewport[4]), d[:ylabel])
|
||||
GR.restorestate()
|
||||
end
|
||||
if get(d, :yrightlabel, "") != ""
|
||||
GR.savestate()
|
||||
GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP)
|
||||
GR.setcharup(1, 0)
|
||||
GR.settextcolorind(fg)
|
||||
GR.text(vp[2], 0.5 * (viewport[3] + viewport[4]), d[:yrightlabel])
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
legend = false
|
||||
|
||||
for p in plt.seriesargs
|
||||
lt = p[:linetype]
|
||||
if get(d, :polar, false)
|
||||
lt = :polar
|
||||
end
|
||||
GR.savestate()
|
||||
xmin, xmax, ymin, ymax = extrema[gr_getaxisind(p),:]
|
||||
GR.setwindow(xmin, xmax, ymin, ymax)
|
||||
if lt in [:path, :line, :steppre, :steppost, :sticks, :hline, :vline, :ohlc, :polar]
|
||||
haskey(p, :linestyle) && GR.setlinetype(gr_linetype[p[:linestyle]])
|
||||
haskey(p, :linewidth) && GR.setlinewidth(p[:linewidth])
|
||||
haskey(p, :linecolor) && GR.setlinecolorind(gr_getcolorind(p[:linecolor]))
|
||||
end
|
||||
if lt == :path
|
||||
if haskey(p, :fillcolor)
|
||||
GR.setfillcolorind(gr_getcolorind(p[:fillcolor]))
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
end
|
||||
if length(p[:x]) > 1
|
||||
if p[:fillrange] != nothing
|
||||
GR.fillarea([p[:x][1]; p[:x]; p[:x][length(p[:x])]], [p[:fillrange]; p[:y]; p[:fillrange]])
|
||||
end
|
||||
GR.polyline(p[:x], p[:y])
|
||||
end
|
||||
legend = true
|
||||
end
|
||||
if lt == :line
|
||||
if length(p[:x]) > 1
|
||||
gr_polyline(p[:x], p[:y])
|
||||
end
|
||||
legend = true
|
||||
elseif lt in [:steppre, :steppost]
|
||||
n = length(p[:x])
|
||||
x = zeros(2*n + 1)
|
||||
y = zeros(2*n + 1)
|
||||
x[1], y[1] = p[:x][1], p[:y][1]
|
||||
j = 2
|
||||
for i = 2:n
|
||||
if lt == :steppre
|
||||
x[j], x[j+1] = p[:x][i-1], p[:x][i]
|
||||
y[j], y[j+1] = p[:y][i], p[:y][i]
|
||||
else
|
||||
x[j], x[j+1] = p[:x][i], p[:x][i]
|
||||
y[j], y[j+1] = p[:y][i-1], p[:y][i]
|
||||
end
|
||||
j += 2
|
||||
end
|
||||
if n > 1
|
||||
GR.polyline(x, y)
|
||||
end
|
||||
legend = true
|
||||
elseif lt == :sticks
|
||||
x, y = p[:x], p[:y]
|
||||
for i = 1:length(y)
|
||||
GR.polyline([x[i], x[i]], [ymin, y[i]])
|
||||
end
|
||||
legend = true
|
||||
elseif lt == :scatter || (p[:markershape] != :none && axes_2d)
|
||||
haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor]))
|
||||
gr_setmarkershape(p)
|
||||
if haskey(d, :markersize)
|
||||
if typeof(p[:markersize]) <: Number
|
||||
GR.setmarkersize(p[:markersize] / 4.0)
|
||||
if length(p[:x]) > 0
|
||||
gr_polymarker(p, p[:x], p[:y])
|
||||
end
|
||||
else
|
||||
c = p[:markercolor]
|
||||
GR.setcolormap(-GR.COLORMAP_GLOWING)
|
||||
for i = 1:length(p[:x])
|
||||
if isa(c, ColorGradient) && p[:marker_z] != nothing
|
||||
ci = round(Int, 1000 + p[:marker_z][i] * 255)
|
||||
GR.setmarkercolorind(ci)
|
||||
end
|
||||
GR.setmarkersize(p[:markersize][i] / 4.0)
|
||||
gr_polymarker(p, [p[:x][i]], [p[:y][i]])
|
||||
end
|
||||
end
|
||||
else
|
||||
if length(p[:x]) > 0
|
||||
gr_polymarker(p, p[:x], p[:y])
|
||||
end
|
||||
end
|
||||
legend = true
|
||||
elseif lt == :bar
|
||||
y = p[:y]
|
||||
for i = 1:length(y)
|
||||
GR.setfillcolorind(gr_getcolorind(p[:fillcolor]))
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
GR.fillrect(i-0.4, i+0.4, max(0, ymin), y[i])
|
||||
GR.setfillcolorind(1)
|
||||
GR.setfillintstyle(GR.INTSTYLE_HOLLOW)
|
||||
GR.fillrect(i-0.4, i+0.4, max(0, ymin), y[i])
|
||||
end
|
||||
elseif lt in [:hist, :density]
|
||||
h = Base.hist(p[:y])
|
||||
x, y = float(collect(h[1])), float(h[2])
|
||||
for i = 2:length(y)
|
||||
GR.setfillcolorind(gr_getcolorind(p[:fillcolor]))
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
GR.fillrect(x[i-1], x[i], ymin, y[i])
|
||||
GR.setfillcolorind(1)
|
||||
GR.setfillintstyle(GR.INTSTYLE_HOLLOW)
|
||||
GR.fillrect(x[i-1], x[i], ymin, y[i])
|
||||
end
|
||||
elseif lt in [:hline, :vline]
|
||||
for xy in p[:y]
|
||||
if lt == :hline
|
||||
GR.polyline([xmin, xmax], [xy, xy])
|
||||
else
|
||||
GR.polyline([xy, xy], [ymin, ymax])
|
||||
end
|
||||
end
|
||||
elseif lt in [:hist2d, :hexbin]
|
||||
E = zeros(length(p[:x]),2)
|
||||
E[:,1] = p[:x]
|
||||
E[:,2] = p[:y]
|
||||
if isa(p[:bins], Tuple)
|
||||
xbins, ybins = p[:bins]
|
||||
else
|
||||
xbins = ybins = p[:bins]
|
||||
end
|
||||
x, y, H = Base.hist2d(E, xbins, ybins)
|
||||
counts = round(Int32, 1000 + 255 * H / maximum(H))
|
||||
n, m = size(counts)
|
||||
GR.setcolormap(GR.COLORMAP_COOLWARM)
|
||||
GR.cellarray(xmin, xmax, ymin, ymax, n, m, counts)
|
||||
GR.setviewport(viewport[2] + 0.02, viewport[2] + 0.05,
|
||||
viewport[3], viewport[4])
|
||||
GR.setspace(0, maximum(counts), 0, 90)
|
||||
diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
charheight = max(0.016 * diag, 0.01)
|
||||
GR.setcharheight(charheight)
|
||||
GR.colormap()
|
||||
elseif lt == :contour
|
||||
x, y, z = p[:x], p[:y], p[:z].surf
|
||||
zmin, zmax = minimum(z), maximum(z)
|
||||
if typeof(p[:levels]) <: Array
|
||||
h = p[:levels]
|
||||
else
|
||||
h = linspace(zmin, zmax, p[:levels])
|
||||
end
|
||||
GR.setspace(zmin, zmax, 0, 90)
|
||||
GR.setcolormap(GR.COLORMAP_COOLWARM)
|
||||
GR.contour(x, y, h, reshape(z, length(x) * length(y)), 1000)
|
||||
GR.setviewport(viewport[2] + 0.02, viewport[2] + 0.05,
|
||||
viewport[3], viewport[4])
|
||||
l = round(Int32, 1000 + (h - minimum(h)) / (maximum(h) - minimum(h)) * 255)
|
||||
GR.setwindow(xmin, xmax, zmin, zmax)
|
||||
GR.cellarray(xmin, xmax, zmax, zmin, 1, length(l), l)
|
||||
ztick = 0.5 * GR.tick(zmin, zmax)
|
||||
diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
charheight = max(0.016 * diag, 0.01)
|
||||
GR.setcharheight(charheight)
|
||||
GR.axes(0, ztick, xmax, zmin, 0, 1, 0.005)
|
||||
elseif lt in [:surface, :wireframe]
|
||||
x, y, z = p[:x], p[:y], p[:z].surf
|
||||
zmin, zmax = GR.adjustrange(minimum(z), maximum(z))
|
||||
GR.setspace(zmin, zmax, 40, 70)
|
||||
xtick = GR.tick(xmin, xmax) / 2
|
||||
ytick = GR.tick(ymin, ymax) / 2
|
||||
ztick = GR.tick(zmin, zmax) / 2
|
||||
diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
charheight = max(0.018 * diag, 0.01)
|
||||
ticksize = 0.01 * (viewport[2] - viewport[1])
|
||||
GR.setlinewidth(1)
|
||||
if grid_flag
|
||||
GR.grid3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2)
|
||||
GR.grid3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0)
|
||||
end
|
||||
z = reshape(z, length(x) * length(y))
|
||||
if lt == :surface
|
||||
GR.setcolormap(GR.COLORMAP_COOLWARM)
|
||||
GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH)
|
||||
else
|
||||
GR.setfillcolorind(0)
|
||||
GR.surface(x, y, z, GR.OPTION_FILLED_MESH)
|
||||
end
|
||||
GR.setlinewidth(1)
|
||||
GR.setcharheight(charheight)
|
||||
GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize)
|
||||
GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize)
|
||||
if cmap
|
||||
GR.setviewport(viewport[2] + 0.07, viewport[2] + 0.1,
|
||||
viewport[3], viewport[4])
|
||||
GR.colormap()
|
||||
end
|
||||
elseif lt == :heatmap
|
||||
x, y, z = p[:x], p[:y], p[:z].surf
|
||||
zmin, zmax = GR.adjustrange(minimum(z), maximum(z))
|
||||
GR.setspace(zmin, zmax, 0, 90)
|
||||
GR.setcolormap(GR.COLORMAP_COOLWARM)
|
||||
z = reshape(z, length(x) * length(y))
|
||||
GR.surface(x, y, z, GR.OPTION_CELL_ARRAY)
|
||||
if cmap
|
||||
GR.setviewport(viewport[2] + 0.02, viewport[2] + 0.05,
|
||||
viewport[3], viewport[4])
|
||||
GR.colormap()
|
||||
end
|
||||
elseif lt in [:path3d, :scatter3d]
|
||||
x, y, z = p[:x], p[:y], p[:z]
|
||||
zmin, zmax = GR.adjustrange(minimum(z), maximum(z))
|
||||
GR.setspace(zmin, zmax, 40, 70)
|
||||
xtick = GR.tick(xmin, xmax) / 2
|
||||
ytick = GR.tick(ymin, ymax) / 2
|
||||
ztick = GR.tick(zmin, zmax) / 2
|
||||
diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
charheight = max(0.018 * diag, 0.01)
|
||||
ticksize = 0.01 * (viewport[2] - viewport[1])
|
||||
GR.setlinewidth(1)
|
||||
if grid_flag && lt == :path3d
|
||||
GR.grid3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2)
|
||||
GR.grid3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0)
|
||||
end
|
||||
if lt == :scatter3d
|
||||
haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor]))
|
||||
gr_setmarkershape(p)
|
||||
for i = 1:length(z)
|
||||
px, py = GR.wc3towc(x[i], y[i], z[i])
|
||||
gr_polymarker(p, [px], [py])
|
||||
end
|
||||
else
|
||||
haskey(p, :linewidth) && GR.setlinewidth(p[:linewidth])
|
||||
if length(x) > 0
|
||||
GR.polyline3d(x, y, z)
|
||||
end
|
||||
end
|
||||
GR.setlinewidth(1)
|
||||
GR.setcharheight(charheight)
|
||||
GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize)
|
||||
GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize)
|
||||
elseif lt == :ohlc
|
||||
y = p[:y]
|
||||
n = size(y, 1)
|
||||
ticksize = 0.5 * (xmax - xmin) / n
|
||||
for i in 1:n
|
||||
GR.polyline([i-ticksize, i], [y[i].open, y[i].open])
|
||||
GR.polyline([i, i], [y[i].low, y[i].high])
|
||||
GR.polyline([i, i+ticksize], [y[i].close, y[i].close])
|
||||
end
|
||||
elseif lt == :pie
|
||||
GR.selntran(0)
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
xmin, xmax, ymin, ymax = viewport
|
||||
ymax -= 0.05 * (xmax - xmin)
|
||||
xcenter = 0.5 * (xmin + xmax)
|
||||
ycenter = 0.5 * (ymin + ymax)
|
||||
if xmax - xmin > ymax - ymin
|
||||
r = 0.5 * (ymax - ymin)
|
||||
xmin, xmax = xcenter - r, xcenter + r
|
||||
else
|
||||
r = 0.5 * (xmax - xmin)
|
||||
ymin, ymax = ycenter - r, ycenter + r
|
||||
end
|
||||
labels, slices = p[:x], p[:y]
|
||||
numslices = length(slices)
|
||||
total = sum(slices)
|
||||
a1 = 0
|
||||
x = zeros(3)
|
||||
y = zeros(3)
|
||||
for i in 1:numslices
|
||||
a2 = round(Int, a1 + (slices[i] / total) * 360.0)
|
||||
GR.setfillcolorind(980 + (i-1) % 20)
|
||||
GR.fillarc(xmin, xmax, ymin, ymax, a1, a2)
|
||||
alpha = 0.5 * (a1 + a2)
|
||||
cosf = r * cos(alpha * pi / 180)
|
||||
sinf = r * sin(alpha * pi / 180)
|
||||
x[1] = xcenter + cosf
|
||||
y[1] = ycenter + sinf
|
||||
x[2] = x[1] + 0.1 * cosf
|
||||
y[2] = y[1] + 0.1 * sinf
|
||||
y[3] = y[2]
|
||||
if 90 <= alpha < 270
|
||||
x[3] = x[2] - 0.05
|
||||
GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF)
|
||||
GR.text(x[3] - 0.01, y[3], string(labels[i]))
|
||||
else
|
||||
x[3] = x[2] + 0.05
|
||||
GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF)
|
||||
GR.text(x[3] + 0.01, y[3], string(labels[i]))
|
||||
end
|
||||
GR.polyline(x, y)
|
||||
a1 = a2
|
||||
end
|
||||
GR.selntran(1)
|
||||
elseif lt == :polar
|
||||
xmin, xmax, ymin, ymax = viewport
|
||||
ymax -= 0.05 * (xmax - xmin)
|
||||
xcenter = 0.5 * (xmin + xmax)
|
||||
ycenter = 0.5 * (ymin + ymax)
|
||||
r = 0.5 * min(xmax - xmin, ymax - ymin)
|
||||
GR.setviewport(xcenter -r, xcenter + r, ycenter - r, ycenter + r)
|
||||
GR.setwindow(-1, 1, -1, 1)
|
||||
rmin, rmax = GR.adjustrange(minimum(r), maximum(r))
|
||||
gr_polaraxes(rmin, rmax)
|
||||
phi, r, = p[:x], p[:y]
|
||||
r = 0.5 * (r - rmin) / (rmax - rmin)
|
||||
n = length(r)
|
||||
x = zeros(n)
|
||||
y = zeros(n)
|
||||
for i in 1:n
|
||||
x[i] = r[i] * cos(phi[i])
|
||||
y[i] = r[i] * sin(phi[i])
|
||||
end
|
||||
GR.polyline(x, y)
|
||||
end
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
if d[:legend] != :none && legend
|
||||
GR.savestate()
|
||||
GR.selntran(0)
|
||||
GR.setscale(0)
|
||||
w = 0
|
||||
i = 0
|
||||
for p in plt.seriesargs
|
||||
if typeof(p[:label]) <: Array
|
||||
i += 1
|
||||
lab = p[:label][i]
|
||||
else
|
||||
lab = p[:label]
|
||||
end
|
||||
tbx, tby = GR.inqtext(0, 0, lab)
|
||||
w = max(w, tbx[3])
|
||||
end
|
||||
px = viewport[2] - 0.05 - w
|
||||
py = viewport[4] - 0.06
|
||||
dy = 0.03 * sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2)
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
GR.setfillcolorind(0)
|
||||
GR.fillrect(px - 0.08, px + w + 0.02, py + dy, py - dy * length(plt.seriesargs))
|
||||
GR.setlinetype(1)
|
||||
GR.setlinewidth(1)
|
||||
GR.drawrect(px - 0.08, px + w + 0.02, py + dy, py - dy * length(plt.seriesargs))
|
||||
haskey(d, :linewidth) && GR.setlinewidth(d[:linewidth])
|
||||
i = 0
|
||||
for p in plt.seriesargs
|
||||
if p[:linetype] in [:path, :line, :steppre, :steppost, :sticks]
|
||||
haskey(p, :linecolor) && GR.setlinecolorind(gr_getcolorind(p[:linecolor]))
|
||||
haskey(p, :linestyle) && GR.setlinetype(gr_linetype[p[:linestyle]])
|
||||
GR.polyline([px - 0.07, px - 0.01], [py, py])
|
||||
end
|
||||
if p[:linetype] == :scatter || p[:markershape] != :none
|
||||
haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor]))
|
||||
gr_setmarkershape(p)
|
||||
if p[:linetype] in [:path, :line, :steppre, :steppost, :sticks]
|
||||
gr_polymarker(p, [px - 0.06, px - 0.02], [py, py])
|
||||
else
|
||||
gr_polymarker(p, [px - 0.06, px - 0.04, px - 0.02], [py, py, py])
|
||||
end
|
||||
end
|
||||
if typeof(p[:label]) <: Array
|
||||
i += 1
|
||||
lab = p[:label][i]
|
||||
else
|
||||
lab = p[:label]
|
||||
end
|
||||
GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF)
|
||||
GR.settextcolorind(1)
|
||||
GR.text(px, py, lab)
|
||||
py -= dy
|
||||
end
|
||||
GR.selntran(1)
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
if haskey(d, :anns)
|
||||
GR.savestate()
|
||||
for ann in d[:anns]
|
||||
x, y, val = ann
|
||||
x, y = GR.wctondc(x, y)
|
||||
alpha = val.font.rotation
|
||||
family = lowercase(val.font.family)
|
||||
GR.setcharheight(0.7 * val.font.pointsize / d[:size][2])
|
||||
GR.setcharup(sin(val.font.rotation), cos(val.font.rotation))
|
||||
if haskey(gr_font_family, family)
|
||||
GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING)
|
||||
end
|
||||
GR.settextcolorind(gr_getcolorind(val.font.color))
|
||||
GR.settextalign(gr_halign[val.font.halign], gr_valign[val.font.valign])
|
||||
GR.text(x, y, val.str)
|
||||
end
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
update && GR.updatews()
|
||||
end
|
||||
|
||||
function gr_display(subplt::Subplot{GRBackend})
|
||||
clear = true
|
||||
update = false
|
||||
l = enumerate(subplt.layout)
|
||||
nr = nrows(subplt.layout)
|
||||
for (i, (r, c)) in l
|
||||
nc = ncols(subplt.layout, r)
|
||||
if i == length(l)
|
||||
update = true
|
||||
end
|
||||
subplot = [(c-1)/nc, c/nc, 1-r/nr, 1-(r-1)/nr]
|
||||
gr_display(subplt.plts[i], clear, update, subplot)
|
||||
clear = false
|
||||
end
|
||||
end
|
||||
|
||||
function _create_plot(pkg::GRBackend, d::KW)
|
||||
Plot(nothing, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
function _add_series(::GRBackend, plt::Plot, d::KW)
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{GRBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
if haskey(plt.plotargs, :anns)
|
||||
append!(plt.plotargs[:anns], anns)
|
||||
else
|
||||
plt.plotargs[:anns] = anns
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{GRBackend})
|
||||
end
|
||||
|
||||
function _update_plot(plt::Plot{GRBackend}, d::KW)
|
||||
for k in (:title, :xlabel, :ylabel)
|
||||
haskey(d, k) && (plt.plotargs[k] = d[k])
|
||||
end
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{GRBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function getxy(plt::Plot{GRBackend}, i::Int)
|
||||
d = plt.seriesargs[i]
|
||||
d[:x], d[:y]
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{GRBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
d[:x], d[:y] = xy
|
||||
plt
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{GRBackend}, isbefore::Bool)
|
||||
true
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{GRBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{GRBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, m::MIME"image/png", plt::AbstractPlot{GRBackend})
|
||||
GR.emergencyclosegks()
|
||||
ENV["GKS_WSTYPE"] = "png"
|
||||
gr_display(plt)
|
||||
GR.emergencyclosegks()
|
||||
write(io, readall("gks.png"))
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, m::MIME"image/svg+xml", plt::AbstractPlot{GRBackend})
|
||||
GR.emergencyclosegks()
|
||||
ENV["GKS_WSTYPE"] = "svg"
|
||||
gr_display(plt)
|
||||
GR.emergencyclosegks()
|
||||
write(io, readall("gks.svg"))
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, m::MIME"text/html", plt::AbstractPlot{GRBackend})
|
||||
writemime(io, MIME("image/svg+xml"), plt)
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, m::MIME"application/pdf", plt::AbstractPlot{GRBackend})
|
||||
GR.emergencyclosegks()
|
||||
ENV["GKS_WSTYPE"] = "pdf"
|
||||
gr_display(plt)
|
||||
GR.emergencyclosegks()
|
||||
write(io, readall("gks.pdf"))
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, m::MIME"application/postscript", plt::AbstractPlot{GRBackend})
|
||||
GR.emergencyclosegks()
|
||||
ENV["GKS_WSTYPE"] = "ps"
|
||||
gr_display(plt)
|
||||
GR.emergencyclosegks()
|
||||
write(io, readall("gks.ps"))
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{GRBackend})
|
||||
gr_display(plt)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Subplot{GRBackend})
|
||||
gr_display(plt)
|
||||
true
|
||||
end
|
||||
@@ -0,0 +1,180 @@
|
||||
|
||||
# https://github.com/JuliaGraphics/Immerse.jl
|
||||
|
||||
supportedArgs(::ImmerseBackend) = supportedArgs(GadflyBackend())
|
||||
supportedAxes(::ImmerseBackend) = supportedAxes(GadflyBackend())
|
||||
supportedTypes(::ImmerseBackend) = supportedTypes(GadflyBackend())
|
||||
supportedStyles(::ImmerseBackend) = supportedStyles(GadflyBackend())
|
||||
supportedMarkers(::ImmerseBackend) = supportedMarkers(GadflyBackend())
|
||||
supportedScales(::ImmerseBackend) = supportedScales(GadflyBackend())
|
||||
subplotSupported(::ImmerseBackend) = true
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
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"))
|
||||
end
|
||||
end
|
||||
|
||||
function createImmerseFigure(d::KW)
|
||||
w,h = d[:size]
|
||||
figidx = Immerse.figure(; name = d[:windowtitle], width = w, height = h)
|
||||
Immerse.Figure(figidx)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
# create a blank Gadfly.Plot object
|
||||
function _create_plot(pkg::ImmerseBackend, d::KW)
|
||||
# create the underlying Gadfly.Plot object
|
||||
gplt = createGadflyPlotObject(d)
|
||||
|
||||
# save both the Immerse.Figure and the Gadfly.Plot
|
||||
Plot((nothing,gplt), pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
# plot one data series
|
||||
function _add_series(::ImmerseBackend, plt::Plot, d::KW)
|
||||
addGadflySeries!(plt, d)
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
function _update_plot(plt::Plot{ImmerseBackend}, d::KW)
|
||||
updateGadflyGuides(plt, d)
|
||||
updateGadflyPlotTheme(plt, d)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{ImmerseBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
push!(getGadflyContext(plt).guides, createGadflyAnnotationObject(ann...))
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
function getxy(plt::Plot{ImmerseBackend}, i::Integer)
|
||||
mapping = getGadflyMappings(plt, i)[1]
|
||||
mapping[:x], mapping[:y]
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
for mapping in getGadflyMappings(plt, i)
|
||||
mapping[:x], mapping[:y] = xy
|
||||
end
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
function _create_subplot(subplt::Subplot{ImmerseBackend}, isbefore::Bool)
|
||||
return false
|
||||
# isbefore && return false
|
||||
end
|
||||
|
||||
function showSubplotObject(subplt::Subplot{ImmerseBackend})
|
||||
# create the Gtk window with vertical box vsep
|
||||
d = getplotargs(subplt,1)
|
||||
w,h = d[:size]
|
||||
vsep = Gtk.GtkBoxLeaf(:v)
|
||||
win = Gtk.GtkWindowLeaf(vsep, d[:windowtitle], w, h)
|
||||
|
||||
figindices = []
|
||||
row = Gtk.GtkBoxLeaf(:h)
|
||||
push!(vsep, row)
|
||||
for (i,(r,c)) in enumerate(subplt.layout)
|
||||
plt = subplt.plts[i]
|
||||
|
||||
# get the components... box is the main plot GtkBox, and canvas is the GtkCanvas where it's plotted
|
||||
box, toolbar, canvas = Immerse.createPlotGuiComponents()
|
||||
|
||||
# add the plot's box to the row
|
||||
push!(row, box)
|
||||
|
||||
# create the figure and store the index returned for destruction later
|
||||
figidx = Immerse.figure(canvas)
|
||||
push!(figindices, figidx)
|
||||
|
||||
fig = Immerse.figure(figidx)
|
||||
plt.o = (fig, plt.o[2])
|
||||
|
||||
# add the row
|
||||
if c == ncols(subplt.layout, r)
|
||||
row = Gtk.GtkBoxLeaf(:h)
|
||||
push!(vsep, row)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# destructor... clean up plots
|
||||
Gtk.on_signal_destroy((x...) -> ([Immerse.dropfig(Immerse._display,i) for i in figindices]; subplt.o = nothing), win)
|
||||
|
||||
subplt.o = win
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
function _remove_axis(plt::Plot{ImmerseBackend}, isx::Bool)
|
||||
gplt = getGadflyContext(plt)
|
||||
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
|
||||
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xlabel : Gadfly.Guide.ylabel, "")
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{ImmerseBackend}, isx::Bool)
|
||||
for l in getGadflyContext(plt).layers
|
||||
_expand_limits(lims, l.mapping[isx ? :x : :y])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
getGadflyContext(plt::Plot{ImmerseBackend}) = plt.o[2]
|
||||
getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt)
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend})
|
||||
|
||||
fig, gplt = plt.o
|
||||
if fig == nothing
|
||||
fig = createImmerseFigure(plt.plotargs)
|
||||
Gtk.on_signal_destroy((x...) -> (Immerse.dropfig(Immerse._display, fig.figno); plt.o = (nothing,gplt)), fig.canvas)
|
||||
plt.o = (fig, gplt)
|
||||
end
|
||||
|
||||
Immerse.figure(fig.figno; displayfig = false)
|
||||
display(gplt)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, subplt::Subplot{ImmerseBackend})
|
||||
|
||||
# if we haven't created the window yet, do it
|
||||
if subplt.o == nothing
|
||||
showSubplotObject(subplt)
|
||||
end
|
||||
|
||||
# display the plots by creating a fresh Immerse.Figure object from the GtkCanvas and Gadfly.Plot
|
||||
for plt in subplt.plts
|
||||
fig, gplt = plt.o
|
||||
Immerse.figure(fig.figno; displayfig = false)
|
||||
display(gplt)
|
||||
end
|
||||
|
||||
# o is the window... show it
|
||||
showall(subplt.o)
|
||||
end
|
||||
@@ -0,0 +1,354 @@
|
||||
# https://github.com/sisl/PGFPlots.jl
|
||||
|
||||
supportedArgs(::PGFPlotsBackend) = [
|
||||
# :annotation,
|
||||
# :axis,
|
||||
:background_color,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
:fillcolor,
|
||||
:fillalpha,
|
||||
# :foreground_color,
|
||||
# :group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
:linealpha,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
:markeralpha,
|
||||
# :markerstrokewidth,
|
||||
:markerstrokecolor,
|
||||
:markerstrokestyle,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
# :show,
|
||||
# :size,
|
||||
:title,
|
||||
# :windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
# :xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
:xscale,
|
||||
:yscale,
|
||||
:xflip,
|
||||
:yflip,
|
||||
:z,
|
||||
:zscale,
|
||||
# :tickfont,
|
||||
# :guidefont,
|
||||
# :legendfont,
|
||||
:grid,
|
||||
# :surface
|
||||
# :levels,
|
||||
]
|
||||
supportedAxes(::PGFPlotsBackend) = [:auto, :left]
|
||||
supportedTypes(::PGFPlotsBackend) = [:path, :path3d, :scatter, :line, :steppre, :stepmid, :steppost, :hist, :bar, :hist2d, :sticks, :ysticks, :xsticks, :contour] # :hexbin, :hline, :vline,]
|
||||
supportedStyles(::PGFPlotsBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::PGFPlotsBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
|
||||
supportedScales(::PGFPlotsBackend) = [:identity, :log, :ln, :log2, :log10] # :asinh, :sqrt]
|
||||
subplotSupported(::PGFPlotsBackend) = false
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _initialize_backend(::PGFPlotsBackend; kw...)
|
||||
@eval begin
|
||||
import PGFPlots
|
||||
export PGFPlots
|
||||
# TODO: other initialization that needs to be eval-ed
|
||||
end
|
||||
# TODO: other initialization
|
||||
end
|
||||
const _pgfplots_linestyles = KW(
|
||||
:solid => "solid",
|
||||
:dash => "dashed",
|
||||
:dot => "dotted",
|
||||
:dashdot => "dashdotted",
|
||||
:dashdotdot => "dashdotdotted"
|
||||
)
|
||||
|
||||
const _pgfplots_markers = KW(
|
||||
:none => "mark = none,",
|
||||
:cross => "mark = +,",
|
||||
:xcross => "mark = x,",
|
||||
:utriangle => "mark = triangle*,",
|
||||
:dtriangle => "mark = triangle*,",
|
||||
:ellipse => "mark = o*,",
|
||||
:rect => "mark = square*,",
|
||||
:star5 => "mark = star,",
|
||||
:star6 => "mark = asterisk,",
|
||||
:diamond => "mark = diamond*,",
|
||||
:pentagon => "mark = pentagon*,"
|
||||
)
|
||||
|
||||
function _pgfplots_get_color(kwargs, symb)
|
||||
c = typeof(kwargs[symb]) == Symbol ? convertColor(kwargs[symb]) : kwargs[symb].c
|
||||
"{rgb,1:red,$(float(c.r));green,$(float(c.g));blue,$(float(c.b))}"
|
||||
end
|
||||
|
||||
function _pgfplots_get_linestyle!(kwargs, plt)
|
||||
ls = plt[:linestyle]
|
||||
if haskey(_pgfplots_linestyles, ls)
|
||||
kwargs[:style] *= _pgfplots_linestyles[ls]*","
|
||||
end
|
||||
|
||||
kwargs[:style] *= "line width = $(plt[:linewidth]) pt"*","
|
||||
end
|
||||
|
||||
|
||||
function _pgfplots_get_marker!(kwargs, plt)
|
||||
# Control marker shape
|
||||
mark = plt[:markershape]
|
||||
kwargs[:style] *= _pgfplots_markers[mark]
|
||||
|
||||
# Control marker size
|
||||
kwargs[:style] *= "mark size = $(plt[:markersize]/2),"
|
||||
|
||||
# Control marker colors and alphas
|
||||
α = plt[:markeralpha] == nothing ? 1.0 : plt[:markeralpha]
|
||||
kwargs[:style] *= "mark options = {color=$(_pgfplots_get_color(plt, :markerstrokecolor)),"
|
||||
kwargs[:style] *= mark == :dtriangle ? "rotate=180," : ""
|
||||
kwargs[:style] *= "fill=$(_pgfplots_get_color(plt, :markercolor)),"
|
||||
kwargs[:style] *= "fill opacity = $α,"
|
||||
markstrokestyle = plt[:markerstrokestyle]
|
||||
if haskey(_pgfplots_linestyles, markstrokestyle)
|
||||
kwargs[:style] *= _pgfplots_linestyles[markstrokestyle]
|
||||
end
|
||||
kwargs[:style] *= "},"
|
||||
end
|
||||
|
||||
function _pgfplots_get_series_color!(kwargs, plt)
|
||||
α = plt[:seriesalpha] == nothing ? 1.0 : plt[:seriesalpha]
|
||||
kwargs[:style] *= "color=$(_pgfplots_get_color(plt, :seriescolor)),"
|
||||
kwargs[:style] *= "draw opacity = $α,"
|
||||
end
|
||||
|
||||
function _pgfplots_get_line_color!(kwargs, plt)
|
||||
α = plt[:linealpha] == nothing ? 1.0 : plt[:linealpha]
|
||||
kwargs[:style] *= "color=$(_pgfplots_get_color(plt, :linecolor)),"
|
||||
kwargs[:style] *= "draw opacity = $α,"
|
||||
end
|
||||
|
||||
function _pgfplots_get_fill_color!(kwargs, plt)
|
||||
α = plt[:fillalpha] == nothing ? 1.0 : plt[:fillalpha]
|
||||
kwargs[:style] *= "fill=$(_pgfplots_get_color(plt, :fillcolor)),"
|
||||
kwargs[:style] *= "fill opacity = $α,"
|
||||
end
|
||||
|
||||
function _pgfplots_get_plot_kwargs(plt)
|
||||
kwargs = KW()
|
||||
kwargs[:style] = ""
|
||||
_pgfplots_get_linestyle!(kwargs, plt)
|
||||
_pgfplots_get_marker!(kwargs, plt)
|
||||
_pgfplots_get_series_color!(kwargs, plt)
|
||||
kwargs
|
||||
end
|
||||
|
||||
function _pgfplots_axis(plt_series)
|
||||
line_type = plt_series[:linetype]
|
||||
plt_kwargs = _pgfplots_get_plot_kwargs(plt_series)
|
||||
if line_type == :path
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :path3d
|
||||
PGFPlots.Linear3(plt_series[:x], plt_series[:y], plt_series[:z]; plt_kwargs...)
|
||||
elseif line_type == :scatter
|
||||
PGFPlots.Scatter(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :steppre
|
||||
plt_kwargs[:style] *= "const plot mark right,"
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :stepmid
|
||||
plt_kwargs[:style] *= "const plot mark mid,"
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :steppost
|
||||
plt_kwargs[:style] *= "const plot,"
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :hist
|
||||
#TODO patch this in PGFPlots.jl instead; the problem is that PGFPlots will
|
||||
# save _all_ data points in the figure which can be quite heavy
|
||||
plt_hist = hist(plt_series[:y])
|
||||
plt_kwargs[:style] *= "ybar interval,"
|
||||
_pgfplots_get_line_color!(plt_kwargs, plt_series)
|
||||
_pgfplots_get_fill_color!(plt_kwargs, plt_series)
|
||||
PGFPlots.Linear(plt_hist[1][1:end-1]+plt_hist[1].step/2, plt_hist[2]; plt_kwargs...)
|
||||
elseif line_type == :hist2d
|
||||
PGFPlots.Histogram2(plt_series[:x], plt_series[:y])
|
||||
elseif line_type == :bar
|
||||
plt_kwargs[:style] *= "ybar,"
|
||||
_pgfplots_get_line_color!(plt_kwargs, plt_series)
|
||||
_pgfplots_get_fill_color!(plt_kwargs, plt_series)
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :sticks || line_type == :ysticks
|
||||
plt_kwargs[:style] *= "ycomb"
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :xsticks
|
||||
plt_kwargs[:style] *= "xcomb"
|
||||
PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
|
||||
elseif line_type == :contour
|
||||
PGFPlots.Contour(plt_series[:z].surf, plt_series[:x], plt_series[:y])
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _create_plot(pkg::PGFPlotsBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
Plot(nothing, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::PGFPlotsBackend, plt::Plot, d::KW)
|
||||
# TODO: add one series to the underlying package
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{PGFPlotsBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
# set or add to the annotation_list
|
||||
if haskey(plt.plotargs, :annotation_list)
|
||||
append!(plt.plotargs[:annotation_list], anns)
|
||||
else
|
||||
plt.plotargs[:annotation_list] = anns
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{PGFPlotsBackend})
|
||||
end
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{PGFPlotsBackend}, d::KW)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{PGFPlotsBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# function getxy(plt::Plot{PGFPlotsBackend}, i::Int)
|
||||
# d = plt.seriesargs[i]
|
||||
# d[:x], d[:y]
|
||||
# end
|
||||
#
|
||||
# function setxy!{X,Y}(plt::Plot{PGFPlotsBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
# d = plt.seriesargs[i]
|
||||
# d[:x], d[:y] = xy
|
||||
# plt
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{PGFPlotsBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
true
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{PGFPlotsBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{PGFPlotsBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _pgfplots_get_axis_kwargs(d)
|
||||
axisargs = KW()
|
||||
for arg in (:xlabel, :ylabel, :zlabel, :title)
|
||||
axisargs[arg] = d[arg]
|
||||
end
|
||||
axisargs[:style] = ""
|
||||
axisargs[:style] *= d[:xflip] == true ? "x dir=reverse," : ""
|
||||
axisargs[:style] *= d[:yflip] == true ? "y dir=reverse," : ""
|
||||
if d[:xscale] in (:log, :log2, :ln, :log10)
|
||||
axisargs[:xmode] = "log"
|
||||
if d[:xscale] == :log2
|
||||
axisargs[:style] *= "log basis x=2,"
|
||||
elseif d[:xscale] in (:log, :log10)
|
||||
axisargs[:style] *= "log basis x=10,"
|
||||
end
|
||||
end
|
||||
if d[:yscale] in (:log, :log2, :ln, :log10)
|
||||
axisargs[:ymode] = "log"
|
||||
if d[:yscale] == :log2
|
||||
axisargs[:style] *= "log basis y=2,"
|
||||
elseif d[:yscale] in (:log, :log10)
|
||||
axisargs[:style] *= "log basis x=10,"
|
||||
end
|
||||
end
|
||||
if d[:zscale] in (:log, :log2, :ln, :log10)
|
||||
axisargs[:zmode] = "log"
|
||||
if d[:zscale] == :log2
|
||||
axisargs[:style] *= "log basis z=2,"
|
||||
elseif d[:zscale] in (:log, :log10)
|
||||
axisargs[:style] *= "log basis x=10,"
|
||||
end
|
||||
end
|
||||
|
||||
# Control background color
|
||||
axisargs[:style] *= "axis background/.style={fill=$(_pgfplots_get_color(d, :background_color))},"
|
||||
# Control x/y-limits
|
||||
if d[:xlims] !== :auto
|
||||
axisargs[:xmin] = d[:xlims][1]
|
||||
axisargs[:xmax] = d[:xlims][2]
|
||||
end
|
||||
if d[:ylims] !== :auto
|
||||
axisargs[:ymin] = d[:ylims][1]
|
||||
axisargs[:ymax] = d[:ylims][2]
|
||||
end
|
||||
if d[:grid] == true
|
||||
axisargs[:style] *= "grid = major"
|
||||
elseif d[:grid] == false
|
||||
|
||||
end
|
||||
|
||||
axisargs
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
################# This is the important method to implement!!! #################
|
||||
function _make_pgf_plot(plt::Plot{PGFPlotsBackend})
|
||||
os = [_pgfplots_axis(plt_series) for plt_series in plt.seriesargs]
|
||||
axisargs =_pgfplots_get_axis_kwargs(plt.plotargs)
|
||||
plt.o = PGFPlots.Axis([os...]; axisargs...)
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, mime::MIME"image/svg+xml", plt::AbstractPlot{PGFPlotsBackend})
|
||||
plt.o = _make_pgf_plot(plt)
|
||||
writemime(io, mime, plt.o)
|
||||
end
|
||||
|
||||
# function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PGFPlotsBackend})
|
||||
# end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::AbstractPlot{PGFPlotsBackend})
|
||||
plt.o = _make_pgf_plot(plt)
|
||||
display(plt.o)
|
||||
end
|
||||
|
||||
# function Base.display(::PlotsDisplay, plt::Subplot{PGFPlotsBackend})
|
||||
# # TODO: display/show the subplot
|
||||
# end
|
||||
@@ -0,0 +1,592 @@
|
||||
|
||||
# https://plot.ly/javascript/getting-started
|
||||
|
||||
supportedArgs(::PlotlyBackend) = [
|
||||
:annotation,
|
||||
# :axis,
|
||||
:background_color,
|
||||
:color_palette,
|
||||
:fillrange,
|
||||
:fillcolor,
|
||||
:fillalpha,
|
||||
:foreground_color,
|
||||
:group,
|
||||
:label,
|
||||
:layout,
|
||||
:legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
:linealpha,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
:markeralpha,
|
||||
:markerstrokewidth,
|
||||
:markerstrokecolor,
|
||||
:markerstrokestyle,
|
||||
:n,
|
||||
:bins,
|
||||
:nc,
|
||||
:nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
:show,
|
||||
:size,
|
||||
:title,
|
||||
:windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
:xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
# :yrightlabel,
|
||||
:yticks,
|
||||
:xscale,
|
||||
:yscale,
|
||||
:xflip,
|
||||
:yflip,
|
||||
:z,
|
||||
:marker_z,
|
||||
:tickfont,
|
||||
:guidefont,
|
||||
:legendfont,
|
||||
:grid,
|
||||
:levels,
|
||||
:xerror,
|
||||
:yerror,
|
||||
:ribbon,
|
||||
:quiver,
|
||||
:orientation,
|
||||
:polar,
|
||||
]
|
||||
supportedAxes(::PlotlyBackend) = [:auto, :left]
|
||||
supportedTypes(::PlotlyBackend) = [:none, :line, :path, :scatter, :steppre, :steppost,
|
||||
:hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d,
|
||||
:pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline]
|
||||
supportedStyles(::PlotlyBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
supportedMarkers(::PlotlyBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross,
|
||||
:pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape)
|
||||
supportedScales(::PlotlyBackend) = [:identity, :log10] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
subplotSupported(::PlotlyBackend) = true
|
||||
stringsSupported(::PlotlyBackend) = true
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::PlotlyBackend; kw...)
|
||||
@eval begin
|
||||
import JSON
|
||||
JSON._print(io::IO, state::JSON.State, dt::Union{Date,DateTime}) = print(io, '"', dt, '"')
|
||||
|
||||
_js_path = Pkg.dir("Plots", "deps", "plotly-latest.min.js")
|
||||
_js_code = open(readall, _js_path, "r")
|
||||
|
||||
# borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2
|
||||
_js_script = """
|
||||
<script type='text/javascript'>
|
||||
define('plotly', function(require, exports, module) {
|
||||
$(_js_code)
|
||||
});
|
||||
require(['plotly'], function(Plotly) {
|
||||
window.Plotly = Plotly;
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
|
||||
# if we're in IJulia call setupnotebook to load js and css
|
||||
if isijulia()
|
||||
display("text/html", _js_script)
|
||||
end
|
||||
|
||||
# if isatom()
|
||||
# import Atom
|
||||
# Atom.@msg evaljs(_js_code)
|
||||
# end
|
||||
|
||||
end
|
||||
# TODO: other initialization
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _create_plot(pkg::PlotlyBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
Plot(nothing, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::PlotlyBackend, plt::Plot, d::KW)
|
||||
# TODO: add one series to the underlying package
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{PlotlyBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
# set or add to the annotation_list
|
||||
if haskey(plt.plotargs, :annotation_list)
|
||||
append!(plt.plotargs[:annotation_list], anns)
|
||||
else
|
||||
plt.plotargs[:annotation_list] = anns
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{PlotlyBackend})
|
||||
end
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{PlotlyBackend}, d::KW)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{PlotlyBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# function getxy(plt::Plot{PlotlyBackend}, i::Int)
|
||||
# d = plt.seriesargs[i]
|
||||
# d[:x], d[:y]
|
||||
# end
|
||||
#
|
||||
# function setxy!{X,Y}(plt::Plot{PlotlyBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
# d = plt.seriesargs[i]
|
||||
# d[:x], d[:y] = xy
|
||||
# plt
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{PlotlyBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
true
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{PlotlyBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{PlotlyBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# TODO:
|
||||
# _plotDefaults[:yrightlabel] = ""
|
||||
# _plotDefaults[:xlims] = :auto
|
||||
# _plotDefaults[:ylims] = :auto
|
||||
# _plotDefaults[:xticks] = :auto
|
||||
# _plotDefaults[:yticks] = :auto
|
||||
# _plotDefaults[:xscale] = :identity
|
||||
# _plotDefaults[:yscale] = :identity
|
||||
# _plotDefaults[:xflip] = false
|
||||
# _plotDefaults[:yflip] = false
|
||||
|
||||
function plotlyfont(font::Font, color = font.color)
|
||||
KW(
|
||||
:family => font.family,
|
||||
:size => round(Int, font.pointsize*1.4),
|
||||
:color => webcolor(color),
|
||||
)
|
||||
end
|
||||
|
||||
function get_annotation_dict(x, y, val::Union{AbstractString,Symbol})
|
||||
KW(
|
||||
:text => val,
|
||||
:xref => "x",
|
||||
:x => x,
|
||||
:yref => "y",
|
||||
:y => y,
|
||||
:showarrow => false,
|
||||
)
|
||||
end
|
||||
|
||||
function get_annotation_dict(x, y, ptxt::PlotText)
|
||||
merge(get_annotation_dict(x, y, ptxt.str), KW(
|
||||
:font => plotlyfont(ptxt.font),
|
||||
:xanchor => ptxt.font.halign == :hcenter ? :center : ptxt.font.halign,
|
||||
:yanchor => ptxt.font.valign == :vcenter ? :middle : ptxt.font.valign,
|
||||
:rotation => ptxt.font.rotation,
|
||||
))
|
||||
end
|
||||
|
||||
function plotlyscale(scale::Symbol)
|
||||
if scale == :log10
|
||||
"log"
|
||||
else
|
||||
"-"
|
||||
end
|
||||
end
|
||||
|
||||
use_axis_field(ticks) = !(ticks in (nothing, :none))
|
||||
|
||||
tickssym(isx::Bool) = symbol((isx ? "x" : "y") * "ticks")
|
||||
limssym(isx::Bool) = symbol((isx ? "x" : "y") * "lims")
|
||||
flipsym(isx::Bool) = symbol((isx ? "x" : "y") * "flip")
|
||||
scalesym(isx::Bool) = symbol((isx ? "x" : "y") * "scale")
|
||||
labelsym(isx::Bool) = symbol((isx ? "x" : "y") * "label")
|
||||
|
||||
function plotlyaxis(d::KW, isx::Bool)
|
||||
ax = KW(
|
||||
:title => d[labelsym(isx)],
|
||||
:showgrid => d[:grid],
|
||||
:zeroline => false,
|
||||
)
|
||||
|
||||
fgcolor = webcolor(d[:foreground_color])
|
||||
tsym = tickssym(isx)
|
||||
|
||||
if use_axis_field(d[tsym])
|
||||
ax[:titlefont] = plotlyfont(d[:guidefont], fgcolor)
|
||||
ax[:type] = plotlyscale(d[scalesym(isx)])
|
||||
ax[:tickfont] = plotlyfont(d[:tickfont], fgcolor)
|
||||
ax[:tickcolor] = fgcolor
|
||||
ax[:linecolor] = fgcolor
|
||||
|
||||
# xlims
|
||||
lims = d[limssym(isx)]
|
||||
if lims != :auto && limsType(lims) == :limits
|
||||
ax[:range] = lims
|
||||
end
|
||||
|
||||
# xflip
|
||||
if d[flipsym(isx)]
|
||||
ax[:autorange] = "reversed"
|
||||
end
|
||||
|
||||
# xticks
|
||||
ticks = d[tsym]
|
||||
if ticks != :auto
|
||||
ttype = ticksType(ticks)
|
||||
if ttype == :ticks
|
||||
ax[:tickmode] = "array"
|
||||
ax[:tickvals] = ticks
|
||||
elseif ttype == :ticks_and_labels
|
||||
ax[:tickmode] = "array"
|
||||
ax[:tickvals], ax[:ticktext] = ticks
|
||||
end
|
||||
end
|
||||
|
||||
ax
|
||||
else
|
||||
ax[:showticklabels] = false
|
||||
ax[:showgrid] = false
|
||||
end
|
||||
|
||||
ax
|
||||
end
|
||||
|
||||
# function get_plot_json(plt::Plot{PlotlyBackend})
|
||||
# d = plt.plotargs
|
||||
function plotly_layout(d::KW)
|
||||
d_out = KW()
|
||||
|
||||
d_out[:width], d_out[:height] = d[:size]
|
||||
|
||||
bgcolor = webcolor(d[:background_color])
|
||||
fgcolor = webcolor(d[:foreground_color])
|
||||
|
||||
# set the fields for the plot
|
||||
d_out[:title] = d[:title]
|
||||
d_out[:titlefont] = plotlyfont(d[:guidefont], fgcolor)
|
||||
d_out[:margin] = KW(:l=>35, :b=>30, :r=>8, :t=>20)
|
||||
d_out[:plot_bgcolor] = bgcolor
|
||||
d_out[:paper_bgcolor] = bgcolor
|
||||
|
||||
# TODO: x/y axis tick values/labels
|
||||
d_out[:xaxis] = plotlyaxis(d, true)
|
||||
d_out[:yaxis] = plotlyaxis(d, false)
|
||||
|
||||
# legend
|
||||
d_out[:showlegend] = d[:legend] != :none
|
||||
if d[:legend] != :none
|
||||
d_out[:legend] = KW(
|
||||
:bgcolor => bgcolor,
|
||||
:bordercolor => fgcolor,
|
||||
:font => plotlyfont(d[:legendfont]),
|
||||
)
|
||||
end
|
||||
|
||||
# annotations
|
||||
anns = get(d, :annotation_list, [])
|
||||
if !isempty(anns)
|
||||
d_out[:annotations] = [get_annotation_dict(ann...) for ann in anns]
|
||||
end
|
||||
|
||||
if get(d, :polar, false)
|
||||
d_out[:direction] = "counterclockwise"
|
||||
end
|
||||
|
||||
d_out
|
||||
end
|
||||
|
||||
function get_plot_json(plt::Plot{PlotlyBackend})
|
||||
JSON.json(plotly_layout(plt.plotargs))
|
||||
end
|
||||
|
||||
|
||||
function plotly_colorscale(grad::ColorGradient, alpha = nothing)
|
||||
[[grad.values[i], webcolor(grad.colors[i], alpha)] for i in 1:length(grad.colors)]
|
||||
end
|
||||
plotly_colorscale(c, alpha = nothing) = plotly_colorscale(default_gradient(), alpha)
|
||||
|
||||
const _plotly_markers = KW(
|
||||
:rect => "square",
|
||||
:xcross => "x",
|
||||
:utriangle => "triangle-up",
|
||||
:dtriangle => "triangle-down",
|
||||
:star5 => "star-triangle-up",
|
||||
:vline => "line-ns",
|
||||
:hline => "line-ew",
|
||||
)
|
||||
|
||||
# get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict)
|
||||
function plotly_series(d::KW, plotargs::KW; plot_index = nothing)
|
||||
# dumpdict(d,"series",true)
|
||||
d_out = KW()
|
||||
|
||||
x, y = collect(d[:x]), collect(d[:y])
|
||||
d_out[:name] = d[:label]
|
||||
|
||||
lt = d[:linetype]
|
||||
isscatter = lt in (:scatter, :scatter3d)
|
||||
hasmarker = isscatter || d[:markershape] != :none
|
||||
hasline = !isscatter
|
||||
|
||||
# set the "type"
|
||||
if lt in (:line, :path, :scatter, :steppre, :steppost)
|
||||
d_out[:type] = "scatter"
|
||||
d_out[:mode] = if hasmarker
|
||||
hasline ? "lines+markers" : "markers"
|
||||
else
|
||||
hasline ? "lines" : "none"
|
||||
end
|
||||
if d[:fillrange] == true || d[:fillrange] == 0
|
||||
d_out[:fill] = "tozeroy"
|
||||
d_out[:fillcolor] = webcolor(d[:fillcolor], d[:fillalpha])
|
||||
elseif !(d[:fillrange] in (false, nothing))
|
||||
warn("fillrange ignored... plotly only supports filling to zero. fillrange: $(d[:fillrange])")
|
||||
end
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
|
||||
elseif lt == :bar
|
||||
d_out[:type] = "bar"
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
|
||||
elseif lt == :hist2d
|
||||
d_out[:type] = "histogram2d"
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
if isa(d[:bins], Tuple)
|
||||
xbins, ybins = d[:bins]
|
||||
else
|
||||
xbins = ybins = d[:bins]
|
||||
end
|
||||
d_out[:nbinsx] = xbins
|
||||
d_out[:nbinsy] = ybins
|
||||
|
||||
elseif lt in (:hist, :density)
|
||||
d_out[:type] = "histogram"
|
||||
isvert = isvertical(d)
|
||||
d_out[isvert ? :x : :y] = y
|
||||
d_out[isvert ? :nbinsx : :nbinsy] = d[:bins]
|
||||
if lt == :density
|
||||
d_out[:histnorm] = "probability density"
|
||||
end
|
||||
|
||||
elseif lt == :heatmap
|
||||
d_out[:type] = "heatmap"
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
d_out[:z] = d[:z].surf
|
||||
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
|
||||
|
||||
elseif lt == :contour
|
||||
d_out[:type] = "contour"
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
d_out[:z] = d[:z].surf
|
||||
# d_out[:showscale] = d[:colorbar] != :none
|
||||
d_out[:ncontours] = d[:levels]
|
||||
d_out[:contours] = KW(:coloring => d[:fillrange] != nothing ? "fill" : "lines")
|
||||
d_out[:colorscale] = plotly_colorscale(d[:linecolor], d[:linealpha])
|
||||
|
||||
elseif lt in (:surface, :wireframe)
|
||||
d_out[:type] = "surface"
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
d_out[:z] = d[:z].surf
|
||||
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
|
||||
|
||||
elseif lt == :pie
|
||||
d_out[:type] = "pie"
|
||||
d_out[:labels] = x
|
||||
d_out[:values] = y
|
||||
d_out[:hoverinfo] = "label+percent+name"
|
||||
|
||||
elseif lt in (:path3d, :scatter3d)
|
||||
d_out[:type] = "scatter3d"
|
||||
d_out[:mode] = if hasmarker
|
||||
hasline ? "lines+markers" : "markers"
|
||||
else
|
||||
hasline ? "lines" : "none"
|
||||
end
|
||||
d_out[:x], d_out[:y] = x, y
|
||||
d_out[:z] = collect(d[:z])
|
||||
|
||||
else
|
||||
warn("Plotly: linetype $lt isn't supported.")
|
||||
return KW()
|
||||
end
|
||||
|
||||
# add "marker"
|
||||
if hasmarker
|
||||
d_out[:marker] = KW(
|
||||
:symbol => get(_plotly_markers, d[:markershape], string(d[:markershape])),
|
||||
:opacity => d[:markeralpha],
|
||||
:size => 2 * d[:markersize],
|
||||
:color => webcolor(d[:markercolor], d[:markeralpha]),
|
||||
:line => KW(
|
||||
:color => webcolor(d[:markerstrokecolor], d[:markerstrokealpha]),
|
||||
:width => d[:markerstrokewidth],
|
||||
),
|
||||
)
|
||||
|
||||
# gotta hack this (for now?) since plotly can't handle rgba values inside the gradient
|
||||
if d[:marker_z] != nothing
|
||||
# d_out[:marker][:color] = d[:marker_z]
|
||||
# d_out[:marker][:colorscale] = plotly_colorscale(d[:markercolor], d[:markeralpha])
|
||||
# d_out[:showscale] = true
|
||||
grad = ColorGradient(d[:markercolor], alpha=d[:markeralpha])
|
||||
zmin, zmax = extrema(d[:marker_z])
|
||||
d_out[:marker][:color] = [webcolor(getColorZ(grad, (zi - zmin) / (zmax - zmin))) for zi in d[:marker_z]]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# add "line"
|
||||
if hasline
|
||||
d_out[:line] = KW(
|
||||
:color => webcolor(d[:linecolor], d[:linealpha]),
|
||||
:width => d[:linewidth],
|
||||
:shape => if lt == :steppre
|
||||
"vh"
|
||||
elseif lt == :steppost
|
||||
"hv"
|
||||
else
|
||||
"linear"
|
||||
end,
|
||||
:dash => string(d[:linestyle]),
|
||||
# :dash => "solid",
|
||||
)
|
||||
end
|
||||
|
||||
# convert polar plots x/y to theta/radius
|
||||
if get(plotargs, :polar, false)
|
||||
d_out[:t] = rad2deg(pop!(d_out, :x))
|
||||
d_out[:r] = pop!(d_out, :y)
|
||||
end
|
||||
|
||||
# # for subplots, we need to add the xaxis/yaxis fields
|
||||
# if plot_index != nothing
|
||||
# d_out[:xaxis] = "x$(plot_index)"
|
||||
# d_out[:yaxis] = "y$(plot_index)"
|
||||
# end
|
||||
|
||||
d_out
|
||||
end
|
||||
|
||||
# get a list of dictionaries, each representing the series params
|
||||
function get_series_json(plt::Plot{PlotlyBackend})
|
||||
JSON.json(map(d -> plotly_series(d, plt.plotargs), plt.seriesargs))
|
||||
end
|
||||
|
||||
function get_series_json(subplt::Subplot{PlotlyBackend})
|
||||
ds = KW[]
|
||||
for (i,plt) in enumerate(subplt.plts)
|
||||
for d in plt.seriesargs
|
||||
push!(ds, plotly_series(d, plt.plotargs, plot_index = i))
|
||||
end
|
||||
end
|
||||
JSON.json(ds)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function html_head(plt::AbstractPlot{PlotlyBackend})
|
||||
"<script src=\"$(Pkg.dir("Plots","deps","plotly-latest.min.js"))\"></script>"
|
||||
end
|
||||
|
||||
function html_body(plt::Plot{PlotlyBackend}, style = nothing)
|
||||
if style == nothing
|
||||
w, h = plt.plotargs[:size]
|
||||
style = "width:$(w)px;height:$(h)px;"
|
||||
end
|
||||
uuid = Base.Random.uuid4()
|
||||
html = """
|
||||
<div id=\"$(uuid)\" style=\"$(style)\"></div>
|
||||
<script>
|
||||
PLOT = document.getElementById('$(uuid)');
|
||||
Plotly.plot(PLOT, $(get_series_json(plt)), $(get_plot_json(plt)));
|
||||
</script>
|
||||
"""
|
||||
# @show html
|
||||
html
|
||||
end
|
||||
|
||||
function js_body(plt::Plot{PlotlyBackend}, uuid)
|
||||
js = """
|
||||
PLOT = document.getElementById('$(uuid)');
|
||||
Plotly.plot(PLOT, $(get_series_json(plt)), $(get_plot_json(plt)));
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
function html_body(subplt::Subplot{PlotlyBackend})
|
||||
w, h = subplt.plts[1].plotargs[:size]
|
||||
html = ["<div style=\"width:$(w)px;height:$(h)px;\">"]
|
||||
nr = nrows(subplt.layout)
|
||||
ph = h / nr
|
||||
|
||||
for r in 1:nr
|
||||
push!(html, "<div style=\"clear:both;\">")
|
||||
|
||||
nc = ncols(subplt.layout, r)
|
||||
pw = w / nc
|
||||
|
||||
for c in 1:nc
|
||||
plt = subplt[r,c]
|
||||
push!(html, html_body(plt, "float:left; width:$(pw)px; height:$(ph)px;"))
|
||||
end
|
||||
|
||||
push!(html, "</div>")
|
||||
end
|
||||
push!(html, "</div>")
|
||||
|
||||
join(html)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{PlotlyBackend})
|
||||
writemime_png_from_html(io, plt)
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PlotlyBackend})
|
||||
write(io, html_head(plt) * html_body(plt))
|
||||
# write(io, html_body(plt))
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::AbstractPlot{PlotlyBackend})
|
||||
standalone_html_window(plt)
|
||||
end
|
||||
|
||||
# function Base.display(::PlotsDisplay, plt::Subplot{PlotlyBackend})
|
||||
# # TODO: display/show the subplot
|
||||
# end
|
||||
@@ -0,0 +1,206 @@
|
||||
|
||||
# https://github.com/spencerlyon2/PlotlyJS.jl
|
||||
|
||||
supportedArgs(::PlotlyJSBackend) = [
|
||||
:annotation,
|
||||
# :axis,
|
||||
:background_color,
|
||||
:color_palette,
|
||||
:fillrange,
|
||||
:fillcolor,
|
||||
:fillalpha,
|
||||
:foreground_color,
|
||||
:group,
|
||||
:label,
|
||||
:layout,
|
||||
:legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linecolor,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
:linealpha,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
:markeralpha,
|
||||
:markerstrokewidth,
|
||||
:markerstrokecolor,
|
||||
:markerstrokestyle,
|
||||
:n,
|
||||
:bins,
|
||||
:nc,
|
||||
:nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
:show,
|
||||
:size,
|
||||
:title,
|
||||
:windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
:xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
# :yrightlabel,
|
||||
:yticks,
|
||||
:xscale,
|
||||
:yscale,
|
||||
:xflip,
|
||||
:yflip,
|
||||
:z,
|
||||
:marker_z,
|
||||
:tickfont,
|
||||
:guidefont,
|
||||
:legendfont,
|
||||
:grid,
|
||||
:levels,
|
||||
:xerror,
|
||||
:yerror,
|
||||
:ribbon,
|
||||
:quiver,
|
||||
:orientation,
|
||||
:polar,
|
||||
]
|
||||
supportedAxes(::PlotlyJSBackend) = [:auto, :left]
|
||||
supportedTypes(::PlotlyJSBackend) = [:none, :line, :path, :scatter, :steppre, :steppost,
|
||||
:hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d,
|
||||
:pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline]
|
||||
supportedStyles(::PlotlyJSBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
supportedMarkers(::PlotlyJSBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross,
|
||||
:pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape)
|
||||
supportedScales(::PlotlyJSBackend) = [:identity, :log10] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
subplotSupported(::PlotlyJSBackend) = true
|
||||
stringsSupported(::PlotlyJSBackend) = true
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::PlotlyJSBackend; kw...)
|
||||
@eval begin
|
||||
import PlotlyJS
|
||||
export PlotlyJS
|
||||
end
|
||||
|
||||
for (mime, fmt) in PlotlyJS._mimeformats
|
||||
# mime == "image/png" && continue # don't use plotlyjs's writemime for png
|
||||
@eval Base.writemime(io::IO, m::MIME{symbol($mime)}, p::Plot{PlotlyJSBackend}) = writemime(io, m, p.o)
|
||||
end
|
||||
|
||||
# override IJulia inline display
|
||||
if isijulia()
|
||||
IJulia.display_dict(plt::AbstractPlot{PlotlyJSBackend}) = IJulia.display_dict(plt.o)
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _create_plot(pkg::PlotlyJSBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
# o = PlotlyJS.Plot(PlotlyJS.GenericTrace[], PlotlyJS.Layout(),
|
||||
# Base.Random.uuid4(), PlotlyJS.ElectronDisplay())
|
||||
# T = isijulia() ? PlotlyJS.JupyterPlot : PlotlyJS.ElectronPlot
|
||||
# o = T(PlotlyJS.Plot())
|
||||
o = PlotlyJS.plot()
|
||||
|
||||
Plot(o, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::PlotlyJSBackend, plt::Plot, d::KW)
|
||||
syncplot = plt.o
|
||||
|
||||
# add to the data array
|
||||
pdict = plotly_series(d, plt.plotargs)
|
||||
typ = pop!(pdict, :type)
|
||||
gt = PlotlyJS.GenericTrace(typ; pdict...)
|
||||
PlotlyJS.addtraces!(syncplot, gt)
|
||||
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{PlotlyJSBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
# set or add to the annotation_list
|
||||
if !haskey(plt.plotargs, :annotation_list)
|
||||
plt.plotargs[:annotation_list] = Any[]
|
||||
end
|
||||
append!(plt.plotargs[:annotation_list], anns)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{PlotlyJSBackend})
|
||||
end
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{PlotlyJSBackend}, d::KW)
|
||||
pdict = plotly_layout(d)
|
||||
syncplot = plt.o
|
||||
w,h = d[:size]
|
||||
PlotlyJS.relayout!(syncplot, pdict, width = w, height = h)
|
||||
end
|
||||
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{PlotlyJSBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# function getxy(plt::Plot{PlotlyJSBackend}, i::Int)
|
||||
# d = plt.seriesargs[i]
|
||||
# d[:x], d[:y]
|
||||
# end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{PlotlyJSBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
ispolar = get(plt.plotargs, :polar, false)
|
||||
xsym = ispolar ? :t : :x
|
||||
ysym = ispolar ? :r : :y
|
||||
d[xsym], d[ysym] = xy
|
||||
# TODO: this is likely ineffecient... we should make a call that ONLY changes the plot data
|
||||
PlotlyJS.restyle!(plt.o, i, KW(xsym=>(d[xsym],), ysym=>(d[ysym],)))
|
||||
plt
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{PlotlyJSBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
true
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{PlotlyJSBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{PlotlyJSBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# function Base.writemime(io::IO, m::MIME"text/html", plt::AbstractPlot{PlotlyJSBackend})
|
||||
# Base.writemime(io, m, plt.o)
|
||||
# end
|
||||
|
||||
# function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{PlotlyJSBackend})
|
||||
# println("here!")
|
||||
# writemime_png_from_html(io, plt)
|
||||
# end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{PlotlyJSBackend})
|
||||
display(plt.o)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Subplot{PlotlyJSBackend})
|
||||
error()
|
||||
end
|
||||
@@ -0,0 +1,331 @@
|
||||
|
||||
# https://github.com/tbreloff/Qwt.jl
|
||||
|
||||
|
||||
supportedArgs(::QwtBackend) = [
|
||||
:annotation,
|
||||
:axis,
|
||||
:background_color,
|
||||
:linecolor,
|
||||
:color_palette,
|
||||
:fillrange,
|
||||
:fillcolor,
|
||||
:foreground_color,
|
||||
:group,
|
||||
:label,
|
||||
:layout,
|
||||
:legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
:n,
|
||||
:bins,
|
||||
:nc,
|
||||
:nr,
|
||||
:pos,
|
||||
:smooth,
|
||||
:show,
|
||||
:size,
|
||||
:title,
|
||||
:windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
:xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
:yrightlabel,
|
||||
:yticks,
|
||||
:xscale,
|
||||
:yscale,
|
||||
]
|
||||
supportedTypes(::QwtBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline]
|
||||
supportedMarkers(::QwtBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :star8, :hexagon]
|
||||
supportedScales(::QwtBackend) = [:identity, :log10]
|
||||
subplotSupported(::QwtBackend) = true
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::QwtBackend; kw...)
|
||||
@eval begin
|
||||
warn("Qwt is no longer supported... many features will likely be broken.")
|
||||
import Qwt
|
||||
export Qwt
|
||||
end
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
@compat const _qwtAliases = KW(
|
||||
:bins => :heatmap_n,
|
||||
:fillrange => :fillto,
|
||||
:linewidth => :width,
|
||||
:markershape => :marker,
|
||||
:hexbin => :heatmap,
|
||||
:path => :line,
|
||||
:steppost => :step,
|
||||
:steppre => :stepinverted,
|
||||
:star5 => :star1,
|
||||
:star8 => :star2,
|
||||
)
|
||||
|
||||
function fixcolors(d::KW)
|
||||
for (k,v) in d
|
||||
if typeof(v) <: ColorScheme
|
||||
d[k] = getColor(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function replaceQwtAliases(d, s)
|
||||
if haskey(_qwtAliases, d[s])
|
||||
d[s] = _qwtAliases[d[s]]
|
||||
end
|
||||
end
|
||||
|
||||
function adjustQwtKeywords(plt::Plot{QwtBackend}, iscreating::Bool; kw...)
|
||||
d = KW(kw)
|
||||
lt = d[:linetype]
|
||||
if lt == :scatter
|
||||
d[:linetype] = :none
|
||||
if d[:markershape] == :none
|
||||
d[:markershape] = :ellipse
|
||||
end
|
||||
|
||||
elseif lt in (:hline, :vline)
|
||||
addLineMarker(plt, d)
|
||||
d[:linetype] = :none
|
||||
d[:markershape] = :ellipse
|
||||
d[:markersize] = 1
|
||||
if lt == :vline
|
||||
d[:x], d[:y] = d[:y], d[:x]
|
||||
end
|
||||
|
||||
elseif !iscreating && lt == :bar
|
||||
d = barHack(; kw...)
|
||||
elseif !iscreating && lt == :hist
|
||||
d = barHack(; histogramHack(; kw...)...)
|
||||
end
|
||||
|
||||
replaceQwtAliases(d, :linetype)
|
||||
replaceQwtAliases(d, :markershape)
|
||||
|
||||
for k in keys(d)
|
||||
if haskey(_qwtAliases, k)
|
||||
d[_qwtAliases[k]] = d[k]
|
||||
end
|
||||
end
|
||||
|
||||
d[:x] = collect(d[:x])
|
||||
d[:y] = collect(d[:y])
|
||||
|
||||
d
|
||||
end
|
||||
|
||||
function _create_plot(pkg::QwtBackend, d::KW)
|
||||
fixcolors(d)
|
||||
dumpdict(d,"\n\n!!! plot")
|
||||
o = Qwt.plot(zeros(0,0); d..., show=false)
|
||||
plt = Plot(o, pkg, 0, d, KW[])
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_series(::QwtBackend, plt::Plot, d::KW)
|
||||
d = adjustQwtKeywords(plt, false; d...)
|
||||
fixcolors(d)
|
||||
dumpdict(d,"\n\n!!! plot!")
|
||||
Qwt.oplot(plt.o; d...)
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
|
||||
lims = get(d, isx ? :xlims : :ylims, nothing)
|
||||
ticks = get(d, isx ? :xticks : :yticks, nothing)
|
||||
w = plt.o.widget
|
||||
axisid = Qwt.QWT.QwtPlot[isx ? :xBottom : :yLeft]
|
||||
|
||||
if typeof(lims) <: @compat(Union{Tuple,AVec}) && length(lims) == 2
|
||||
if isx
|
||||
plt.o.autoscale_x = false
|
||||
else
|
||||
plt.o.autoscale_y = false
|
||||
end
|
||||
w[:setAxisScale](axisid, lims...)
|
||||
end
|
||||
|
||||
if typeof(ticks) <: Range
|
||||
if isx
|
||||
plt.o.autoscale_x = false
|
||||
else
|
||||
plt.o.autoscale_y = false
|
||||
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))")
|
||||
end
|
||||
|
||||
# change the scale
|
||||
scalesym = isx ? :xscale : :yscale
|
||||
if haskey(d, scalesym)
|
||||
scaletype = d[scalesym]
|
||||
scaletype == :identity && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLinearScaleEngine())
|
||||
# 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 supportedScales() || warn("Unsupported scale type: ", scaletype)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
function _update_plot(plt::Plot{QwtBackend}, d::KW)
|
||||
haskey(d, :title) && Qwt.title(plt.o, d[:title])
|
||||
haskey(d, :xlabel) && Qwt.xlabel(plt.o, d[:xlabel])
|
||||
haskey(d, :ylabel) && Qwt.ylabel(plt.o, d[:ylabel])
|
||||
updateLimsAndTicks(plt, d, true)
|
||||
updateLimsAndTicks(plt, d, false)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{QwtBackend}, d::KW)
|
||||
haskey(d, :size) && Qwt.resizewidget(plt.o, d[:size]...)
|
||||
haskey(d, :pos) && Qwt.movewidget(plt.o, d[:pos]...)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# curve.setPen(Qt.QPen(Qt.QColor(color), linewidth, self.getLineStyle(linestyle)))
|
||||
function addLineMarker(plt::Plot{QwtBackend}, d::KW)
|
||||
for yi in d[:y]
|
||||
marker = Qwt.QWT.QwtPlotMarker()
|
||||
ishorizontal = (d[:linetype] == :hline)
|
||||
marker[:setLineStyle](ishorizontal ? 1 : 2)
|
||||
marker[ishorizontal ? :setYValue : :setXValue](yi)
|
||||
qcolor = Qwt.convertRGBToQColor(getColor(d[:linecolor]))
|
||||
linestyle = plt.o.widget[:getLineStyle](string(d[:linestyle]))
|
||||
marker[:setLinePen](Qwt.QT.QPen(qcolor, d[:linewidth], linestyle))
|
||||
marker[:attach](plt.o.widget)
|
||||
end
|
||||
|
||||
# marker[:setValue](x, y)
|
||||
# marker[:setLabel](Qwt.QWT.QwtText(val))
|
||||
# marker[:attach](plt.o.widget)
|
||||
end
|
||||
|
||||
function createQwtAnnotation(plt::Plot, x, y, val::PlotText)
|
||||
marker = Qwt.QWT.QwtPlotMarker()
|
||||
marker[:setValue](x, y)
|
||||
qwttext = Qwt.QWT.QwtText(val.str)
|
||||
qwttext[:setFont](Qwt.QT.QFont(val.font.family, val.font.pointsize))
|
||||
qwttext[:setColor](Qwt.convertRGBToQColor(getColor(val.font.color)))
|
||||
marker[:setLabel](qwttext)
|
||||
marker[:attach](plt.o.widget)
|
||||
end
|
||||
|
||||
function createQwtAnnotation(plt::Plot, x, y, val::@compat(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})})
|
||||
for ann in anns
|
||||
createQwtAnnotation(plt, ann...)
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
function getxy(plt::Plot{QwtBackend}, i::Int)
|
||||
series = plt.o.lines[i]
|
||||
series.x, series.y
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
series = plt.o.lines[i]
|
||||
series.x, series.y = xy
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# savepng(::QwtBackend, plt::AbstractPlot, fn::@compat(AbstractString), args...) = Qwt.savepng(plt.o, fn)
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# create the underlying object (each backend will do this differently)
|
||||
function _create_subplot(subplt::Subplot{QwtBackend}, isbefore::Bool)
|
||||
isbefore && return false
|
||||
i = 0
|
||||
rows = Any[]
|
||||
row = Any[]
|
||||
for (i,(r,c)) in enumerate(subplt.layout)
|
||||
push!(row, subplt.plts[i].o)
|
||||
if c == ncols(subplt.layout, r)
|
||||
push!(rows, Qwt.hsplitter(row...))
|
||||
row = Any[]
|
||||
end
|
||||
end
|
||||
# for rowcnt in subplt.layout.rowcounts
|
||||
# push!(rows, Qwt.hsplitter([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...))
|
||||
# i += rowcnt
|
||||
# end
|
||||
subplt.o = Qwt.vsplitter(rows...)
|
||||
# Qwt.resizewidget(subplt.o, getplotargs(subplt,1)[:size]...)
|
||||
# Qwt.moveToLastScreen(subplt.o) # hack so it goes to my center monitor... sorry
|
||||
true
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{QwtBackend}, isx::Bool)
|
||||
for series in plt.o.lines
|
||||
_expand_limits(lims, isx ? series.x : series.y)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _remove_axis(plt::Plot{QwtBackend}, isx::Bool)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(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})
|
||||
for plt in subplt.plts
|
||||
Qwt.refresh(plt.o)
|
||||
end
|
||||
Qwt.savepng(subplt.o, "/tmp/dfskjdhfkh.png")
|
||||
write(io, readall("/tmp/dfskjdhfkh.png"))
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{QwtBackend})
|
||||
Qwt.refresh(plt.o)
|
||||
Qwt.showwidget(plt.o)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, subplt::Subplot{QwtBackend})
|
||||
for plt in subplt.plts
|
||||
Qwt.refresh(plt.o)
|
||||
end
|
||||
Qwt.showwidget(subplt.o)
|
||||
end
|
||||
@@ -0,0 +1,698 @@
|
||||
#
|
||||
# supportedAxes(::AbstractBackend) = [:left]
|
||||
# supportedTypes(::AbstractBackend) = []
|
||||
# supportedStyles(::AbstractBackend) = [:solid]
|
||||
# supportedMarkers(::AbstractBackend) = [:none]
|
||||
# supportedScales(::AbstractBackend) = [:identity]
|
||||
# subplotSupported(::AbstractBackend) = false
|
||||
# stringsSupported(::AbstractBackend) = false
|
||||
#
|
||||
# supportedAxes() = supportedAxes(backend())
|
||||
# supportedTypes() = supportedTypes(backend())
|
||||
# supportedStyles() = supportedStyles(backend())
|
||||
# supportedMarkers() = supportedMarkers(backend())
|
||||
# supportedScales() = supportedScales(backend())
|
||||
# subplotSupported() = subplotSupported(backend())
|
||||
# stringsSupported() = stringsSupported(backend())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# supportedArgs(::GadflyBackend) = [
|
||||
# :annotation,
|
||||
# :background_color, :foreground_color, :color_palette,
|
||||
# :group,
|
||||
# :label,
|
||||
# :linetype,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor, :linestyle, :linewidth, :linealpha,
|
||||
# :markershape, :markercolor, :markersize, :markeralpha,
|
||||
# :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
# :fillrange, :fillcolor, :fillalpha,
|
||||
# :bins,
|
||||
# :n, :nc, :nr, :layout,
|
||||
# :smooth,
|
||||
# :title, :windowtitle, :show, :size,
|
||||
# :x, :xlabel, :xlims, :xticks, :xscale, :xflip,
|
||||
# :y, :ylabel, :ylims, :yticks, :yscale, :yflip,
|
||||
# # :z, :zlabel, :zlims, :zticks, :zscale, :zflip,
|
||||
# :z,
|
||||
# :tickfont, :guidefont, :legendfont,
|
||||
# :grid, :legend, :colorbar,
|
||||
# :marker_z, :levels,
|
||||
# :xerror, :yerror,
|
||||
# :ribbon, :quiver,
|
||||
# :orientation,
|
||||
# ]
|
||||
# supportedAxes(::GadflyBackend) = [:auto, :left]
|
||||
# supportedTypes(::GadflyBackend) = [
|
||||
# :none, :line, :path, :steppre, :steppost, :sticks,
|
||||
# :scatter, :hist2d, :hexbin, :hist,
|
||||
# :bar, :box, :violin, :quiver,
|
||||
# :hline, :vline, :contour, :shape
|
||||
# ]
|
||||
# supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
# supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape)
|
||||
# supportedScales(::GadflyBackend) = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
# subplotSupported(::GadflyBackend) = true
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# supportedArgs(::ImmerseBackend) = supportedArgs(GadflyBackend())
|
||||
# supportedAxes(::ImmerseBackend) = supportedAxes(GadflyBackend())
|
||||
# supportedTypes(::ImmerseBackend) = supportedTypes(GadflyBackend())
|
||||
# supportedStyles(::ImmerseBackend) = supportedStyles(GadflyBackend())
|
||||
# supportedMarkers(::ImmerseBackend) = supportedMarkers(GadflyBackend())
|
||||
# supportedScales(::ImmerseBackend) = supportedScales(GadflyBackend())
|
||||
# subplotSupported(::ImmerseBackend) = true
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
#
|
||||
# supportedArgs(::PyPlotBackend) = [
|
||||
# :annotation,
|
||||
# :background_color, :foreground_color, :color_palette,
|
||||
# :background_color_legend, :background_color_inside, :background_color_outside,
|
||||
# :foreground_color_legend, :foreground_color_grid, :foreground_color_axis,
|
||||
# :foreground_color_text, :foreground_color_border,
|
||||
# :group,
|
||||
# :label,
|
||||
# :linetype,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor, :linestyle, :linewidth, :linealpha,
|
||||
# :markershape, :markercolor, :markersize, :markeralpha,
|
||||
# :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
# :fillrange, :fillcolor, :fillalpha,
|
||||
# :bins,
|
||||
# :n, :nc, :nr, :layout,
|
||||
# :smooth,
|
||||
# :title, :windowtitle, :show, :size,
|
||||
# :x, :xlabel, :xlims, :xticks, :xscale, :xflip,
|
||||
# :y, :ylabel, :ylims, :yticks, :yscale, :yflip,
|
||||
# :axis, :yrightlabel,
|
||||
# :z, :zlabel, :zlims, :zticks, :zscale, :zflip,
|
||||
# :z,
|
||||
# :tickfont, :guidefont, :legendfont,
|
||||
# :grid, :legend, :colorbar,
|
||||
# :marker_z, :levels,
|
||||
# :xerror, :yerror,
|
||||
# :ribbon, :quiver,
|
||||
# :orientation,
|
||||
# :overwrite_figure,
|
||||
# :polar,
|
||||
# :normalize, :weights, :contours, :aspect_ratio
|
||||
# ]
|
||||
# supportedAxes(::PyPlotBackend) = _allAxes
|
||||
# supportedTypes(::PyPlotBackend) = [
|
||||
# :none, :line, :path, :steppre, :steppost, :shape,
|
||||
# :scatter, :hist2d, :hexbin, :hist, :density,
|
||||
# :bar, :sticks, :box, :violin, :quiver,
|
||||
# :hline, :vline, :heatmap, :pie,
|
||||
# :contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
|
||||
# ]
|
||||
# supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
# supportedMarkers(::PyPlotBackend) = vcat(_allMarkers, Shape)
|
||||
# supportedScales(::PyPlotBackend) = [:identity, :ln, :log2, :log10]
|
||||
# subplotSupported(::PyPlotBackend) = true
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
#
|
||||
# supportedArgs(::GRBackend) = [
|
||||
# :annotation,
|
||||
# :background_color, :foreground_color, :color_palette,
|
||||
# :group,
|
||||
# :label,
|
||||
# :linetype,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor, :linestyle, :linewidth, :linealpha,
|
||||
# :markershape, :markercolor, :markersize, :markeralpha,
|
||||
# :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
# :fillrange, :fillcolor, :fillalpha,
|
||||
# :bins,
|
||||
# :n, :nc, :nr, :layout,
|
||||
# :smooth,
|
||||
# :title, :windowtitle, :show, :size,
|
||||
# :x, :xlabel, :xlims, :xticks, :xscale, :xflip,
|
||||
# :y, :ylabel, :ylims, :yticks, :yscale, :yflip,
|
||||
# :axis, :yrightlabel,
|
||||
# :z, :zlabel, :zlims, :zticks, :zscale, :zflip,
|
||||
# :z,
|
||||
# :tickfont, :guidefont, :legendfont,
|
||||
# :grid, :legend, :colorbar,
|
||||
# :marker_z, :levels,
|
||||
# :xerror, :yerror,
|
||||
# :ribbon, :quiver,
|
||||
# :orientation,
|
||||
# :overwrite_figure,
|
||||
# :polar,
|
||||
# ]
|
||||
# supportedAxes(::GRBackend) = _allAxes
|
||||
# supportedTypes(::GRBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
|
||||
# :scatter, :hist2d, :hexbin, :hist, :density, :bar,
|
||||
# :hline, :vline, :contour, :heatmap, :path3d, :scatter3d, :surface,
|
||||
# :wireframe, :ohlc, :pie]
|
||||
# supportedStyles(::GRBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
# supportedMarkers(::GRBackend) = vcat(_allMarkers, Shape)
|
||||
# supportedScales(::GRBackend) = [:identity, :log10]
|
||||
# subplotSupported(::GRBackend) = true
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
#
|
||||
# supportedArgs(::QwtBackend) = [
|
||||
# :annotation,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
# :linecolor,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# :foreground_color,
|
||||
# :group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
# :smooth,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# ]
|
||||
# supportedTypes(::QwtBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline]
|
||||
# supportedMarkers(::QwtBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :star8, :hexagon]
|
||||
# supportedScales(::QwtBackend) = [:identity, :log10]
|
||||
# subplotSupported(::QwtBackend) = true
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# supportedArgs(::UnicodePlotsBackend) = [
|
||||
# # :annotation,
|
||||
# # :args,
|
||||
# # :axis,
|
||||
# # :background_color,
|
||||
# # :linecolor,
|
||||
# # :fill,
|
||||
# # :foreground_color,
|
||||
# :group,
|
||||
# # :heatmap_c,
|
||||
# # :kwargs,
|
||||
# :label,
|
||||
# # :layout,
|
||||
# :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# # :linewidth,
|
||||
# :markershape,
|
||||
# # :markercolor,
|
||||
# # :markersize,
|
||||
# # :markerstrokewidth,
|
||||
# # :markerstrokecolor,
|
||||
# # :markerstrokestyle,
|
||||
# # :n,
|
||||
# :bins,
|
||||
# # :nc,
|
||||
# # :nr,
|
||||
# # :pos,
|
||||
# # :reg,
|
||||
# # :ribbon,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# # :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# # :yrightlabel,
|
||||
# # :yticks,
|
||||
# # :xscale,
|
||||
# # :yscale,
|
||||
# # :xflip,
|
||||
# # :yflip,
|
||||
# # :z,
|
||||
# ]
|
||||
# supportedAxes(::UnicodePlotsBackend) = [:auto, :left]
|
||||
# supportedTypes(::UnicodePlotsBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline]
|
||||
# supportedStyles(::UnicodePlotsBackend) = [:auto, :solid]
|
||||
# supportedMarkers(::UnicodePlotsBackend) = [:none, :auto, :ellipse]
|
||||
# supportedScales(::UnicodePlotsBackend) = [:identity]
|
||||
# subplotSupported(::UnicodePlotsBackend) = true
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# supportedArgs(::WinstonBackend) = [
|
||||
# :annotation,
|
||||
# # :args,
|
||||
# # :axis,
|
||||
# # :background_color,
|
||||
# :linecolor,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# # :foreground_color,
|
||||
# :group,
|
||||
# # :heatmap_c,
|
||||
# # :kwargs,
|
||||
# :label,
|
||||
# # :layout,
|
||||
# :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# # :markerstrokewidth,
|
||||
# # :markerstrokecolor,
|
||||
# # :markerstrokestyle,
|
||||
# # :n,
|
||||
# :bins,
|
||||
# # :nc,
|
||||
# # :nr,
|
||||
# # :pos,
|
||||
# :smooth,
|
||||
# # :ribbon,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# # :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# # :yrightlabel,
|
||||
# # :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# # :xflip,
|
||||
# # :yflip,
|
||||
# # :z,
|
||||
# ]
|
||||
# supportedAxes(::WinstonBackend) = [:auto, :left]
|
||||
# supportedTypes(::WinstonBackend) = [:none, :line, :path, :sticks, :scatter, :hist, :bar]
|
||||
# supportedStyles(::WinstonBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
# supportedMarkers(::WinstonBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
|
||||
# supportedScales(::WinstonBackend) = [:identity, :log10]
|
||||
# subplotSupported(::WinstonBackend) = false
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
#
|
||||
# supportedArgs(::BokehBackend) = [
|
||||
# # :annotation,
|
||||
# # :axis,
|
||||
# # :background_color,
|
||||
# :linecolor,
|
||||
# # :color_palette,
|
||||
# # :fillrange,
|
||||
# # :fillcolor,
|
||||
# # :fillalpha,
|
||||
# # :foreground_color,
|
||||
# :group,
|
||||
# # :label,
|
||||
# # :layout,
|
||||
# # :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# # :linealpha,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# # :markeralpha,
|
||||
# # :markerstrokewidth,
|
||||
# # :markerstrokecolor,
|
||||
# # :markerstrokestyle,
|
||||
# # :n,
|
||||
# # :bins,
|
||||
# # :nc,
|
||||
# # :nr,
|
||||
# # :pos,
|
||||
# # :smooth,
|
||||
# # :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# # :windowtitle,
|
||||
# :x,
|
||||
# # :xlabel,
|
||||
# # :xlims,
|
||||
# # :xticks,
|
||||
# :y,
|
||||
# # :ylabel,
|
||||
# # :ylims,
|
||||
# # :yrightlabel,
|
||||
# # :yticks,
|
||||
# # :xscale,
|
||||
# # :yscale,
|
||||
# # :xflip,
|
||||
# # :yflip,
|
||||
# # :z,
|
||||
# # :tickfont,
|
||||
# # :guidefont,
|
||||
# # :legendfont,
|
||||
# # :grid,
|
||||
# # :surface,
|
||||
# # :levels,
|
||||
# ]
|
||||
# supportedAxes(::BokehBackend) = [:auto, :left]
|
||||
# supportedTypes(::BokehBackend) = [:none, :path, :scatter] #,:steppre, :steppost, :sticks, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour]
|
||||
# supportedStyles(::BokehBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
# supportedMarkers(::BokehBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape)
|
||||
# supportedScales(::BokehBackend) = [:identity, :ln] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
# subplotSupported(::BokehBackend) = false
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
#
|
||||
# supportedArgs(::PlotlyBackend) = [
|
||||
# :annotation,
|
||||
# # :axis,
|
||||
# :background_color,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# :fillalpha,
|
||||
# :foreground_color,
|
||||
# :group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# :linealpha,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :markeralpha,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# # :pos,
|
||||
# # :smooth,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# # :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
# :marker_z,
|
||||
# :tickfont,
|
||||
# :guidefont,
|
||||
# :legendfont,
|
||||
# :grid,
|
||||
# :levels,
|
||||
# :xerror,
|
||||
# :yerror,
|
||||
# :ribbon,
|
||||
# :quiver,
|
||||
# :orientation,
|
||||
# :polar,
|
||||
# ]
|
||||
# supportedAxes(::PlotlyBackend) = [:auto, :left]
|
||||
# supportedTypes(::PlotlyBackend) = [:none, :line, :path, :scatter, :steppre, :steppost,
|
||||
# :hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d,
|
||||
# :pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline]
|
||||
# supportedStyles(::PlotlyBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
# supportedMarkers(::PlotlyBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross,
|
||||
# :pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape)
|
||||
# supportedScales(::PlotlyBackend) = [:identity, :log10] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
# subplotSupported(::PlotlyBackend) = true
|
||||
# stringsSupported(::PlotlyBackend) = true
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
#
|
||||
# supportedArgs(::PlotlyJSBackend) = [
|
||||
# :annotation,
|
||||
# # :axis,
|
||||
# :background_color,
|
||||
# :color_palette,
|
||||
# :fillrange,
|
||||
# :fillcolor,
|
||||
# :fillalpha,
|
||||
# :foreground_color,
|
||||
# :group,
|
||||
# :label,
|
||||
# :layout,
|
||||
# :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# :linealpha,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :markeralpha,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
# :bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# # :pos,
|
||||
# # :smooth,
|
||||
# :show,
|
||||
# :size,
|
||||
# :title,
|
||||
# :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# # :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
# :marker_z,
|
||||
# :tickfont,
|
||||
# :guidefont,
|
||||
# :legendfont,
|
||||
# :grid,
|
||||
# :levels,
|
||||
# :xerror,
|
||||
# :yerror,
|
||||
# :ribbon,
|
||||
# :quiver,
|
||||
# :orientation,
|
||||
# :polar,
|
||||
# ]
|
||||
# supportedAxes(::PlotlyJSBackend) = [:auto, :left]
|
||||
# supportedTypes(::PlotlyJSBackend) = [:none, :line, :path, :scatter, :steppre, :steppost,
|
||||
# :hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d,
|
||||
# :pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline]
|
||||
# supportedStyles(::PlotlyJSBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
# supportedMarkers(::PlotlyJSBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross,
|
||||
# :pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape)
|
||||
# supportedScales(::PlotlyJSBackend) = [:identity, :log10] #, :ln, :log2, :log10, :asinh, :sqrt]
|
||||
# subplotSupported(::PlotlyJSBackend) = true
|
||||
# stringsSupported(::PlotlyJSBackend) = true
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
#
|
||||
# supportedArgs(::GLVisualizeBackend) = [
|
||||
# # :annotation,
|
||||
# # :axis,
|
||||
# # :background_color,
|
||||
# # :color_palette,
|
||||
# # :fillrange,
|
||||
# # :fillcolor,
|
||||
# # :fillalpha,
|
||||
# # :foreground_color,
|
||||
# # :group,
|
||||
# # :label,
|
||||
# # :layout,
|
||||
# # :legend,
|
||||
# # :linecolor,
|
||||
# # :linestyle,
|
||||
# :linetype
|
||||
# # :seriescolor, :seriesalpha,
|
||||
# # :linewidth,
|
||||
# # :linealpha,
|
||||
# # :markershape,
|
||||
# # :markercolor,
|
||||
# # :markersize,
|
||||
# # :markeralpha,
|
||||
# # :markerstrokewidth,
|
||||
# # :markerstrokecolor,
|
||||
# # :markerstrokestyle,
|
||||
# # :n,
|
||||
# # :bins,
|
||||
# # :nc,
|
||||
# # :nr,
|
||||
# # :pos,
|
||||
# # :smooth,
|
||||
# # :show,
|
||||
# # :size,
|
||||
# # :title,
|
||||
# # :windowtitle,
|
||||
# # :x,
|
||||
# # :xlabel,
|
||||
# # :xlims,
|
||||
# # :xticks,
|
||||
# # :y,
|
||||
# # :ylabel,
|
||||
# # :ylims,
|
||||
# # :yrightlabel,
|
||||
# # :yticks,
|
||||
# # :xscale,
|
||||
# # :yscale,
|
||||
# # :xflip,
|
||||
# # :yflip,
|
||||
# # :z,
|
||||
# # :tickfont,
|
||||
# # :guidefont,
|
||||
# # :legendfont,
|
||||
# # :grid,
|
||||
# # :surface
|
||||
# # :levels,
|
||||
# ]
|
||||
# supportedAxes(::GLVisualizeBackend) = [:auto, :left]
|
||||
# supportedTypes(::GLVisualizeBackend) = [:surface] #, :path, :scatter ,:steppre, :steppost, :sticks, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :contour]
|
||||
# supportedStyles(::GLVisualizeBackend) = [:auto, :solid] #, :dash, :dot, :dashdot, :dashdotdot]
|
||||
# supportedMarkers(::GLVisualizeBackend) = [:none, :auto, :ellipse] #, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape)
|
||||
# supportedScales(::GLVisualizeBackend) = [:identity] #, :log, :log2, :log10, :asinh, :sqrt]
|
||||
# subplotSupported(::GLVisualizeBackend) = false
|
||||
#
|
||||
# # --------------------------------------------------------------------------------------
|
||||
#
|
||||
# supportedArgs(::PGFPlotsBackend) = [
|
||||
# # :annotation,
|
||||
# # :axis,
|
||||
# :background_color,
|
||||
# # :color_palette,
|
||||
# # :fillrange,
|
||||
# :fillcolor,
|
||||
# :fillalpha,
|
||||
# # :foreground_color,
|
||||
# # :group,
|
||||
# # :label,
|
||||
# # :layout,
|
||||
# # :legend,
|
||||
# :seriescolor, :seriesalpha,
|
||||
# :linecolor,
|
||||
# :linestyle,
|
||||
# :linetype,
|
||||
# :linewidth,
|
||||
# :linealpha,
|
||||
# :markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :markeralpha,
|
||||
# # :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# # :n,
|
||||
# # :bins,
|
||||
# # :nc,
|
||||
# # :nr,
|
||||
# # :pos,
|
||||
# # :smooth,
|
||||
# # :show,
|
||||
# # :size,
|
||||
# :title,
|
||||
# # :windowtitle,
|
||||
# :x,
|
||||
# :xlabel,
|
||||
# :xlims,
|
||||
# # :xticks,
|
||||
# :y,
|
||||
# :ylabel,
|
||||
# :ylims,
|
||||
# # :yrightlabel,
|
||||
# # :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
# :zscale,
|
||||
# # :tickfont,
|
||||
# # :guidefont,
|
||||
# # :legendfont,
|
||||
# :grid,
|
||||
# # :surface
|
||||
# # :levels,
|
||||
# ]
|
||||
# supportedAxes(::PGFPlotsBackend) = [:auto, :left]
|
||||
# supportedTypes(::PGFPlotsBackend) = [:path, :path3d, :scatter, :line, :steppre, :stepmid, :steppost, :hist, :bar, :hist2d, :sticks, :ysticks, :xsticks, :contour] # :hexbin, :hline, :vline,]
|
||||
# supportedStyles(::PGFPlotsBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
# supportedMarkers(::PGFPlotsBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
|
||||
# supportedScales(::PGFPlotsBackend) = [:identity, :log, :ln, :log2, :log10] # :asinh, :sqrt]
|
||||
# subplotSupported(::PGFPlotsBackend) = false
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
# TODO: find/replace all [PkgName] with CamelCase, all [pkgname] with lowercase
|
||||
|
||||
# [WEBSITE]
|
||||
|
||||
function _initialize_backend(::[PkgName]AbstractBackend; kw...)
|
||||
@eval begin
|
||||
import [PkgName]
|
||||
export [PkgName]
|
||||
# TODO: other initialization that needs to be eval-ed
|
||||
end
|
||||
# TODO: other initialization
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function _create_plot(pkg::[PkgName]AbstractBackend, d::KW)
|
||||
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
|
||||
# TODO: initialize the plot... title, xlabel, bgcolor, etc
|
||||
Plot(nothing, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
|
||||
function _add_series(::[PkgName]AbstractBackend, plt::Plot, d::KW)
|
||||
# TODO: add one series to the underlying package
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{[PkgName]AbstractBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
# TODO: add the annotation to the plot
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _before_update_plot(plt::Plot{[PkgName]AbstractBackend})
|
||||
end
|
||||
|
||||
# TODO: override this to update plot items (title, xlabel, etc) after creation
|
||||
function _update_plot(plt::Plot{[PkgName]AbstractBackend}, d::KW)
|
||||
end
|
||||
|
||||
function _update_plot_pos_size(plt::AbstractPlot{[PkgName]AbstractBackend}, d::KW)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# accessors for x/y data
|
||||
|
||||
# function getxy(plt::Plot{[PkgName]AbstractBackend}, i::Int)
|
||||
# # TODO: return a tuple of (x, y) vectors
|
||||
# end
|
||||
#
|
||||
# function setxy!{X,Y}(plt::Plot{[PkgName]AbstractBackend}, xy::Tuple{X,Y}, i::Integer)
|
||||
# # TODO: set the plot data from the (x,y) tuple
|
||||
# plt
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{[PkgName]AbstractBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
end
|
||||
|
||||
function _expand_limits(lims, plt::Plot{[PkgName]AbstractBackend}, isx::Bool)
|
||||
# TODO: call expand limits for each plot data
|
||||
end
|
||||
|
||||
function _remove_axis(plt::Plot{[PkgName]AbstractBackend}, isx::Bool)
|
||||
# TODO: if plot is inner subplot, might need to remove ticks or axis labels
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{[PkgName]AbstractBackend})
|
||||
# TODO: write a png to io
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{[PkgName]AbstractBackend})
|
||||
# TODO: display/show the plot
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Subplot{[PkgName]AbstractBackend})
|
||||
# TODO: display/show the subplot
|
||||
end
|
||||
@@ -0,0 +1,263 @@
|
||||
|
||||
# https://github.com/Evizero/UnicodePlots.jl
|
||||
|
||||
supportedArgs(::UnicodePlotsBackend) = [
|
||||
# :annotation,
|
||||
# :args,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
# :linecolor,
|
||||
# :fill,
|
||||
# :foreground_color,
|
||||
:group,
|
||||
# :heatmap_c,
|
||||
# :kwargs,
|
||||
:label,
|
||||
# :layout,
|
||||
:legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
# :linewidth,
|
||||
:markershape,
|
||||
# :markercolor,
|
||||
# :markersize,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
:bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
# :reg,
|
||||
# :ribbon,
|
||||
:show,
|
||||
:size,
|
||||
:title,
|
||||
:windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
# :xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
# :xscale,
|
||||
# :yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
]
|
||||
supportedAxes(::UnicodePlotsBackend) = [:auto, :left]
|
||||
supportedTypes(::UnicodePlotsBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline]
|
||||
supportedStyles(::UnicodePlotsBackend) = [:auto, :solid]
|
||||
supportedMarkers(::UnicodePlotsBackend) = [:none, :auto, :ellipse]
|
||||
supportedScales(::UnicodePlotsBackend) = [:identity]
|
||||
subplotSupported(::UnicodePlotsBackend) = true
|
||||
|
||||
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
function _initialize_backend(::UnicodePlotsBackend; kw...)
|
||||
@eval begin
|
||||
import UnicodePlots
|
||||
export UnicodePlots
|
||||
end
|
||||
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)
|
||||
|
||||
# figure out the plotting area xlim = [xmin, xmax] and ylim = [ymin, ymax]
|
||||
sargs = plt.seriesargs
|
||||
iargs = plt.plotargs
|
||||
|
||||
# get the x/y limits
|
||||
if get(iargs, :xlims, :auto) == :auto
|
||||
xlim = [Inf, -Inf]
|
||||
for d in sargs
|
||||
_expand_limits(xlim, d[:x])
|
||||
end
|
||||
else
|
||||
xmin, xmax = iargs[:xlims]
|
||||
xlim = [xmin, xmax]
|
||||
end
|
||||
|
||||
if get(iargs, :ylims, :auto) == :auto
|
||||
ylim = [Inf, -Inf]
|
||||
for d in sargs
|
||||
_expand_limits(ylim, d[:y])
|
||||
end
|
||||
else
|
||||
ymin, ymax = iargs[:ylims]
|
||||
ylim = [ymin, ymax]
|
||||
end
|
||||
|
||||
# we set x/y to have a single point, since we need to create the plot with some data.
|
||||
# since this point is at the bottom left corner of the plot, it shouldn't actually be shown
|
||||
x = Float64[xlim[1]]
|
||||
y = Float64[ylim[1]]
|
||||
|
||||
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
|
||||
width, height = iargs[:size]
|
||||
o = UnicodePlots.Plot(x, y; width = width,
|
||||
height = height,
|
||||
title = iargs[:title],
|
||||
# labels = iargs[:legend],
|
||||
xlim = xlim,
|
||||
ylim = ylim)
|
||||
|
||||
# set the axis labels
|
||||
UnicodePlots.xlabel!(o, iargs[:xlabel])
|
||||
UnicodePlots.ylabel!(o, iargs[:ylabel])
|
||||
|
||||
# now use the ! functions to add to the plot
|
||||
for d in sargs
|
||||
addUnicodeSeries!(o, d, iargs[:legend] != :none, xlim, ylim)
|
||||
end
|
||||
|
||||
# save the object
|
||||
plt.o = o
|
||||
end
|
||||
|
||||
|
||||
# add a single series
|
||||
function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
|
||||
|
||||
# get the function, or special handling for step/bar/hist
|
||||
lt = d[:linetype]
|
||||
|
||||
# handle hline/vline separately
|
||||
if lt in (:hline,:vline)
|
||||
for yi in d[:y]
|
||||
if lt == :hline
|
||||
UnicodePlots.lineplot!(o, xlim, [yi,yi])
|
||||
else
|
||||
UnicodePlots.lineplot!(o, [yi,yi], ylim)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
stepstyle = :post
|
||||
if lt == :path
|
||||
func = UnicodePlots.lineplot!
|
||||
elseif lt == :scatter || d[:markershape] != :none
|
||||
func = UnicodePlots.scatterplot!
|
||||
elseif lt == :steppost
|
||||
func = UnicodePlots.stairs!
|
||||
elseif lt == :steppre
|
||||
func = UnicodePlots.stairs!
|
||||
stepstyle = :pre
|
||||
else
|
||||
error("Linestyle $lt not supported by UnicodePlots")
|
||||
end
|
||||
|
||||
# get the series data and label
|
||||
x, y = [collect(float(d[s])) for s in (:x, :y)]
|
||||
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
|
||||
func(o, x, y; color = color, name = label, style = stepstyle)
|
||||
end
|
||||
|
||||
|
||||
function handlePlotColors(::UnicodePlotsBackend, d::KW)
|
||||
# TODO: something special for unicodeplots, since it doesn't take kindly to people messing with its color palette
|
||||
d[:color_palette] = [RGB(0,0,0)]
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
|
||||
function _create_plot(pkg::UnicodePlotsBackend, d::KW)
|
||||
plt = Plot(nothing, pkg, 0, d, KW[])
|
||||
|
||||
# do we want to give a new default size?
|
||||
if !haskey(plt.plotargs, :size) || plt.plotargs[:size] == _plotDefaults[:size]
|
||||
plt.plotargs[:size] = (60,20)
|
||||
end
|
||||
|
||||
plt
|
||||
end
|
||||
|
||||
function _add_series(::UnicodePlotsBackend, plt::Plot, d::KW)
|
||||
if d[:linetype] in (:sticks, :bar)
|
||||
d = barHack(; d...)
|
||||
elseif d[:linetype] == :hist
|
||||
d = barHack(; histogramHack(; d...)...)
|
||||
end
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
function _update_plot(plt::Plot{UnicodePlotsBackend}, d::KW)
|
||||
for k in (:title, :xlabel, :ylabel, :xlims, :ylims)
|
||||
if haskey(d, k)
|
||||
plt.plotargs[k] = d[k]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# since this is such a hack, it's only callable using `png`... should error during normal `writemime`
|
||||
function png(plt::AbstractPlot{UnicodePlotsBackend}, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "png")
|
||||
|
||||
# make some whitespace and show the plot
|
||||
println("\n\n\n\n\n\n")
|
||||
gui(plt)
|
||||
|
||||
@osx_only begin
|
||||
# BEGIN HACK
|
||||
|
||||
# wait while the plot gets drawn
|
||||
sleep(0.5)
|
||||
|
||||
# use osx screen capture when my terminal is maximized and cursor starts at the bottom (I know, right?)
|
||||
# TODO: compute size of plot to adjust these numbers (or maybe implement something good??)
|
||||
run(`screencapture -R50,600,700,420 $fn`)
|
||||
|
||||
# END HACK (phew)
|
||||
return
|
||||
end
|
||||
|
||||
error("Can only savepng on osx with UnicodePlots (though even then I wouldn't do it)")
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# we don't do very much for subplots... just stack them vertically
|
||||
|
||||
function _create_subplot(subplt::Subplot{UnicodePlotsBackend}, isbefore::Bool)
|
||||
isbefore && return false
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{UnicodePlotsBackend})
|
||||
rebuildUnicodePlot!(plt)
|
||||
show(plt.o)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend})
|
||||
for plt in subplt.plts
|
||||
gui(plt)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
# NOTE: backend should implement `html_body` and `html_head`
|
||||
|
||||
# CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl
|
||||
|
||||
|
||||
function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.plotargs, :window_title, "Plots.jl"))
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>$title</title>
|
||||
$(html_head(plt))
|
||||
</head>
|
||||
<body>
|
||||
$(html_body(plt))
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
|
||||
function open_browser_window(filename::AbstractString)
|
||||
@osx_only return run(`open $(filename)`)
|
||||
@linux_only return run(`xdg-open $(filename)`)
|
||||
@windows_only return run(`$(ENV["COMSPEC"]) /c start $(filename)`)
|
||||
warn("Unknown OS... cannot open browser window.")
|
||||
end
|
||||
|
||||
function write_temp_html(plt::AbstractPlot)
|
||||
html = standalone_html(plt; title = plt.plotargs[:title])
|
||||
filename = string(tempname(), ".html")
|
||||
output = open(filename, "w")
|
||||
write(output, html)
|
||||
close(output)
|
||||
filename
|
||||
end
|
||||
|
||||
function standalone_html_window(plt::AbstractPlot)
|
||||
filename = write_temp_html(plt)
|
||||
open_browser_window(filename)
|
||||
end
|
||||
|
||||
# uses wkhtmltopdf/wkhtmltoimage: http://wkhtmltopdf.org/downloads.html
|
||||
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)
|
||||
# write html to a temporary file
|
||||
html_fn = write_temp_html(plt)
|
||||
|
||||
# convert that html file to a temporary png file using wkhtmltoimage
|
||||
png_fn = tempname() * ".png"
|
||||
w, h = plt.plotargs[:size]
|
||||
html_to_png(html_fn, png_fn, w, h)
|
||||
|
||||
# now read that file data into io
|
||||
pngdata = readall(png_fn)
|
||||
write(io, pngdata)
|
||||
end
|
||||
@@ -0,0 +1,305 @@
|
||||
|
||||
# https://github.com/nolta/Winston.jl
|
||||
|
||||
# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation
|
||||
|
||||
supportedArgs(::WinstonBackend) = [
|
||||
:annotation,
|
||||
# :args,
|
||||
# :axis,
|
||||
# :background_color,
|
||||
:linecolor,
|
||||
:color_palette,
|
||||
:fillrange,
|
||||
:fillcolor,
|
||||
# :foreground_color,
|
||||
:group,
|
||||
# :heatmap_c,
|
||||
# :kwargs,
|
||||
:label,
|
||||
# :layout,
|
||||
:legend,
|
||||
:seriescolor, :seriesalpha,
|
||||
:linestyle,
|
||||
:linetype,
|
||||
:linewidth,
|
||||
:markershape,
|
||||
:markercolor,
|
||||
:markersize,
|
||||
# :markerstrokewidth,
|
||||
# :markerstrokecolor,
|
||||
# :markerstrokestyle,
|
||||
# :n,
|
||||
:bins,
|
||||
# :nc,
|
||||
# :nr,
|
||||
# :pos,
|
||||
:smooth,
|
||||
# :ribbon,
|
||||
:show,
|
||||
:size,
|
||||
:title,
|
||||
:windowtitle,
|
||||
:x,
|
||||
:xlabel,
|
||||
:xlims,
|
||||
# :xticks,
|
||||
:y,
|
||||
:ylabel,
|
||||
:ylims,
|
||||
# :yrightlabel,
|
||||
# :yticks,
|
||||
:xscale,
|
||||
:yscale,
|
||||
# :xflip,
|
||||
# :yflip,
|
||||
# :z,
|
||||
]
|
||||
supportedAxes(::WinstonBackend) = [:auto, :left]
|
||||
supportedTypes(::WinstonBackend) = [:none, :line, :path, :sticks, :scatter, :hist, :bar]
|
||||
supportedStyles(::WinstonBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
supportedMarkers(::WinstonBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
|
||||
supportedScales(::WinstonBackend) = [:identity, :log10]
|
||||
subplotSupported(::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.")
|
||||
import Winston, Gtk
|
||||
export Winston, Gtk
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
## dictionaries for conversion of Plots.jl names to Winston ones.
|
||||
@compat const winston_linestyle = KW(:solid=>"solid",
|
||||
:dash=>"dash",
|
||||
:dot=>"dotted",
|
||||
:dashdot=>"dotdashed"
|
||||
)
|
||||
|
||||
@compat const winston_marker = KW(:none=>".",
|
||||
:rect => "square",
|
||||
:ellipse=>"circle",
|
||||
:diamond=>"diamond",
|
||||
:utriangle=>"triangle",
|
||||
:dtriangle=>"down-triangle",
|
||||
:cross => "plus",
|
||||
:xcross => "cross",
|
||||
:star5 => "asterisk"
|
||||
)
|
||||
|
||||
function _before_add_series(plt::Plot{WinstonBackend})
|
||||
Winston.ghf(plt.o)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _create_plot(pkg::WinstonBackend, d::KW)
|
||||
wplt = Winston.FramedPlot(title = d[:title], xlabel = d[:xlabel], ylabel = d[:ylabel])
|
||||
|
||||
Plot(wplt, pkg, 0, d, KW[])
|
||||
end
|
||||
|
||||
copy_remove(d::KW, s::Symbol) = delete!(copy(d), s)
|
||||
|
||||
function addRegressionLineWinston(d::KW, wplt)
|
||||
xs, ys = regressionXY(d[:x], d[:y])
|
||||
Winston.add(wplt, Winston.Curve(xs, ys, kind="dotted"))
|
||||
end
|
||||
|
||||
function getWinstonItems(plt::Plot)
|
||||
if isa(plt.o, Winston.FramedPlot)
|
||||
wplt = plt.o
|
||||
window, canvas = nothing, nothing
|
||||
else
|
||||
window, canvas, wplt = plt.o
|
||||
end
|
||||
window, canvas, wplt
|
||||
end
|
||||
|
||||
function _add_series(::WinstonBackend, plt::Plot, d::KW)
|
||||
window, canvas, wplt = getWinstonItems(plt)
|
||||
|
||||
# until we call it normally, do the hack
|
||||
if d[:linetype] == :bar
|
||||
d = barHack(;d...)
|
||||
end
|
||||
|
||||
|
||||
e = KW()
|
||||
e[:color] = getColor(d[:linecolor])
|
||||
e[:linewidth] = d[:linewidth]
|
||||
e[:kind] = winston_linestyle[d[:linestyle]]
|
||||
e[:symbolkind] = winston_marker[d[:markershape]]
|
||||
# markercolor # same choices as `color`, or :match will set the color to be the same as `color`
|
||||
e[:symbolsize] = d[:markersize] / 5
|
||||
|
||||
# pos # (Int,Int), move the enclosing window to this position
|
||||
# screen # Integer, move enclosing window to this screen number (for multiscreen desktops)
|
||||
|
||||
|
||||
|
||||
## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :hist2d, :hexbin, :hist, :bar
|
||||
if d[:linetype] == :none
|
||||
Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))
|
||||
|
||||
elseif d[:linetype] == :path
|
||||
x, y = d[:x], d[:y]
|
||||
Winston.add(wplt, Winston.Curve(x, y; e...))
|
||||
|
||||
fillrange = d[:fillrange]
|
||||
if fillrange != nothing
|
||||
if isa(fillrange, AbstractVector)
|
||||
y2 = fillrange
|
||||
else
|
||||
y2 = Float64[fillrange for yi in y]
|
||||
end
|
||||
Winston.add(wplt, Winston.FillBetween(x, y, x, y2, fillcolor=getColor(d[:fillcolor])))
|
||||
end
|
||||
|
||||
elseif d[:linetype] == :scatter
|
||||
if d[:markershape] == :none
|
||||
d[:markershape] = :ellipse
|
||||
end
|
||||
|
||||
# elseif d[:linetype] == :step
|
||||
# fn = Winston.XXX
|
||||
|
||||
# elseif d[:linetype] == :stepinverted
|
||||
# fn = Winston.XXX
|
||||
|
||||
elseif d[:linetype] == :sticks
|
||||
Winston.add(wplt, Winston.Stems(d[:x], d[:y]; e...))
|
||||
|
||||
# elseif d[:linetype] == :dots
|
||||
# fn = Winston.XXX
|
||||
|
||||
# elseif d[:linetype] == :hist2d
|
||||
# fn = Winston.XXX
|
||||
|
||||
# elseif d[:linetype] == :hexbin
|
||||
# fn = Winston.XXX
|
||||
|
||||
elseif d[:linetype] == :hist
|
||||
hst = hist(d[:y], d[:bins])
|
||||
Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :bins)...))
|
||||
|
||||
# elseif d[:linetype] == :bar
|
||||
# # fn = Winston.XXX
|
||||
|
||||
else
|
||||
error("linetype $(d[:linetype]) not supported by Winston.")
|
||||
|
||||
end
|
||||
|
||||
|
||||
# markershape
|
||||
if d[:markershape] != :none
|
||||
Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))
|
||||
end
|
||||
|
||||
|
||||
# optionally add a regression line
|
||||
d[:smooth] && d[:linetype] != :hist && addRegressionLineWinston(d, wplt)
|
||||
|
||||
push!(plt.seriesargs, d)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
@compat const _winstonNames = KW(
|
||||
:xlims => :xrange,
|
||||
:ylims => :yrange,
|
||||
:xscale => :xlog,
|
||||
:yscale => :ylog,
|
||||
)
|
||||
|
||||
function _update_plot(plt::Plot{WinstonBackend}, d::KW)
|
||||
window, canvas, wplt = getWinstonItems(plt)
|
||||
for k in (:xlabel, :ylabel, :title, :xlims, :ylims)
|
||||
if haskey(d, k)
|
||||
Winston.setattr(wplt, string(get(_winstonNames, k, k)), d[k])
|
||||
end
|
||||
end
|
||||
|
||||
for k in (:xscale, :yscale)
|
||||
if haskey(d, k)
|
||||
islogscale = d[k] == :log10
|
||||
Winston.setattr(wplt, (k == :xscale ? :xlog : :ylog), islogscale)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::@compat(AbstractString))
|
||||
Winston.text(x, y, val)
|
||||
end
|
||||
|
||||
function _add_annotations{X,Y,V}(plt::Plot{WinstonBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
|
||||
for ann in anns
|
||||
createWinstonAnnotationObject(plt, ann...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _create_subplot(subplt::Subplot{WinstonBackend}, isbefore::Bool)
|
||||
# TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function addWinstonLegend(plt::Plot, wplt)
|
||||
if plt.plotargs[:legend] != :none
|
||||
Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs])
|
||||
end
|
||||
end
|
||||
|
||||
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{WinstonBackend})
|
||||
window, canvas, wplt = getWinstonItems(plt)
|
||||
addWinstonLegend(plt, wplt)
|
||||
writemime(io, "image/png", wplt)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend})
|
||||
|
||||
window, canvas, wplt = getWinstonItems(plt)
|
||||
|
||||
if window == nothing
|
||||
if Winston.output_surface != :gtk
|
||||
error("Gtk is the only supported display for Winston in Plots. Set `output_surface = gtk` in src/Winston.ini")
|
||||
end
|
||||
# initialize window
|
||||
w,h = plt.plotargs[:size]
|
||||
canvas = Gtk.GtkCanvasLeaf()
|
||||
window = Gtk.GtkWindowLeaf(canvas, plt.plotargs[:windowtitle], w, h)
|
||||
plt.o = (window, canvas, wplt)
|
||||
end
|
||||
|
||||
addWinstonLegend(plt, wplt)
|
||||
|
||||
Winston.display(canvas, wplt)
|
||||
Gtk.showall(window)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend})
|
||||
# TODO: display/show the Subplot object
|
||||
end
|
||||
@@ -0,0 +1,422 @@
|
||||
|
||||
abstract ColorScheme
|
||||
|
||||
getColor(scheme::ColorScheme) = getColor(scheme, 1)
|
||||
getColorVector(scheme::ColorScheme) = [getColor(scheme)]
|
||||
|
||||
colorscheme(scheme::ColorScheme) = scheme
|
||||
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(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...)
|
||||
colorscheme{T<:Colorant}(cs::AVec{T}; kw...) = 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)]'
|
||||
colorscheme(c::Colorant; kw...) = ColorWrapper(c; kw...)
|
||||
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
|
||||
convertColor(c::@compat(Union{AbstractString, Symbol})) = parse(Colorant, string(c))
|
||||
convertColor(c::Colorant) = c
|
||||
convertColor(cvec::AbstractVector) = map(convertColor, cvec)
|
||||
convertColor(c::ColorScheme) = c
|
||||
|
||||
function convertColor(c, α::Real)
|
||||
c = convertColor(c)
|
||||
RGBA(RGB(c), α)
|
||||
end
|
||||
convertColor(cs::AVec, α::Real) = map(c -> convertColor(c, α), cs)
|
||||
convertColor(c, α::@compat(Void)) = convertColor(c)
|
||||
|
||||
# backup... try to convert
|
||||
getColor(c) = convertColor(c)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
function darken(c, v=0.1)
|
||||
rgba = convert(RGBA, c)
|
||||
r = max(0, min(rgba.r - v, 1))
|
||||
g = max(0, min(rgba.g - v, 1))
|
||||
b = max(0, min(rgba.b - v, 1))
|
||||
RGBA(r,g,b,rgba.alpha)
|
||||
end
|
||||
function lighten(c, v=0.3)
|
||||
darken(c, -v)
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
const _rainbowColors = [colorant"blue", colorant"purple", 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)]
|
||||
|
||||
const _gradients = KW(
|
||||
:blues => [colorant"lightblue", colorant"darkblue"],
|
||||
:reds => [colorant"lightpink", colorant"darkred"],
|
||||
:greens => [colorant"lightgreen", colorant"darkgreen"],
|
||||
:redsblues => [colorant"darkred", RGB(0.8,0.85,0.8), colorant"darkblue"],
|
||||
:bluesreds => [colorant"darkblue", RGB(0.8,0.85,0.8), colorant"darkred"],
|
||||
:heat => [colorant"lightyellow", colorant"orange", colorant"darkred"],
|
||||
:grays => [RGB(.95,.95,.95),RGB(.05,.05,.05)],
|
||||
:rainbow => _rainbowColors,
|
||||
:lightrainbow => map(lighten, _rainbowColors),
|
||||
:darkrainbow => map(darken, _rainbowColors),
|
||||
:darktest => _testColors,
|
||||
:lighttest => map(c -> lighten(c, 0.3), _testColors),
|
||||
)
|
||||
|
||||
function register_gradient_colors{C<:Colorant}(name::Symbol, colors::AVec{C})
|
||||
_gradients[name] = colors
|
||||
end
|
||||
|
||||
include("color_gradients.jl")
|
||||
|
||||
default_gradient() = ColorGradient(:inferno)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Continuous gradient between values. Wraps a list of bounding colors and the values they represent."
|
||||
immutable ColorGradient <: ColorScheme
|
||||
colors::Vector
|
||||
values::Vector
|
||||
|
||||
function ColorGradient{S<:Real}(cs::AVec, vals::AVec{S} = linspace(0, 1, length(cs)); alpha = nothing)
|
||||
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))]
|
||||
# new(convertColor(cs,alpha), vs)
|
||||
|
||||
# interpolate the colors for each value
|
||||
vals = merge(linspace(0, 1, length(cs)), vals)
|
||||
grad = ColorGradient(cs)
|
||||
cs = [getColorZ(grad, z) for z in linspace(0, 1, length(vals))]
|
||||
new(convertColor(cs, alpha), vals)
|
||||
end
|
||||
end
|
||||
|
||||
# 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...)
|
||||
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))
|
||||
end
|
||||
ColorGradient(cs, vals; kw...)
|
||||
end
|
||||
|
||||
# function ColorGradient{T<:Real}(cs::AVec, vals::AVec{T} = linspace(0, 1, length(cs)); kw...)
|
||||
# ColorGradient(map(convertColor, cs), vals; kw...)
|
||||
# end
|
||||
|
||||
function ColorGradient(grad::ColorGradient; alpha = nothing)
|
||||
ColorGradient(convertColor(grad.colors, alpha), grad.values)
|
||||
end
|
||||
|
||||
getColor(gradient::ColorGradient, idx::Int) = gradient.colors[mod1(idx, length(gradient.colors))]
|
||||
|
||||
function getColorZ(gradient::ColorGradient, z::Real)
|
||||
cs = gradient.colors
|
||||
vs = gradient.values
|
||||
n = length(cs)
|
||||
@assert n > 0 && n == length(vs)
|
||||
|
||||
# can we just return the first color?
|
||||
if z <= vs[1] || n == 1
|
||||
return cs[1]
|
||||
end
|
||||
|
||||
# find the bounding colors and interpolate
|
||||
for i in 2:n
|
||||
if z <= vs[i]
|
||||
return interpolate_rgb(cs[i-1], cs[i], (z - vs[i-1]) / (vs[i] - vs[i-1]))
|
||||
end
|
||||
end
|
||||
|
||||
# if we get here, return the last color
|
||||
cs[end]
|
||||
end
|
||||
|
||||
getColorVector(gradient::ColorGradient) = gradient.colors
|
||||
|
||||
# for 0.3
|
||||
Colors.RGBA(c::Colorant) = RGBA(red(c), green(c), blue(c), alpha(c))
|
||||
Colors.RGB(c::Colorant) = RGB(red(c), green(c), blue(c))
|
||||
|
||||
function interpolate_rgb(c1::Colorant, c2::Colorant, w::Real)
|
||||
rgb1 = RGBA(c1)
|
||||
rgb2 = RGBA(c2)
|
||||
r = interpolate(rgb1.r, rgb2.r, w)
|
||||
g = interpolate(rgb1.g, rgb2.g, w)
|
||||
b = interpolate(rgb1.b, rgb2.b, w)
|
||||
a = interpolate(rgb1.alpha, rgb2.alpha, w)
|
||||
RGBA(r, g, b, a)
|
||||
end
|
||||
|
||||
|
||||
function interpolate(v1::Real, v2::Real, w::Real)
|
||||
(1-w) * v1 + w * v2
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a function, taking an index and returning a Colorant"
|
||||
immutable ColorFunction <: ColorScheme
|
||||
f::Function
|
||||
end
|
||||
|
||||
getColor(scheme::ColorFunction, idx::Int) = scheme.f(idx)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a function, taking an z-value and returning a Colorant"
|
||||
immutable ColorZFunction <: ColorScheme
|
||||
f::Function
|
||||
end
|
||||
|
||||
getColorZ(scheme::ColorFunction, z::Real) = scheme.f(z)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a vector of colors... may be vector of Symbol/String/Colorant"
|
||||
immutable ColorVector <: ColorScheme
|
||||
v::Vector{Colorant}
|
||||
ColorVector(v::AVec; alpha = nothing) = new(convertColor(v,alpha))
|
||||
end
|
||||
|
||||
getColor(scheme::ColorVector, idx::Int) = convertColor(scheme.v[mod1(idx, length(scheme.v))])
|
||||
getColorVector(scheme::ColorVector) = scheme.v
|
||||
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
"Wraps a single color"
|
||||
immutable ColorWrapper <: ColorScheme
|
||||
c::RGBA
|
||||
ColorWrapper(c::Colorant; alpha = nothing) = new(convertColor(c, alpha))
|
||||
end
|
||||
|
||||
ColorWrapper(s::Symbol; alpha = nothing) = ColorWrapper(convertColor(parse(Colorant, s), alpha))
|
||||
|
||||
getColor(scheme::ColorWrapper, idx::Int) = scheme.c
|
||||
getColorZ(scheme::ColorWrapper, z::Real) = scheme.c
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
|
||||
isbackgrounddark(bgcolor::Color) = Lab(bgcolor).l < 0.5
|
||||
|
||||
# move closer to lighter/darker depending on background value
|
||||
function adjustAway(val, bgval, vmin=0., vmax=100.)
|
||||
if bgval < 0.5 * (vmax+vmin)
|
||||
tmp = max(val, bgval)
|
||||
return 0.5 * (tmp + max(tmp, vmax))
|
||||
else
|
||||
tmp = min(val, bgval)
|
||||
return 0.5 * (tmp + min(tmp, vmin))
|
||||
end
|
||||
end
|
||||
|
||||
# borrowed from http://stackoverflow.com/a/1855903:
|
||||
lightnessLevel(c::Colorant) = 0.299 * red(c) + 0.587 * green(c) + 0.114 * blue(c)
|
||||
|
||||
isdark(c::Colorant) = lightnessLevel(c) < 0.5
|
||||
islight(c::Colorant) = !isdark(c)
|
||||
|
||||
function convertHexToRGB(h::Unsigned)
|
||||
mask = 0x0000FF
|
||||
RGB([(x & mask) / 0xFF for x in (h >> 16, h >> 8, h)]...)
|
||||
end
|
||||
|
||||
# note: I found this list of hex values in a comment by Tatarize here: http://stackoverflow.com/a/12224359
|
||||
const _masterColorList = [
|
||||
0xFFFFFF, 0x000000, 0x0000FF, 0x00FF00, 0xFF0000, 0x01FFFE, 0xFFA6FE, 0xFFDB66, 0x006401, 0x010067,
|
||||
0x95003A, 0x007DB5, 0xFF00F6, 0xFFEEE8, 0x774D00, 0x90FB92, 0x0076FF, 0xD5FF00, 0xFF937E, 0x6A826C,
|
||||
0xFF029D, 0xFE8900, 0x7A4782, 0x7E2DD2, 0x85A900, 0xFF0056, 0xA42400, 0x00AE7E, 0x683D3B, 0xBDC6FF,
|
||||
0x263400, 0xBDD393, 0x00B917, 0x9E008E, 0x001544, 0xC28C9F, 0xFF74A3, 0x01D0FF, 0x004754, 0xE56FFE,
|
||||
0x788231, 0x0E4CA1, 0x91D0CB, 0xBE9970, 0x968AE8, 0xBB8800, 0x43002C, 0xDEFF74, 0x00FFC6, 0xFFE502,
|
||||
0x620E00, 0x008F9C, 0x98FF52, 0x7544B1, 0xB500FF, 0x00FF78, 0xFF6E41, 0x005F39, 0x6B6882, 0x5FAD4E,
|
||||
0xA75740, 0xA5FFD2, 0xFFB167, 0x009BFF, 0xE85EBE
|
||||
]
|
||||
const _allColors = map(convertHexToRGB, _masterColorList)
|
||||
const _darkColors = filter(isdark, _allColors)
|
||||
const _lightColors = filter(islight, _allColors)
|
||||
const _sortedColorsForDarkBackground = vcat(_lightColors, reverse(_darkColors[2:end]))
|
||||
const _sortedColorsForLightBackground = vcat(_darkColors, reverse(_lightColors[2:end]))
|
||||
|
||||
const _defaultNumColors = 17
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
# Methods to automatically generate gradients for color selection based on
|
||||
# background color and a short list of seed colors
|
||||
|
||||
# here are some magic constants that could be changed if you really want
|
||||
const _lightness_darkbg = [80.0]
|
||||
const _lightness_lightbg = [60.0]
|
||||
const _lch_c_const = [60]
|
||||
|
||||
function adjust_lch(color, l, c)
|
||||
lch = convert(LCHab, color)
|
||||
convert(RGB, LCHab(l, c, lch.h))
|
||||
end
|
||||
|
||||
function lightness_from_background(bgcolor)
|
||||
bglight = convert(LCHab, bgcolor).l
|
||||
bglight < 50.0 ? _lightness_darkbg[1] : _lightness_lightbg[1]
|
||||
end
|
||||
|
||||
function gradient_from_list(cs)
|
||||
zvalues = Plots.get_zvalues(length(cs))
|
||||
indices = sortperm(zvalues)
|
||||
sorted_colors = map(RGBA, cs[indices])
|
||||
sorted_zvalues = zvalues[indices]
|
||||
ColorGradient(sorted_colors, sorted_zvalues)
|
||||
end
|
||||
|
||||
function generate_colorgradient(bgcolor = colorant"white";
|
||||
color_bases = color_bases=[colorant"steelblue",colorant"orangered"],
|
||||
lightness = lightness_from_background(bgcolor),
|
||||
chroma = _lch_c_const[1],
|
||||
n = _defaultNumColors)
|
||||
seed_colors = vcat(bgcolor, map(c -> adjust_lch(c, lightness, chroma), color_bases))
|
||||
colors = distinguishable_colors(n,
|
||||
seed_colors,
|
||||
lchoices=Float64[lightness],
|
||||
cchoices=Float64[chroma],
|
||||
hchoices=linspace(0, 340, 20)
|
||||
)[2:end]
|
||||
gradient_from_list(colors)
|
||||
end
|
||||
|
||||
function get_color_palette(palette, bgcolor::@compat(Union{Colorant,ColorWrapper}), numcolors::Integer)
|
||||
grad = if palette == :auto
|
||||
generate_colorgradient(bgcolor)
|
||||
else
|
||||
ColorGradient(palette)
|
||||
end
|
||||
zrng = get_zvalues(numcolors)
|
||||
RGBA[getColorZ(grad, z) for z in zrng]
|
||||
end
|
||||
|
||||
function get_color_palette(palette::Vector{RGBA}, bgcolor::@compat(Union{Colorant,ColorWrapper}), numcolors::Integer)
|
||||
palette
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function getpctrange(n::Int)
|
||||
n > 0 || error()
|
||||
n == 1 && return zeros(1)
|
||||
zs = [0.0, 1.0]
|
||||
for i in 3:n
|
||||
sorted = sort(zs)
|
||||
diffs = diff(sorted)
|
||||
widestj = 0
|
||||
widest = 0.0
|
||||
for (j,d) in enumerate(diffs)
|
||||
if d > widest
|
||||
widest = d
|
||||
widestj = j
|
||||
end
|
||||
end
|
||||
push!(zs, sorted[widestj] + 0.5 * diffs[widestj])
|
||||
end
|
||||
zs
|
||||
end
|
||||
|
||||
function get_zvalues(n::Int)
|
||||
offsets = getpctrange(ceil(Int,n/4)+1)/4
|
||||
offsets = vcat(offsets[1], offsets[3:end])
|
||||
zvalues = Float64[]
|
||||
for offset in offsets
|
||||
append!(zvalues, offset + [0.0, 0.5, 0.25, 0.75])
|
||||
end
|
||||
vcat(zvalues[1], 1.0, zvalues[2:n-1])
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
make255(x) = round(Int, 255 * x)
|
||||
|
||||
function webcolor(c::Color)
|
||||
@sprintf("rgb(%d, %d, %d)", [make255(f(c)) for f in [red,green,blue]]...)
|
||||
end
|
||||
function webcolor(c::TransparentColor)
|
||||
@sprintf("rgba(%d, %d, %d, %1.3f)", [make255(f(c)) for f in [red,green,blue]]..., alpha(c))
|
||||
end
|
||||
webcolor(cs::ColorScheme) = webcolor(getColor(cs))
|
||||
webcolor(c) = webcolor(convertColor(c))
|
||||
webcolor(c, α) = webcolor(convertColor(getColor(c), α))
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
# TODO: allow the setting of the algorithm, either by passing a symbol (:colordiff, :fixed, etc) or a function?
|
||||
|
||||
function handlePlotColors(::AbstractBackend, d::KW)
|
||||
if :background_color in supportedArgs()
|
||||
bgcolor = convertColor(d[:background_color])
|
||||
else
|
||||
bgcolor = _plotDefaults[:background_color]
|
||||
if d[:background_color] != _plotDefaults[:background_color]
|
||||
warn("Cannot set background_color with backend $(backend())")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
d[:color_palette] = get_color_palette(get(d, :color_palette, :auto), bgcolor, 100)
|
||||
|
||||
|
||||
# set the foreground color (text, ticks, gridlines) to be white or black depending
|
||||
# on how dark the background is.
|
||||
fgcolor = get(d, :foreground_color, :auto)
|
||||
fgcolor = if fgcolor == :auto
|
||||
isdark(bgcolor) ? colorant"white" : colorant"black"
|
||||
else
|
||||
convertColor(fgcolor)
|
||||
end
|
||||
|
||||
# bg/fg color
|
||||
d[:background_color] = colorscheme(bgcolor)
|
||||
d[:foreground_color] = colorscheme(fgcolor)
|
||||
|
||||
# update sub-background colors
|
||||
for bgtype in ("legend", "inside", "outside")
|
||||
bgsym = symbol("background_color_" * bgtype)
|
||||
if d[bgsym] == :match
|
||||
d[bgsym] = d[:background_color]
|
||||
elseif d[bgsym] == nothing
|
||||
d[bgsym] = colorscheme(RGBA(0,0,0,0))
|
||||
end
|
||||
end
|
||||
|
||||
# update sub-foreground colors
|
||||
for fgtype in ("legend", "grid", "axis", "text", "border", "guide")
|
||||
fgsym = symbol("foreground_color_" * fgtype)
|
||||
if d[fgsym] == :match
|
||||
d[fgsym] = d[:foreground_color]
|
||||
elseif d[fgsym] == nothing
|
||||
d[fgsym] = colorscheme(RGBA(0,0,0,0))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically
|
||||
function getSeriesRGBColor(c, plotargs::KW, n::Int)
|
||||
|
||||
if c == :auto
|
||||
c = autopick(plotargs[:color_palette], n)
|
||||
end
|
||||
|
||||
# c should now be a subtype of ColorScheme
|
||||
colorscheme(c)
|
||||
end
|
||||
@@ -0,0 +1,438 @@
|
||||
|
||||
|
||||
typealias P2 FixedSizeArrays.Vec{2,Float64}
|
||||
typealias 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)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
||||
immutable Shape
|
||||
# vertices::AVec
|
||||
x::AVec
|
||||
y::AVec
|
||||
end
|
||||
|
||||
# Shape(x, y) = Shape(collect(zip(x, y)))
|
||||
Shape(verts::AVec) = Shape(unzip(verts)...)
|
||||
|
||||
# get_xs(shape::Shape) = Float64[v[1] for v in shape.vertices]
|
||||
# get_ys(shape::Shape) = Float64[v[2] for v in shape.vertices]
|
||||
get_xs(shape::Shape) = shape.x
|
||||
get_ys(shape::Shape) = shape.y
|
||||
vertices(shape::Shape) = collect(zip(shape.x, shape.y))
|
||||
|
||||
|
||||
function shape_coords(shape::Shape)
|
||||
# unzip(shape.vertices)
|
||||
shape.x, shape.y
|
||||
end
|
||||
|
||||
function shape_coords(shapes::AVec{Shape})
|
||||
length(shapes) == 0 && return zeros(0), zeros(0)
|
||||
xs = map(get_xs, shapes)
|
||||
ys = map(get_ys, shapes)
|
||||
# x, y = shapes[1].x, shapes[1].y #unzip(shapes[1].vertices)
|
||||
x, y = map(copy, shape_coords(shapes[1]))
|
||||
for shape in shapes[2:end]
|
||||
# tmpx, tmpy = unzip(shape.vertices)
|
||||
nanappend!(x, shape.x)
|
||||
nanappend!(y, shape.y)
|
||||
# x = vcat(x, NaN, tmpx)
|
||||
# y = vcat(y, NaN, tmpy)
|
||||
end
|
||||
x, y
|
||||
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)]
|
||||
end
|
||||
|
||||
"interleave 2 vectors into each other (like a zipper's teeth)"
|
||||
function weave(x,y; ordering = Vector[x,y])
|
||||
ret = eltype(x)[]
|
||||
done = false
|
||||
while !done
|
||||
for o in ordering
|
||||
try
|
||||
push!(ret, shift!(o))
|
||||
end
|
||||
# try
|
||||
# push!(ret, shift!(y))
|
||||
# end
|
||||
end
|
||||
done = isempty(x) && isempty(y)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
|
||||
"create a star by weaving together points from an outer and inner circle. `n` is the number of arms"
|
||||
function makestar(n; offset = -0.5, radius = 1.0)
|
||||
z1 = offset * π
|
||||
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])
|
||||
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])
|
||||
end
|
||||
|
||||
|
||||
function makecross(; offset = -0.5, radius = 1.0)
|
||||
z2 = offset * π
|
||||
z1 = z2 - π/8
|
||||
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])
|
||||
end
|
||||
|
||||
|
||||
from_polar(angle, dist) = P2(dist*cos(angle), dist*sin(angle))
|
||||
|
||||
function makearrowhead(angle; h = 2.0, w = 0.4)
|
||||
tip = from_polar(angle, h)
|
||||
Shape(P2[(0,0), from_polar(angle - 0.5π, w) - tip,
|
||||
from_polar(angle + 0.5π, w) - tip, (0,0)])
|
||||
end
|
||||
|
||||
const _shapes = KW(
|
||||
:ellipse => makeshape(20),
|
||||
:rect => makeshape(4, offset=-0.25),
|
||||
:diamond => makeshape(4),
|
||||
:utriangle => makeshape(3),
|
||||
:dtriangle => makeshape(3, offset=0.5),
|
||||
:pentagon => makeshape(5),
|
||||
:hexagon => makeshape(6),
|
||||
:heptagon => makeshape(7),
|
||||
:octagon => makeshape(8),
|
||||
:cross => makecross(offset=-0.25),
|
||||
:xcross => makecross(),
|
||||
:vline => Shape([(0,1),(0,-1)]),
|
||||
:hline => Shape([(1,0),(-1,0)]),
|
||||
)
|
||||
|
||||
for n in [4,5,6,7,8]
|
||||
_shapes[symbol("star$n")] = makestar(n)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
center(shape::Shape) = (mean(shape.x), mean(shape.y))
|
||||
|
||||
function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
sx, sy = shape_coords(shape)
|
||||
cx, cy = c
|
||||
for i=1:length(sx)
|
||||
sx[i] = (sx[i] - cx) * x + cx
|
||||
sy[i] = (sy[i] - cy) * y + cy
|
||||
end
|
||||
shape
|
||||
end
|
||||
|
||||
function Base.scale(shape::Shape, x::Real, y::Real = x, c = center(shape))
|
||||
shapecopy = deepcopy(shape)
|
||||
scale!(shape, x, y, c)
|
||||
end
|
||||
|
||||
function translate!(shape::Shape, x::Real, y::Real = x)
|
||||
sx, sy = shape_coords(shape)
|
||||
for i=1:length(sx)
|
||||
sx[i] += x
|
||||
sy[i] += y
|
||||
end
|
||||
shape
|
||||
end
|
||||
|
||||
function translate(shape::Shape, x::Real, y::Real = x)
|
||||
shapecopy = deepcopy(shape)
|
||||
translate!(shape, x, y)
|
||||
end
|
||||
|
||||
function rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real)
|
||||
(x - centerx) * cos(Θ) - (y - centery) * sin(Θ) + centerx
|
||||
end
|
||||
|
||||
function rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real)
|
||||
(y - centery) * cos(Θ) + (x - centerx) * sin(Θ) + centery
|
||||
end
|
||||
|
||||
function rotate(x::Real, y::Real, θ::Real, c = center(shape))
|
||||
cx, cy = c
|
||||
rotate_x(x, y, Θ, cx, cy), rotate_y(x, y, Θ, cx, cy)
|
||||
end
|
||||
|
||||
function rotate!(shape::Shape, Θ::Real, c = center(shape))
|
||||
x, y = shape_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)
|
||||
end
|
||||
shape
|
||||
end
|
||||
|
||||
function rotate(shape::Shape, Θ::Real, c = center(shape))
|
||||
shapecopy = deepcopy(shape)
|
||||
rotate!(shapecopy, Θ, c)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
immutable Font
|
||||
family::AbstractString
|
||||
pointsize::Int
|
||||
halign::Symbol
|
||||
valign::Symbol
|
||||
rotation::Float64
|
||||
color::Colorant
|
||||
end
|
||||
|
||||
"Create a Font from a list of unordered features"
|
||||
function font(args...)
|
||||
|
||||
# defaults
|
||||
family = "Helvetica"
|
||||
pointsize = 14
|
||||
halign = :hcenter
|
||||
valign = :vcenter
|
||||
rotation = 0.0
|
||||
color = colorant"black"
|
||||
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
|
||||
if arg == :center
|
||||
halign = :hcenter
|
||||
valign = :vcenter
|
||||
elseif arg in (:hcenter, :left, :right)
|
||||
halign = arg
|
||||
elseif arg in (:vcenter, :top, :bottom)
|
||||
valign = arg
|
||||
elseif T <: Colorant
|
||||
color = arg
|
||||
elseif T <: @compat Union{Symbol,AbstractString}
|
||||
try
|
||||
color = parse(Colorant, string(arg))
|
||||
catch
|
||||
family = string(arg)
|
||||
end
|
||||
elseif typeof(arg) <: Integer
|
||||
pointsize = arg
|
||||
elseif typeof(arg) <: Real
|
||||
rotation = convert(Float64, arg)
|
||||
else
|
||||
warn("Unused font arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
Font(family, pointsize, halign, valign, rotation, color)
|
||||
end
|
||||
|
||||
"Wrap a string with font info"
|
||||
immutable PlotText
|
||||
str::@compat(AbstractString)
|
||||
font::Font
|
||||
end
|
||||
PlotText(str) = PlotText(string(str), font())
|
||||
|
||||
function text(str, args...)
|
||||
PlotText(string(str), font(args...))
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
immutable Stroke
|
||||
width
|
||||
color
|
||||
alpha
|
||||
style
|
||||
end
|
||||
|
||||
function stroke(args...; alpha = nothing)
|
||||
# defaults
|
||||
# width = 1
|
||||
# color = colorant"black"
|
||||
# style = :solid
|
||||
width = nothing
|
||||
color = nothing
|
||||
style = nothing
|
||||
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
|
||||
# if arg in _allStyles
|
||||
if allStyles(arg)
|
||||
style = arg
|
||||
elseif T <: Colorant
|
||||
color = arg
|
||||
elseif T <: @compat Union{Symbol,AbstractString}
|
||||
try
|
||||
color = parse(Colorant, string(arg))
|
||||
end
|
||||
# elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg)
|
||||
elseif allAlphas(arg)
|
||||
alpha = arg
|
||||
# elseif typeof(arg) <: Real
|
||||
elseif allReals(arg)
|
||||
width = arg
|
||||
else
|
||||
warn("Unused stroke arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
Stroke(width, color, alpha, style)
|
||||
end
|
||||
|
||||
|
||||
immutable Brush
|
||||
size # fillrange, markersize, or any other sizey attribute
|
||||
color
|
||||
alpha
|
||||
end
|
||||
|
||||
function brush(args...; alpha = nothing)
|
||||
# defaults
|
||||
# sz = 1
|
||||
# color = colorant"black"
|
||||
size = nothing
|
||||
color = nothing
|
||||
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
|
||||
if T <: Colorant
|
||||
color = arg
|
||||
elseif T <: @compat Union{Symbol,AbstractString}
|
||||
try
|
||||
color = parse(Colorant, string(arg))
|
||||
end
|
||||
# elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg)
|
||||
elseif allAlphas(arg)
|
||||
alpha = arg
|
||||
# elseif typeof(arg) <: Real
|
||||
elseif allReals(arg)
|
||||
size = arg
|
||||
else
|
||||
warn("Unused brush arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
|
||||
Brush(size, color, alpha)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"type which represents z-values for colors and sizes (and anything else that might come up)"
|
||||
immutable ZValues
|
||||
values::Vector{Float64}
|
||||
zrange::Tuple{Float64,Float64}
|
||||
end
|
||||
|
||||
function zvalues{T<:Real}(values::AVec{T}, zrange::Tuple{T,T} = (minimum(values), maximum(values)))
|
||||
ZValues(collect(float(values)), map(Float64, zrange))
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
abstract AbstractSurface
|
||||
|
||||
"represents a contour or surface mesh"
|
||||
immutable Surface{M<:AMat} <: AbstractSurface
|
||||
# x::AVec
|
||||
# y::AVec
|
||||
surf::M
|
||||
end
|
||||
|
||||
Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for xi in x, yi in y])
|
||||
|
||||
Base.Array(surf::Surface) = surf.surf
|
||||
|
||||
for f in (:length, :size)
|
||||
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
|
||||
end
|
||||
Base.copy(surf::Surface) = Surface(copy(surf.surf))
|
||||
|
||||
|
||||
"For the case of representing a surface as a function of x/y... can possibly avoid allocations."
|
||||
immutable SurfaceFunction <: AbstractSurface
|
||||
f::Function
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
type OHLC{T<:Real}
|
||||
open::T
|
||||
high::T
|
||||
low::T
|
||||
close::T
|
||||
end
|
||||
|
||||
|
||||
# @require FixedSizeArrays begin
|
||||
|
||||
type BezierCurve{T <: FixedSizeArrays.Vec}
|
||||
control_points::Vector{T}
|
||||
end
|
||||
|
||||
function Base.call(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
|
||||
|
||||
Base.mean(x::Real, y::Real) = 0.5*(x+y)
|
||||
Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps)
|
||||
|
||||
curve_points(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, linspace(range..., n))
|
||||
|
||||
# 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
|
||||
[]
|
||||
end
|
||||
|
||||
BezierCurve([p, upper_control, inside_control_points..., lower_control, q])
|
||||
end
|
||||
|
||||
# end
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
|
||||
# TODO:
|
||||
"""
|
||||
- load Contours.jl similar to DataFrames
|
||||
- method to build grid from x/y/z vectors
|
||||
- method to wrap contours creation
|
||||
- method to plot contours as custom shapes (TODO: create Stroke and Fill types and add markerstroke/markerfill args)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# # ----------------------------------------------------------
|
||||
# # ----------------------------------------------------------
|
||||
|
||||
|
||||
# immutable Vertex
|
||||
# x::Float64
|
||||
# y::Float64
|
||||
# z::Float64
|
||||
# end
|
||||
|
||||
# immutable Edge
|
||||
# v::Vertex
|
||||
# u::Vertex
|
||||
# end
|
||||
|
||||
# # ----------------------------------------------------------
|
||||
|
||||
# # one rectangle's z-values and the center vertex
|
||||
# # z is ordered: topleft, topright, bottomright, bottomleft
|
||||
# immutable GridRect
|
||||
# z::Vector{Float64}
|
||||
# center::Vertex
|
||||
# data::Vector{Vertex}
|
||||
# end
|
||||
|
||||
|
||||
|
||||
# type Grid
|
||||
# xs::Vector{Float64}
|
||||
# ys::Vector{Float64}
|
||||
# rects::Matrix{GridRect}
|
||||
# end
|
||||
|
||||
# function splitDataEvenly(v::AbstractVector{Float64}, n::Int)
|
||||
# vs = sort(v)
|
||||
|
||||
# end
|
||||
|
||||
# # the goal here is to create the vertical and horizontal partitions
|
||||
# # which define the grid, so that the data is somewhat evenly split
|
||||
# function bucketData(x, y, z)
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# function buildGrid(x, y, z)
|
||||
# # create
|
||||
# end
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
|
||||
# https://github.com/dcjones/Gadfly.jl
|
||||
|
||||
immutable GadflyPackage <: PlottingPackage end
|
||||
|
||||
|
||||
# create a blank Gadfly.Plot object
|
||||
function plot(pkg::GadflyPackage; kw...)
|
||||
@eval import DataFrames
|
||||
|
||||
plt = Gadfly.Plot()
|
||||
plt.mapping = Dict()
|
||||
plt.data_source = DataFrames.DataFrame()
|
||||
plt.layers = plt.layers[1:0]
|
||||
|
||||
# add the title, axis labels, and theme
|
||||
d = Dict(kw)
|
||||
|
||||
plt.guides = Gadfly.GuideElement[Gadfly.Guide.xlabel(d[:xlabel]),
|
||||
Gadfly.Guide.ylabel(d[:ylabel]),
|
||||
Gadfly.Guide.title(d[:title])]
|
||||
|
||||
# add the legend?
|
||||
if d[:legend]
|
||||
unshift!(plt.guides, Gadfly.Guide.manual_color_key("", AbstractString[], Color[]))
|
||||
end
|
||||
|
||||
plt.theme = Gadfly.Theme(background_color = (haskey(d, :background_color) ? d[:background_color] : colorant"white"))
|
||||
|
||||
Plot(plt, pkg, 0)
|
||||
end
|
||||
|
||||
function getGeomFromLineType(linetype::Symbol, nbins::Int)
|
||||
linetype == :line && return Gadfly.Geom.line
|
||||
linetype == :dots && return Gadfly.Geom.point
|
||||
linetype == :bar && return Gadfly.Geom.bar
|
||||
linetype == :step && return Gadfly.Geom.step
|
||||
linetype == :hist && return Gadfly.Geom.histogram(bincount=nbins)
|
||||
linetype == :none && return Gadfly.Geom.point # change this? are we usually pairing no line with scatterplots?
|
||||
linetype == :sticks && return Gadfly.Geom.bar
|
||||
error("linetype $linetype not currently supported with Gadfly")
|
||||
end
|
||||
|
||||
function getGeoms(linetype::Symbol, marker::Symbol, nbins::Int)
|
||||
geoms = []
|
||||
|
||||
# handle heatmaps (hexbins) specially
|
||||
if linetype in (:heatmap,:hexbin)
|
||||
push!(geoms, Gadfly.Geom.hexbin(xbincount=nbins, ybincount=nbins))
|
||||
else
|
||||
|
||||
# for other linetypes, get the correct Geom
|
||||
push!(geoms, getGeomFromLineType(linetype, nbins))
|
||||
|
||||
# for any marker, add Geom.point
|
||||
if marker != :none
|
||||
push!(geoms, Gadfly.Geom.point)
|
||||
end
|
||||
end
|
||||
|
||||
geoms
|
||||
end
|
||||
|
||||
|
||||
# plot one data series
|
||||
function plot!(::GadflyPackage, plt::Plot; kw...)
|
||||
d = Dict(kw)
|
||||
|
||||
gfargs = []
|
||||
|
||||
# add the Geoms
|
||||
append!(gfargs, getGeoms(d[:linetype], d[:marker], d[:nbins]))
|
||||
|
||||
# set color, line width, and point size
|
||||
theme = Gadfly.Theme(default_color = d[:color],
|
||||
line_width = d[:width] * Gadfly.px,
|
||||
default_point_size = d[:markersize] * Gadfly.px)
|
||||
push!(gfargs, theme)
|
||||
|
||||
# add a regression line?
|
||||
if d[:reg]
|
||||
push!(gfargs, Gadfly.Geom.smooth(method=:lm))
|
||||
end
|
||||
|
||||
# for histograms, set x=y
|
||||
x = d[d[:linetype] == :hist ? :y : :x]
|
||||
|
||||
# add to the legend
|
||||
if length(plt.o.guides) > 0 && isa(plt.o.guides[1], Gadfly.Guide.ManualColorKey)
|
||||
push!(plt.o.guides[1].labels, d[:label])
|
||||
push!(plt.o.guides[1].colors, d[:color])
|
||||
end
|
||||
|
||||
if d[:axis] != :left
|
||||
warn("Gadly only supports one y axis")
|
||||
end
|
||||
|
||||
# add the layer to the Gadfly.Plot
|
||||
prepend!(plt.o.layers, Gadfly.layer(unique(gfargs)...; x = x, y = d[:y]))
|
||||
plt
|
||||
end
|
||||
|
||||
function Base.display(::GadflyPackage, plt::Plot)
|
||||
display(plt.o)
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
function savepng(::GadflyPackage, plt::PlottingObject, fn::String;
|
||||
w = 6 * Gadfly.inch,
|
||||
h = 4 * Gadfly.inch)
|
||||
Gadfly.draw(Gadfly.PNG(fn, w, h), plt.o)
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# # create the underlying object (each backend will do this differently)
|
||||
# o = buildSubplotObject(plts, pkg, layout)
|
||||
|
||||
function buildSubplotObject!(::GadflyPackage, subplt::Subplot)
|
||||
i = 0
|
||||
rows = []
|
||||
for rowcnt in subplt.layout.rowcounts
|
||||
push!(rows, Gadfly.hstack([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...))
|
||||
i += rowcnt
|
||||
end
|
||||
subplt.o = Gadfly.vstack(rows...)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::GadflyPackage, subplt::Subplot)
|
||||
display(subplt.o)
|
||||
end
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# GridLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Simple grid, indices are row-major."
|
||||
immutable GridLayout <: SubplotLayout
|
||||
nr::Int
|
||||
nc::Int
|
||||
end
|
||||
|
||||
Base.length(layout::GridLayout) = layout.nr * layout.nc
|
||||
Base.start(layout::GridLayout) = 1
|
||||
Base.done(layout::GridLayout, state) = state > length(layout)
|
||||
function Base.next(layout::GridLayout, state)
|
||||
r = div(state-1, layout.nc) + 1
|
||||
c = mod1(state, layout.nc)
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::GridLayout) = layout.nr
|
||||
ncols(layout::GridLayout) = layout.nc
|
||||
ncols(layout::GridLayout, row::Int) = layout.nc
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::GridLayout, r::Int, c::Int) = (r-1) * layout.nc + c
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# RowsLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Number of plots per row"
|
||||
immutable RowsLayout <: SubplotLayout
|
||||
numplts::Int
|
||||
rowcounts::AbstractVector{Int}
|
||||
end
|
||||
|
||||
Base.length(layout::RowsLayout) = layout.numplts
|
||||
Base.start(layout::RowsLayout) = 1
|
||||
Base.done(layout::RowsLayout, state) = state > length(layout)
|
||||
function Base.next(layout::RowsLayout, state)
|
||||
r = 1
|
||||
c = 0
|
||||
for i = 1:state
|
||||
c += 1
|
||||
if c > layout.rowcounts[r]
|
||||
r += 1
|
||||
c = 1
|
||||
end
|
||||
end
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::RowsLayout) = length(layout.rowcounts)
|
||||
ncols(layout::RowsLayout, row::Int) = row < 1 ? 0 : (row > nrows(layout) ? 0 : layout.rowcounts[row])
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::RowsLayout, r::Int, c::Int) = sum(layout.rowcounts[1:r-1]) + c
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# FlexLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Flexible, nested layout with optional size percentages."
|
||||
immutable FlexLayout <: SubplotLayout
|
||||
n::Int
|
||||
grid::Matrix # Nested layouts. Each position
|
||||
# can be a plot index or another FlexLayout
|
||||
widths::Vector{Float64}
|
||||
heights::Vector{Float64}
|
||||
end
|
||||
|
||||
typealias IntOrFlex Union{Int,FlexLayout}
|
||||
|
||||
Base.length(layout::FlexLayout) = layout.n
|
||||
Base.start(layout::FlexLayout) = 1
|
||||
Base.done(layout::FlexLayout, state) = state > length(layout)
|
||||
function Base.next(layout::FlexLayout, state)
|
||||
# TODO: change this method to return more info
|
||||
# TODO: might consider multiple iterator types.. some backends might have an easier time row-by-row for example
|
||||
error()
|
||||
r = 1
|
||||
c = 0
|
||||
for i = 1:state
|
||||
c += 1
|
||||
if c > layout.rowcounts[r]
|
||||
r += 1
|
||||
c = 1
|
||||
end
|
||||
end
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::FlexLayout) = size(layout.grid, 1)
|
||||
ncols(layout::FlexLayout, row::Int) = size(layout.grid, 2)
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::FlexLayout, r::Int, c::Int) = layout.grid[r,c]
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# we're taking in a nested structure of some kind... parse it out and build a FlexLayout
|
||||
function subplotlayout(mat::AbstractVecOrMat; widths = nothing, heights = nothing)
|
||||
n = 0
|
||||
nr, nc = size(mat)
|
||||
grid = Array(IntOrFlex, nr, nc)
|
||||
for i=1:nr, j=1:nc
|
||||
v = mat[i,j]
|
||||
|
||||
if isa(v, Integer)
|
||||
grid[i,j] = Int(v)
|
||||
n += 1
|
||||
|
||||
elseif isa(v, Tuple)
|
||||
warn("need to handle tuples somehow... (idx, sizepct)")
|
||||
grid[i,j] = nothing
|
||||
|
||||
elseif v == nothing
|
||||
grid[i,j] = nothing
|
||||
|
||||
elseif isa(v, AbstractVecOrMat)
|
||||
grid[i,j] = layout(v)
|
||||
n += grid[i,j].n
|
||||
|
||||
else
|
||||
error("How do we process? $v")
|
||||
end
|
||||
end
|
||||
|
||||
if widths == nothing
|
||||
widths = ones(nc) ./ nc
|
||||
end
|
||||
if heights == nothing
|
||||
heights = ones(nr) ./ nr
|
||||
end
|
||||
|
||||
FlexLayout(n, grid, widths, heights)
|
||||
end
|
||||
|
||||
|
||||
function subplotlayout(sz::Tuple{Int,Int})
|
||||
GridLayout(sz...)
|
||||
end
|
||||
|
||||
function subplotlayout(rowcounts::AVec{Int})
|
||||
RowsLayout(sum(rowcounts), rowcounts)
|
||||
end
|
||||
|
||||
function subplotlayout(numplts::Int, nr::Int, nc::Int)
|
||||
|
||||
# figure out how many rows/columns we need
|
||||
if nr == -1
|
||||
if nc == -1
|
||||
nr = round(Int, sqrt(numplts))
|
||||
nc = ceil(Int, numplts / nr)
|
||||
else
|
||||
nr = ceil(Int, numplts / nc)
|
||||
end
|
||||
else
|
||||
nc = ceil(Int, numplts / nr)
|
||||
end
|
||||
|
||||
# if it's a perfect rectangle, just create a grid
|
||||
if numplts == nr * nc
|
||||
return GridLayout(nr, nc)
|
||||
end
|
||||
|
||||
# create the rowcounts vector
|
||||
i = 0
|
||||
rowcounts = Int[]
|
||||
for r in 1:nr
|
||||
cnt = min(nc, numplts - i)
|
||||
push!(rowcounts, cnt)
|
||||
i += cnt
|
||||
end
|
||||
|
||||
RowsLayout(numplts, rowcounts)
|
||||
end
|
||||
@@ -0,0 +1,151 @@
|
||||
|
||||
|
||||
defaultOutputFormat(plt::AbstractPlot) = "png"
|
||||
|
||||
function png(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "png")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("image/png"), plt)
|
||||
close(io)
|
||||
end
|
||||
png(fn::@compat(AbstractString)) = png(current(), fn)
|
||||
|
||||
function svg(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "svg")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("image/svg+xml"), plt)
|
||||
close(io)
|
||||
end
|
||||
svg(fn::@compat(AbstractString)) = svg(current(), fn)
|
||||
|
||||
|
||||
function pdf(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "pdf")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/pdf"), plt)
|
||||
close(io)
|
||||
end
|
||||
pdf(fn::@compat(AbstractString)) = pdf(current(), fn)
|
||||
|
||||
|
||||
function ps(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "ps")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/postscript"), plt)
|
||||
close(io)
|
||||
end
|
||||
ps(fn::@compat(AbstractString)) = ps(current(), fn)
|
||||
|
||||
|
||||
function tex(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
fn = addExtension(fn, "tex")
|
||||
io = open(fn, "w")
|
||||
writemime(io, MIME("application/x-tex"), plt)
|
||||
close(io)
|
||||
end
|
||||
tex(fn::@compat(AbstractString)) = tex(current(), fn)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
@compat const _savemap = Dict(
|
||||
"png" => png,
|
||||
"svg" => svg,
|
||||
"pdf" => pdf,
|
||||
"ps" => ps,
|
||||
"tex" => tex,
|
||||
)
|
||||
|
||||
function getExtension(fn::@compat(AbstractString))
|
||||
pieces = split(fn, ".")
|
||||
length(pieces) > 1 || error("Can't extract file extension: ", fn)
|
||||
ext = pieces[end]
|
||||
haskey(_savemap, ext) || error("Invalid file extension: ", fn)
|
||||
ext
|
||||
end
|
||||
|
||||
function addExtension(fn::@compat(AbstractString), ext::@compat(AbstractString))
|
||||
try
|
||||
oldext = getExtension(fn)
|
||||
if oldext == ext
|
||||
return fn
|
||||
else
|
||||
return "$fn.$ext"
|
||||
end
|
||||
catch
|
||||
return "$fn.$ext"
|
||||
end
|
||||
end
|
||||
|
||||
function savefig(plt::AbstractPlot, fn::@compat(AbstractString))
|
||||
|
||||
# get the extension
|
||||
local ext
|
||||
try
|
||||
ext = getExtension(fn)
|
||||
catch
|
||||
# if we couldn't extract the extension, add the default
|
||||
ext = defaultOutputFormat(plt)
|
||||
fn = addExtension(fn, ext)
|
||||
end
|
||||
|
||||
# save it
|
||||
func = get(_savemap, ext) do
|
||||
error("Unsupported extension $ext with filename ", fn)
|
||||
end
|
||||
func(plt, fn)
|
||||
end
|
||||
savefig(fn::@compat(AbstractString)) = savefig(current(), fn)
|
||||
|
||||
|
||||
# savepng(args...; kw...) = savepng(current(), args...; kw...)
|
||||
# savepng(plt::AbstractPlot, fn::@compat(AbstractString); kw...) = (io = open(fn, "w"); writemime(io, MIME("image/png"), plt); close(io))
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
gui(plt::AbstractPlot = current()) = display(PlotsDisplay(), plt)
|
||||
|
||||
|
||||
# override the REPL display to open a gui window
|
||||
Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::AbstractPlot) = gui(plt)
|
||||
|
||||
# a backup for html... passes to svg
|
||||
function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot)
|
||||
writemime(io, MIME("image/svg+xml"), plt)
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Atom PlotPane
|
||||
# ---------------------------------------------------------
|
||||
|
||||
function setup_atom()
|
||||
# @require Atom begin
|
||||
if isatom()
|
||||
# @eval import Atom, Media
|
||||
@eval import Atom
|
||||
|
||||
# connects the render function
|
||||
for T in (GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend)
|
||||
Atom.Media.media(AbstractPlot{T}, Atom.Media.Plot)
|
||||
end
|
||||
# Atom.Media.media{T <: Union{GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend}}(Plot{T}, Atom.Media.Plot)
|
||||
|
||||
# Atom.displaysize(::AbstractPlot) = (535, 379)
|
||||
# Atom.displaytitle(plt::AbstractPlot) = "Plots.jl (backend: $(backend(plt)))"
|
||||
|
||||
# this is like "display"... sends an html div with the plot to the PlotPane
|
||||
function Atom.Media.render(pane::Atom.PlotPane, plt::AbstractPlot)
|
||||
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
|
||||
end
|
||||
@@ -1,414 +1,278 @@
|
||||
|
||||
type CurrentPlot
|
||||
nullableplot::Nullable{PlottingObject}
|
||||
nullableplot::Nullable{AbstractPlot}
|
||||
end
|
||||
const CURRENT_PLOT = CurrentPlot(Nullable{PlottingObject}())
|
||||
const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}())
|
||||
|
||||
isplotnull() = isnull(CURRENT_PLOT.nullableplot)
|
||||
|
||||
function currentPlot()
|
||||
if isplotnull()
|
||||
error("No current plot/subplot")
|
||||
end
|
||||
get(CURRENT_PLOT.nullableplot)
|
||||
function current()
|
||||
if isplotnull()
|
||||
error("No current plot/subplot")
|
||||
end
|
||||
get(CURRENT_PLOT.nullableplot)
|
||||
end
|
||||
currentPlot!(plot::PlottingObject) = (CURRENT_PLOT.nullableplot = Nullable(plot))
|
||||
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = Nullable(plot))
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
Base.string(plt::Plot) = "Plot{$(plt.plotter) n=$(plt.n)}"
|
||||
Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}"
|
||||
Base.print(io::IO, plt::Plot) = print(io, string(plt))
|
||||
Base.show(io::IO, plt::Plot) = print(io, string(plt))
|
||||
|
||||
getplot(plt::Plot) = plt
|
||||
getplotargs(plt::Plot, idx::Int = 1) = plt.plotargs
|
||||
convertSeriesIndex(plt::Plot, n::Int) = n
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
doc"""
|
||||
The main plot command. Call `plotter!(:module)` to set the current plotting backend.
|
||||
Commands are converted into the relevant plotting commands for that package:
|
||||
|
||||
```
|
||||
plotter!(:gadfly)
|
||||
plot(1:10) # this effectively calls `y = 1:10; Gadfly.plot(x=1:length(y), y=y)`
|
||||
plotter!(:qwt)
|
||||
plot(1:10) # this effectively calls `Qwt.plot(1:10)`
|
||||
```
|
||||
|
||||
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 currentPlot
|
||||
plot!(args...; kw...) # adds to the `currentPlot`
|
||||
plot!(plotobj, args...; kw...) # adds to the plot `plotobj`
|
||||
```
|
||||
|
||||
There are lots of ways to pass in data... 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.
|
||||
|
||||
Some keyword arguments you can set:
|
||||
|
||||
```
|
||||
axis # :left or :right
|
||||
color # can be a string ("red") or a symbol (:red) or a ColorsTypes.jl Colorant (RGB(1,0,0)) or :auto (which lets the package pick)
|
||||
label # string or symbol, applies to that line, may go in a legend
|
||||
width # width of a line
|
||||
linetype # :line, :step, :stepinverted, :sticks, :dots, :none, :heatmap
|
||||
linestyle # :solid, :dash, :dot, :dashdot, :dashdotdot
|
||||
marker # :none, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :star2, :hexagon
|
||||
markercolor # same choices as `color`
|
||||
markersize # size of the marker
|
||||
nbins # number of bins for heatmap/hexbin and histograms
|
||||
heatmap_c # color cutoffs for Qwt heatmaps
|
||||
fillto # fillto value for area plots
|
||||
title # string or symbol, title of the plot
|
||||
xlabel # string or symbol, label on the bottom (x) axis
|
||||
ylabel # string or symbol, label on the left (y) axis
|
||||
yrightlabel # string or symbol, label on the right (y) axis
|
||||
reg # true or false, add a regression line for each line
|
||||
size # (Int,Int), resize the enclosing window
|
||||
pos # (Int,Int), move the enclosing window to this position
|
||||
windowtitle # string or symbol, set the title of the enclosing windowtitle
|
||||
screen # Integer, move enclosing window to this screen number (for multiscreen desktops)
|
||||
show # true or false, show the plot (in case you don't want the window to pop up right away)
|
||||
```
|
||||
|
||||
When plotting multiple lines, you can give every line the same trait by using the singular, or add an "s" to pluralize.
|
||||
(yes I know it's not gramatically correct, but it's easy to use and implement)
|
||||
|
||||
```
|
||||
plot(rand(100,2); colors = [:red, RGB(.5,.5,0)], axiss = [:left, :right], width = 5) # note the width=5 is applied to both lines
|
||||
```
|
||||
|
||||
"""
|
||||
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
|
||||
plot!(args...; kw...) # adds to the `current`
|
||||
plot!(plotobj, args...; kw...) # adds to the plot `plotobj`
|
||||
```
|
||||
|
||||
# -------------------------
|
||||
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.
|
||||
"""
|
||||
|
||||
# this creates a new plot with args/kw and sets it to be the current plot
|
||||
function plot(args...; kw...)
|
||||
plt = plot(plotter(); getPlotKeywordArgs(kw, 1, 0)...) # create a new, blank plot
|
||||
plot!(plt, args...; kw...) # add to it
|
||||
pkg = backend()
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
dumpdict(d, "After plot preprocessing")
|
||||
|
||||
plotargs = merge(d, getPlotArgs(pkg, d, 1))
|
||||
dumpdict(plotargs, "Plot args")
|
||||
plt = _create_plot(pkg, plotargs) # create a new, blank plot
|
||||
|
||||
delete!(d, :background_color)
|
||||
plot!(plt, args...; d...) # add to it
|
||||
end
|
||||
|
||||
# this adds to the current plot
|
||||
|
||||
|
||||
# this adds to the current plot, or creates a new plot if none are current
|
||||
function plot!(args...; kw...)
|
||||
plot!(currentPlot(), args...; kw...)
|
||||
end
|
||||
|
||||
# not allowed:
|
||||
function plot!(subplt::Subplot, args...; kw...)
|
||||
error("Can't call plot! on a Subplot!")
|
||||
local plt
|
||||
try
|
||||
plt = current()
|
||||
catch
|
||||
return plot(args...; kw...)
|
||||
end
|
||||
plot!(current(), args...; kw...)
|
||||
end
|
||||
|
||||
# this adds to a specific plot... most plot commands will flow through here
|
||||
function plot!(plt::Plot, args...; kw...)
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
|
||||
# # increment n if we're going directly to the package's plot method
|
||||
# if length(args) == 0
|
||||
# plt.n += 1
|
||||
# end
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
args = _apply_recipe(d, args...; kw...)
|
||||
|
||||
# plot!(plt.plotter, plt, args...; kw...)
|
||||
dumpdict(d, "After plot! preprocessing")
|
||||
warnOnUnsupportedArgs(plt.backend, d)
|
||||
|
||||
# just in case the backend needs to set up the plot (make it current or something)
|
||||
_before_add_series(plt)
|
||||
|
||||
# # grouping
|
||||
groupby = if haskey(d, :group)
|
||||
extractGroupArgs(d[:group], args...)
|
||||
else
|
||||
nothing
|
||||
end
|
||||
|
||||
# merge plot args
|
||||
if !haskey(d, :subplot)
|
||||
for k in keys(_plotDefaults)
|
||||
if haskey(d, k)
|
||||
plt.plotargs[k] = d[k]
|
||||
end
|
||||
end
|
||||
# merge!(plt.plotargs, d)
|
||||
handlePlotColors(plt.backend, plt.plotargs)
|
||||
end
|
||||
|
||||
_add_series(plt, d, groupby, args...)
|
||||
_add_annotations(plt, d)
|
||||
|
||||
warnOnUnsupportedScales(plt.backend, d)
|
||||
|
||||
|
||||
kwList = createKWargsList(plt, args...; kw...)
|
||||
for (i,d) in enumerate(kwList)
|
||||
plt.n += 1
|
||||
plot!(plt.plotter, plt; d...)
|
||||
end
|
||||
# add title, axis labels, ticks, etc
|
||||
if !haskey(d, :subplot)
|
||||
# merge!(plt.plotargs, d)
|
||||
# handlePlotColors(plt.backend, plt.plotargs)
|
||||
dumpdict(plt.plotargs, "Updating plot items")
|
||||
_update_plot(plt, plt.plotargs)
|
||||
end
|
||||
|
||||
currentPlot!(plt)
|
||||
_update_plot_pos_size(plt, d)
|
||||
|
||||
# do we want to show it?
|
||||
d = Dict(kw)
|
||||
if haskey(d, :show) && d[:show]
|
||||
display(plt)
|
||||
end
|
||||
current(plt)
|
||||
|
||||
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]
|
||||
gui()
|
||||
end
|
||||
|
||||
plt
|
||||
end
|
||||
|
||||
# show/update the plot
|
||||
function Base.display(plt::PlottingObject)
|
||||
display(plt.plotter, plt)
|
||||
# handle the grouping
|
||||
function _add_series(plt::Plot, d::KW, groupby::GroupBy, args...)
|
||||
starting_n = plt.n
|
||||
for (i, glab) in enumerate(groupby.groupLabels)
|
||||
tmpd = copy(d)
|
||||
tmpd[:numUncounted] = plt.n - starting_n
|
||||
_add_series(plt, tmpd, nothing, args...;
|
||||
idxfilter = groupby.groupIds[i],
|
||||
grouplabel = string(glab))
|
||||
end
|
||||
end
|
||||
|
||||
# -------------------------
|
||||
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
|
||||
filter_data(v, idxfilter) = v
|
||||
|
||||
|
||||
|
||||
doc"Build a vector of dictionaries which hold the keyword arguments for a call to plot!"
|
||||
|
||||
# no args... 1 series
|
||||
function createKWargsList(plt::PlottingObject; kw...)
|
||||
d = Dict(kw)
|
||||
@assert haskey(d, :y)
|
||||
if !haskey(d, :x)
|
||||
d[:x] = 1:length(d[:y])
|
||||
end
|
||||
[getPlotKeywordArgs(d, 1, plt.n + 1)]
|
||||
function filter_data!(d::KW, idxfilter)
|
||||
for s in (:x, :y, :z)
|
||||
d[s] = filter_data(get(d, s, nothing), idxfilter)
|
||||
end
|
||||
end
|
||||
|
||||
# create one series where y is vectors of numbers
|
||||
function createKWargsList{T<:Real}(plt::PlottingObject, y::AVec{T}; kw...)
|
||||
d = getPlotKeywordArgs(kw, 1, plt.n + 1)
|
||||
d[:x] = 1:length(y)
|
||||
d[:y] = y
|
||||
[d]
|
||||
# no grouping
|
||||
function _add_series(plt::Plot, d::KW, ::Void, args...;
|
||||
idxfilter = nothing,
|
||||
grouplabel = "")
|
||||
|
||||
# get the list of dictionaries, one per series
|
||||
dumpdict(d, "before process_inputs")
|
||||
process_inputs(plt, d, args...)
|
||||
dumpdict(d, "after process_inputs")
|
||||
|
||||
if idxfilter != nothing
|
||||
# add the group name as the label if there isn't one passed in
|
||||
get!(d, :label, grouplabel)
|
||||
# filter the data
|
||||
filter_data!(d, idxfilter)
|
||||
end
|
||||
|
||||
seriesArgList, xmeta, ymeta = build_series_args(plt, d) #, idxfilter)
|
||||
# seriesArgList, xmeta, ymeta = build_series_args(plt, groupargs..., args...; d...)
|
||||
|
||||
# if we were able to extract guide information from the series inputs, then update the plot
|
||||
# @show xmeta, ymeta
|
||||
updateDictWithMeta(d, plt.plotargs, xmeta, true)
|
||||
updateDictWithMeta(d, plt.plotargs, ymeta, false)
|
||||
|
||||
# now we can plot the series
|
||||
for (i,di) in enumerate(seriesArgList)
|
||||
plt.n += 1
|
||||
|
||||
if !stringsSupported() && di[:linetype] != :pie
|
||||
setTicksFromStringVector(d, di, :x, :xticks)
|
||||
setTicksFromStringVector(d, di, :y, :yticks)
|
||||
setTicksFromStringVector(d, di, :z, :zticks)
|
||||
end
|
||||
|
||||
# remove plot args
|
||||
for k in keys(_plotDefaults)
|
||||
delete!(di, k)
|
||||
end
|
||||
|
||||
# merge in plotarg_overrides
|
||||
plotarg_overrides = pop!(di, :plotarg_overrides, nothing)
|
||||
if plotarg_overrides != nothing
|
||||
merge!(plt.plotargs, plotarg_overrides)
|
||||
end
|
||||
# dumpdict(plt.plotargs, "pargs", true)
|
||||
|
||||
dumpdict(di, "Series $i")
|
||||
|
||||
_add_series(plt.backend, plt, di)
|
||||
end
|
||||
end
|
||||
|
||||
# create one series where x/y are vectors of numbers
|
||||
function createKWargsList{T<:Real,S<:Real}(plt::PlottingObject, x::AVec{T}, y::AVec{S}; kw...)
|
||||
@assert length(x) == length(y)
|
||||
d = getPlotKeywordArgs(kw, 1, plt.n + 1)
|
||||
d[:x] = x
|
||||
d[:y] = y
|
||||
[d]
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# if x or y are a vector of strings, we should create a list of unique strings,
|
||||
# and map x/y to be the index of the string... then set the x/y tick labels
|
||||
function setTicksFromStringVector(d::KW, di::KW, sym::Symbol, ticksym::Symbol)
|
||||
# if the x or y values are strings, set ticks to the unique values, and x/y to the indices of the ticks
|
||||
|
||||
v = di[sym]
|
||||
isa(v, AbstractArray) || return
|
||||
|
||||
T = eltype(v)
|
||||
if T <: @compat(AbstractString) || (!isempty(T.types) && all(x -> x <: @compat(AbstractString), T.types))
|
||||
ticks = unique(di[sym])
|
||||
di[sym] = Int[findnext(ticks, v, 1) for v in di[sym]]
|
||||
|
||||
if !haskey(d, ticksym) || d[ticksym] == :auto
|
||||
d[ticksym] = (collect(1:length(ticks)), UTF8String[t for t in ticks])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# create m series, 1 for each column of y
|
||||
function createKWargsList(plt::PlottingObject, y::AMat; kw...)
|
||||
n,m = size(y)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = 1:n
|
||||
d[:y] = y[:,i]
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
_before_add_series(plt::Plot) = nothing
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# should we update the x/y label given the meta info during input slicing?
|
||||
function updateDictWithMeta(d::KW, plotargs::KW, meta::Symbol, isx::Bool)
|
||||
lsym = isx ? :xlabel : :ylabel
|
||||
if plotargs[lsym] == default(lsym)
|
||||
d[lsym] = string(meta)
|
||||
end
|
||||
end
|
||||
updateDictWithMeta(d::KW, plotargs::KW, meta, isx::Bool) = nothing
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
annotations(::@compat(Void)) = []
|
||||
annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v
|
||||
annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t]
|
||||
annotations(v::AVec{PlotText}) = v
|
||||
annotations(v::AVec) = map(PlotText, v)
|
||||
annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ",
|
||||
"(x, y, annotation)\n got: $(typeof(anns))")
|
||||
|
||||
function _add_annotations(plt::Plot, d::KW)
|
||||
anns = annotations(get(d, :annotation, nothing))
|
||||
if !isempty(anns)
|
||||
|
||||
# if we just have a list of PlotText objects, then create (x,y,text) tuples
|
||||
if typeof(anns) <: AVec{PlotText}
|
||||
x, y = plt[plt.n]
|
||||
anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)]
|
||||
end
|
||||
|
||||
_add_annotations(plt, anns)
|
||||
end
|
||||
end
|
||||
|
||||
# create m series, 1 for each column of y
|
||||
function createKWargsList(plt::PlottingObject, x::AVec, y::AMat; kw...)
|
||||
n,m = size(y)
|
||||
@assert length(x) == n
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = x
|
||||
d[:y] = y[:,i]
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
function Base.copy(plt::Plot)
|
||||
backend(plt.backend)
|
||||
plt2 = plot(; plt.plotargs...)
|
||||
for sargs in plt.seriesargs
|
||||
sargs = filter((k,v) -> haskey(_seriesDefaults,k), sargs)
|
||||
plot!(plt2; sargs...)
|
||||
end
|
||||
plt2
|
||||
end
|
||||
|
||||
# create m series, 1 for each column of y
|
||||
function createKWargsList(plt::PlottingObject, x::AMat, y::AMat; kw...)
|
||||
@assert size(x) == size(y)
|
||||
n,m = size(y)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = x[:,i]
|
||||
d[:y] = y[:,i]
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# create 1 series, y = f(x)
|
||||
function createKWargsList(plt::PlottingObject, x::AVec, f::Function; kw...)
|
||||
d = getPlotKeywordArgs(kw, 1, plt.n + 1)
|
||||
d[:x] = x
|
||||
d[:y] = map(f, x)
|
||||
[d]
|
||||
end
|
||||
|
||||
# create m series, y = f(x), 1 for each column of x
|
||||
function createKWargsList(plt::PlottingObject, x::AMat, f::Function; kw...)
|
||||
n,m = size(x)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = x[:,i]
|
||||
d[:y] = map(f, d[:x])
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# create m series, 1 for each item in y (assumes vectors of something other than numbers... functions? vectors?)
|
||||
function createKWargsList(plt::PlottingObject, y::AVec; kw...)
|
||||
m = length(y)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = 1:length(y[i])
|
||||
d[:y] = y[i]
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
function getyvec(x::AVec, y::AVec)
|
||||
@assert length(x) == length(y)
|
||||
y
|
||||
end
|
||||
getyvec(x::AVec, f::Function) = map(f, x)
|
||||
getyvec(x, y) = error("Couldn't create yvec from types: x ($(typeof(x))), y ($(typeof(y)))")
|
||||
|
||||
# same, but given an x to use for all series
|
||||
function createKWargsList{T<:Real}(plt::PlottingObject, x::AVec{T}, y::AVec; kw...)
|
||||
m = length(y)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = x
|
||||
d[:y] = getyvec(x, y[i])
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# same, but m series of (x[i],y[i])
|
||||
function createKWargsList(plt::PlottingObject, x::AVec, y::AVec; kw...)
|
||||
@assert length(x) == length(y)
|
||||
m = length(y)
|
||||
ret = []
|
||||
for i in 1:m
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = x[i]
|
||||
d[:y] = getyvec(x[i], y[i])
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# n empty series
|
||||
function createKWargsList(plt::PlottingObject, n::Integer; kw...)
|
||||
ret = []
|
||||
for i in 1:n
|
||||
d = getPlotKeywordArgs(kw, i, plt.n + i)
|
||||
d[:x] = zeros(0)
|
||||
d[:y] = zeros(0)
|
||||
push!(ret, d)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# TODO: handle DataFrames (might have NAs!)
|
||||
|
||||
# -------------------------
|
||||
|
||||
# # most calls should flow through here now... we create a Dict with the keyword args for each series, and plot them
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, args...; kw...)
|
||||
# kwList = createKWargsList(plt, args...; kw...)
|
||||
# for (i,d) in enumerate(kwList)
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; d...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# -------------------------
|
||||
|
||||
# # These methods are various ways to add to an existing plot
|
||||
|
||||
# function plot!{T<:Real}(pkg::PlottingPackage, plt::Plot, y::AVec{T}; kw...)
|
||||
# plt.n += 1
|
||||
# # plot!(pkg, plt; x = 1:length(y), y = y, getPlotKeywordArgs(kw, 1, plt)...)
|
||||
# end
|
||||
|
||||
# function plot!{T<:Real,S<:Real}(pkg::PlottingPackage, plt::Plot, x::AVec{T}, y::AVec{S}; kw...) # one line (will assert length(x) == length(y))
|
||||
# @assert length(x) == length(y)
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x=x, y=y, getPlotKeywordArgs(kw, 1, plt)...)
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, y::AMat; kw...) # multiple lines (one per column of x), all sharing x = 1:size(y,1)
|
||||
# n,m = size(y)
|
||||
# for i in 1:m
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = 1:n, y = y[:,i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, y::AMat; kw...) # multiple lines (one per column of x), all sharing x (will assert length(x) == size(y,1))
|
||||
# n,m = size(y)
|
||||
# for i in 1:m
|
||||
# @assert length(x) == n
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x, y = y[:,i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, x::AMat, y::AMat; kw...) # multiple lines (one per column of x/y... will assert size(x) == size(y))
|
||||
# @assert size(x) == size(y)
|
||||
# for i in 1:size(x,2)
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x[:,i], y = y[:,i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, f::Function; kw...) # one line, y = f(x)
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x, y = map(f,x), getPlotKeywordArgs(kw, 1, plt)...)
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, x::AMat, f::Function; kw...) # multiple lines, yᵢⱼ = f(xᵢⱼ)
|
||||
# for i in 1:size(x,2)
|
||||
# xi = x[:,i]
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = xi, y = map(f, xi), getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# # function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, fs::AVec{Function}; kw...) # multiple lines, yᵢⱼ = fⱼ(xᵢ)
|
||||
# # for i in 1:length(fs)
|
||||
# # plt.n += 1
|
||||
# # plot!(pkg, plt; x = x, y = map(fs[i], x), getPlotKeywordArgs(kw, i, plt)...)
|
||||
# # end
|
||||
# # plt
|
||||
# # end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, y::AVec; kw...) # multiple lines, each with x = 1:length(y[i])
|
||||
# for i in 1:length(y)
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = 1:length(y[i]), y = y[i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!{T<:Real}(pkg::PlottingPackage, plt::Plot, x::AVec{T}, y::AVec; kw...) # multiple lines, will assert length(x) == length(y[i])
|
||||
# for i in 1:length(y)
|
||||
# if typeof(y[i]) <: AbstractVector
|
||||
# @assert length(x) == length(y[i])
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x, y = y[i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# elseif typeof(y[i]) == Function
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x, y = map(y[i], x), getPlotKeywordArgs(kw, 1, plt)...)
|
||||
# end
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, y::AVec; kw...) # multiple lines, will assert length(x[i]) == length(y[i])
|
||||
# @assert length(x) == length(y)
|
||||
# for i in 1:length(x)
|
||||
# @assert length(x[i]) == length(y[i])
|
||||
# plt.n += 1
|
||||
# plot!(pkg, plt; x = x[i], y = y[i], getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# plt
|
||||
# end
|
||||
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot, n::Integer; kw...) # n lines, all empty (for updating plots)
|
||||
# for i in 1:n
|
||||
# plt.n += 1
|
||||
# plot(pkg, plt, x = zeros(0), y = zeros(0), getPlotKeywordArgs(kw, i, plt)...)
|
||||
# end
|
||||
# end
|
||||
|
||||
# -------------------------
|
||||
|
||||
# # this is the core method... add to a plot object using kwargs, with args already converted into kwargs
|
||||
# function plot!(pkg::PlottingPackage, plt::Plot; kw...)
|
||||
# plot!(pl, plt; kw...)
|
||||
# end
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
|
||||
plot(pkg::PlottingPackage; kw...) = error("plot($pkg; kw...) is not implemented")
|
||||
plot!(pkg::PlottingPackage, plt::Plot; kw...) = error("plot!($pkg, plt; kw...) is not implemented")
|
||||
Base.display(pkg::PlottingPackage, plt::Plot) = error("display($pkg, plt) is not implemented")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
const AVAILABLE_PACKAGES = [:qwt, :gadfly]
|
||||
const INITIALIZED_PACKAGES = Set{Symbol}()
|
||||
|
||||
type CurrentPackage
|
||||
sym::Symbol
|
||||
pkg::PlottingPackage
|
||||
end
|
||||
# const CURRENT_PACKAGE = CurrentPackage(:qwt, QwtPackage())
|
||||
const CURRENT_PACKAGE = CurrentPackage(:gadfly, GadflyPackage())
|
||||
|
||||
|
||||
doc"""
|
||||
Returns the current plotting package name. Initializes package on first call.
|
||||
"""
|
||||
function plotter()
|
||||
|
||||
currentPackageSymbol = CURRENT_PACKAGE.sym
|
||||
if !(currentPackageSymbol in INITIALIZED_PACKAGES)
|
||||
|
||||
# initialize
|
||||
print("[Plots.jl] Initializing package: $CURRENT_PACKAGE... ")
|
||||
if currentPackageSymbol == :qwt
|
||||
@eval import Qwt
|
||||
elseif currentPackageSymbol == :gadfly
|
||||
@eval import Gadfly
|
||||
else
|
||||
error("Unknown plotter $currentPackageSymbol. Choose from: $AVAILABLE_PACKAGES")
|
||||
end
|
||||
push!(INITIALIZED_PACKAGES, currentPackageSymbol)
|
||||
println("done.")
|
||||
|
||||
end
|
||||
CURRENT_PACKAGE.pkg
|
||||
end
|
||||
|
||||
doc"""
|
||||
Set the plot backend. Choose from: :qwt, :gadfly
|
||||
"""
|
||||
function plotter!(modname)
|
||||
|
||||
# set the PlottingPackage
|
||||
if modname == :qwt
|
||||
CURRENT_PACKAGE.pkg = QwtPackage()
|
||||
elseif modname == :gadfly
|
||||
CURRENT_PACKAGE.pkg = GadflyPackage()
|
||||
else
|
||||
error("Unknown plotter $modname. Choose from: $AVAILABLE_PACKAGES")
|
||||
end
|
||||
|
||||
# update the symbol
|
||||
CURRENT_PACKAGE.sym = modname
|
||||
|
||||
# return the package
|
||||
CURRENT_PACKAGE.pkg
|
||||
end
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
# https://github.com/tbreloff/Qwt.jl
|
||||
|
||||
immutable QwtPackage <: PlottingPackage end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
function adjustQwtKeywords(iscreating::Bool; kw...)
|
||||
d = Dict(kw)
|
||||
d[:heatmap_n] = d[:nbins]
|
||||
|
||||
if d[:linetype] == :hexbin
|
||||
d[:linetype] = :heatmap
|
||||
elseif d[:linetype] == :dots
|
||||
d[:linetype] = :none
|
||||
d[:marker] = :hexagon
|
||||
elseif !iscreating && d[:linetype] == :bar
|
||||
return barHack(; kw...)
|
||||
elseif !iscreating && d[:linetype] == :hist
|
||||
return barHack(; histogramHack(; kw...)...)
|
||||
end
|
||||
d
|
||||
end
|
||||
|
||||
function plot(pkg::QwtPackage; kw...)
|
||||
kw = adjustQwtKeywords(true; kw...)
|
||||
plt = Plot(Qwt.plot(zeros(0,0); kw..., show=false), pkg, 0)
|
||||
plt
|
||||
end
|
||||
|
||||
function plot!(::QwtPackage, plt::Plot; kw...)
|
||||
kw = adjustQwtKeywords(false; kw...)
|
||||
Qwt.oplot(plt.o; kw...)
|
||||
end
|
||||
|
||||
function Base.display(::QwtPackage, plt::Plot)
|
||||
Qwt.refresh(plt.o)
|
||||
Qwt.showwidget(plt.o)
|
||||
end
|
||||
|
||||
# -------------------------------
|
||||
|
||||
savepng(::QwtPackage, plt::PlottingObject, fn::String, args...) = Qwt.savepng(plt.o, fn)
|
||||
|
||||
# -------------------------------
|
||||
|
||||
# # create the underlying object (each backend will do this differently)
|
||||
# o = buildSubplotObject(plts, pkg, layout)
|
||||
|
||||
function buildSubplotObject!(::QwtPackage, subplt::Subplot)
|
||||
i = 0
|
||||
rows = []
|
||||
for rowcnt in subplt.layout.rowcounts
|
||||
push!(rows, Qwt.hsplitter([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...))
|
||||
i += rowcnt
|
||||
end
|
||||
subplt.o = Qwt.vsplitter(rows...)
|
||||
end
|
||||
|
||||
|
||||
function Base.display(::QwtPackage, subplt::Subplot)
|
||||
for plt in subplt.plts
|
||||
Qwt.refresh(plt.o)
|
||||
end
|
||||
Qwt.showwidget(subplt.o)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
|
||||
|
||||
# TODO: there should be a distinction between an object that will manage a full plot, vs a component of a plot.
|
||||
# the PlotRecipe as currently implemented is more of a "custom component"
|
||||
# a recipe should fully describe the plotting command(s) and call them, likewise for updating.
|
||||
# actually... maybe those should explicitly derive from AbstractPlot???
|
||||
|
||||
abstract PlotRecipe
|
||||
|
||||
getRecipeXY(recipe::PlotRecipe) = Float64[], Float64[]
|
||||
getRecipeArgs(recipe::PlotRecipe) = ()
|
||||
|
||||
plot(recipe::PlotRecipe, args...; kw...) = plot(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
||||
plot!(recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
||||
plot!(plt::Plot, recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
||||
|
||||
num_series(x::AMat) = size(x,2)
|
||||
num_series(x) = 1
|
||||
|
||||
_apply_recipe(d::KW; kw...) = ()
|
||||
|
||||
# if it's not a recipe, just do nothing and return the args
|
||||
function _apply_recipe(d::KW, args...; issubplot=false, kw...)
|
||||
if issubplot && !haskey(d, :n) && !haskey(d, :layout)
|
||||
# put in a sensible default
|
||||
d[:n] = maximum(map(num_series, args))
|
||||
end
|
||||
args
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
`apply_series_recipe` should take a processed series KW dict and break it up
|
||||
into component parts. For example, a box plot is made up of `shape` for the
|
||||
boxes, `path` for the lines, and `scatter` for the outliers.
|
||||
|
||||
Returns a Vector{KW}.
|
||||
"""
|
||||
apply_series_recipe(d::KW, lt) = KW[d]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Box Plot
|
||||
|
||||
const _box_halfwidth = 0.4
|
||||
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:box}})
|
||||
# dumpdict(d, "box before", true)
|
||||
# TODO: add scatter series with outliers
|
||||
|
||||
# create a list of shapes, where each shape is a single boxplot
|
||||
shapes = Shape[]
|
||||
d[:linetype] = :shape
|
||||
groupby = extractGroupArgs(d[:x])
|
||||
|
||||
for (i, glabel) in enumerate(groupby.groupLabels)
|
||||
|
||||
# filter y values, then compute quantiles
|
||||
q1,q2,q3,q4,q5 = quantile(d[:y][groupby.groupIds[i]], linspace(0,1,5))
|
||||
|
||||
# make the shape
|
||||
l, m, r = i - _box_halfwidth, i, i + _box_halfwidth
|
||||
xcoords = [
|
||||
m, l, r, m, m, NaN, # lower T
|
||||
l, l, r, r, l, NaN, # lower box
|
||||
l, l, r, r, l, NaN, # upper box
|
||||
m, l, r, m, m # upper T
|
||||
]
|
||||
ycoords = [
|
||||
q1, q1, q1, q1, q2, NaN, # lower T
|
||||
q2, q3, q3, q2, q2, NaN, # lower box
|
||||
q4, q3, q3, q4, q4, NaN, # upper box
|
||||
q5, q5, q5, q5, q4, NaN, # upper T
|
||||
]
|
||||
push!(shapes, Shape(xcoords, ycoords))
|
||||
end
|
||||
|
||||
d[:x], d[:y] = shape_coords(shapes)
|
||||
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
|
||||
|
||||
KW[d]
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Violin Plot
|
||||
|
||||
# if the user has KernelDensity installed, use this for violin plots.
|
||||
# otherwise, just use a histogram
|
||||
try
|
||||
Pkg.installed("KernelDensity")
|
||||
import KernelDensity
|
||||
|
||||
# warn("using KD for violin")
|
||||
@eval function violin_coords(y)
|
||||
kd = KernelDensity.kde(y, npoints = 30)
|
||||
kd.density, kd.x
|
||||
end
|
||||
catch
|
||||
# warn("using hist for violin")
|
||||
@eval function violin_coords(y)
|
||||
edges, widths = hist(y, 20)
|
||||
centers = 0.5 * (edges[1:end-1] + edges[2:end])
|
||||
ymin, ymax = extrema(y)
|
||||
vcat(0.0, widths, 0.0), vcat(ymin, centers, ymax)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:violin}})
|
||||
# dumpdict(d, "box before", true)
|
||||
# TODO: add scatter series with outliers
|
||||
|
||||
# create a list of shapes, where each shape is a single boxplot
|
||||
shapes = Shape[]
|
||||
d[:linetype] = :shape
|
||||
groupby = extractGroupArgs(d[:x])
|
||||
|
||||
for (i, glabel) in enumerate(groupby.groupLabels)
|
||||
|
||||
# get the edges and widths
|
||||
y = d[:y][groupby.groupIds[i]]
|
||||
widths, centers = violin_coords(y)
|
||||
|
||||
# normalize
|
||||
widths = _box_halfwidth * widths / maximum(widths)
|
||||
|
||||
# make the violin
|
||||
xcoords = vcat(widths, -reverse(widths)) + i
|
||||
ycoords = vcat(centers, reverse(centers))
|
||||
push!(shapes, Shape(xcoords, ycoords))
|
||||
end
|
||||
|
||||
d[:x], d[:y] = shape_coords(shapes)
|
||||
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
|
||||
|
||||
KW[d]
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Error Bars
|
||||
|
||||
function error_style!(d::KW)
|
||||
d[:linetype] = :path
|
||||
d[:linecolor] = d[:markerstrokecolor]
|
||||
d[:linewidth] = d[:markerstrokewidth]
|
||||
d[:label] = ""
|
||||
end
|
||||
|
||||
# if we're passed a tuple of vectors, convert to a vector of tuples
|
||||
function error_zipit(ebar)
|
||||
if istuple(ebar)
|
||||
collect(zip(ebar...))
|
||||
else
|
||||
ebar
|
||||
end
|
||||
end
|
||||
|
||||
function error_coords(xorig, yorig, ebar)
|
||||
# init empty x/y, and zip errors if passed Tuple{Vector,Vector}
|
||||
x, y = zeros(0), zeros(0)
|
||||
|
||||
# for each point, create a line segment from the bottom to the top of the errorbar
|
||||
for i = 1:max(length(xorig), length(yorig))
|
||||
xi = get_mod(xorig, i)
|
||||
yi = get_mod(yorig, i)
|
||||
ebi = get_mod(ebar, i)
|
||||
nanappend!(x, [xi, xi])
|
||||
e1, e2 = if istuple(ebi)
|
||||
first(ebi), last(ebi)
|
||||
elseif isscalar(ebi)
|
||||
ebi, ebi
|
||||
else
|
||||
error("unexpected ebi type $(typeof(ebi)) for errorbar: $ebi")
|
||||
end
|
||||
nanappend!(y, [yi - e1, yi + e2])
|
||||
end
|
||||
x, y
|
||||
end
|
||||
|
||||
# we will create a series of path segments, where each point represents one
|
||||
# side of an errorbar
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:yerror}})
|
||||
error_style!(d)
|
||||
d[:markershape] = :hline
|
||||
d[:x], d[:y] = error_coords(d[:x], d[:y], error_zipit(d[:yerror]))
|
||||
KW[d]
|
||||
end
|
||||
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:xerror}})
|
||||
error_style!(d)
|
||||
d[:markershape] = :vline
|
||||
d[:y], d[:x] = error_coords(d[:y], d[:x], error_zipit(d[:xerror]))
|
||||
KW[d]
|
||||
end
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# quiver
|
||||
|
||||
# function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
|
||||
# d[:label] = ""
|
||||
# d[:linetype] = :scatter
|
||||
#
|
||||
# # create a second series to draw the arrow shaft
|
||||
# dpath = copy(d)
|
||||
# error_style!(dpath)
|
||||
# dpath[:markershape] = :none
|
||||
#
|
||||
# velocity = error_zipit(d[:quiver])
|
||||
# xorig, yorig = d[:x], d[:y]
|
||||
#
|
||||
# # for each point, we create an arrow of velocity vi, translated to the x/y coordinates
|
||||
# # x, y = zeros(0), zeros(0)
|
||||
# paths = P2[]
|
||||
# arrows = P2[]
|
||||
# arrowshapes = Shape[]
|
||||
# for i = 1:max(length(xorig), length(yorig))
|
||||
#
|
||||
# # get the starting position
|
||||
# xi = get_mod(xorig, i)
|
||||
# yi = get_mod(yorig, i)
|
||||
# p = P2(xi, yi)
|
||||
#
|
||||
# # get the velocity
|
||||
# vi = get_mod(velocity, i)
|
||||
# vx, vy = if istuple(vi)
|
||||
# first(vi), last(vi)
|
||||
# elseif isscalar(vi)
|
||||
# vi, vi
|
||||
# else
|
||||
# error("unexpected vi type $(typeof(vi)) for quiver: $vi")
|
||||
# end
|
||||
# v = P2(vx, vy)
|
||||
#
|
||||
# nanappend!(paths, [p, p+v])
|
||||
# push!(arrows, p+v)
|
||||
# push!(arrowshapes, makearrowhead(compute_angle(v)))
|
||||
#
|
||||
# # # dist = sqrt(vx^2 + vy^2)
|
||||
# # dist = norm(v)
|
||||
# # arrow_h = 0.1dist # height of arrowhead
|
||||
# # arrow_w = 0.5arrow_h # halfwidth of arrowhead
|
||||
# # U1 = v ./ dist # vector of arrowhead height
|
||||
# # U2 = P2(-U1[2], U1[1]) # vector of arrowhead halfwidth
|
||||
# # U1 *= arrow_h
|
||||
# # U2 *= arrow_w
|
||||
# #
|
||||
# # append!(pts, P2(xi, yi) .+ P2[(0,0), v-U1, v-U1+U2, v, v-U1-U2, v-U1, (NaN,NaN)])
|
||||
# # # a1 = v - arrow_h * U1 + arrow_w * U2
|
||||
# # # a2 = v - arrow_h * U1 - arrow_w * U2
|
||||
# # # nanappend!(x, xi + [0.0, vx, a1[1], a2[1], vx])
|
||||
# # # nanappend!(y, yi + [0.0, vy, a1[2], a2[2], vy])
|
||||
# end
|
||||
#
|
||||
# # d[:x], d[:y] = Plots.unzip(pts)
|
||||
# dpath[:x], dpath[:y] = Plots.unzip(paths)
|
||||
# d[:x], d[:y] = Plots.unzip(arrows)
|
||||
# d[:markershape] = arrowshapes
|
||||
#
|
||||
# KW[dpath, d]
|
||||
# end
|
||||
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
|
||||
d[:label] = ""
|
||||
d[:linetype] = :shape
|
||||
|
||||
velocity = error_zipit(d[:quiver])
|
||||
xorig, yorig = d[:x], d[:y]
|
||||
|
||||
# for each point, we create an arrow of velocity vi, translated to the x/y coordinates
|
||||
pts = P2[]
|
||||
for i = 1:max(length(xorig), length(yorig))
|
||||
|
||||
# get the starting position
|
||||
xi = get_mod(xorig, i)
|
||||
yi = get_mod(yorig, i)
|
||||
p = P2(xi, yi)
|
||||
|
||||
# get the velocity
|
||||
vi = get_mod(velocity, i)
|
||||
vx, vy = if istuple(vi)
|
||||
first(vi), last(vi)
|
||||
elseif isscalar(vi)
|
||||
vi, vi
|
||||
elseif isa(vi,Function)
|
||||
vi(xi, yi)
|
||||
else
|
||||
error("unexpected vi type $(typeof(vi)) for quiver: $vi")
|
||||
end
|
||||
v = P2(vx, vy)
|
||||
|
||||
dist = norm(v)
|
||||
arrow_h = 0.1dist # height of arrowhead
|
||||
arrow_w = 0.5arrow_h # halfwidth of arrowhead
|
||||
U1 = v ./ dist # vector of arrowhead height
|
||||
U2 = P2(-U1[2], U1[1]) # vector of arrowhead halfwidth
|
||||
U1 *= arrow_h
|
||||
U2 *= arrow_w
|
||||
|
||||
ppv = p+v
|
||||
nanappend!(pts, P2[p, ppv-U1, ppv-U1+U2, ppv, ppv-U1-U2, ppv-U1])
|
||||
end
|
||||
|
||||
d[:x], d[:y] = Plots.unzip(pts[2:end])
|
||||
KW[d]
|
||||
end
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function rotate(x::Real, y::Real, θ::Real; center = (0,0))
|
||||
cx = x - center[1]
|
||||
cy = y - center[2]
|
||||
xrot = cx * cos(θ) - cy * sin(θ)
|
||||
yrot = cy * cos(θ) + cx * sin(θ)
|
||||
xrot + center[1], yrot + center[2]
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
type EllipseRecipe <: PlotRecipe
|
||||
w::Float64
|
||||
h::Float64
|
||||
x::Float64
|
||||
y::Float64
|
||||
θ::Float64
|
||||
end
|
||||
EllipseRecipe(w,h,x,y) = EllipseRecipe(w,h,x,y,0)
|
||||
|
||||
# return x,y coords of a rotated ellipse, centered at the origin
|
||||
function rotatedEllipse(w, h, x, y, θ, rotθ)
|
||||
# # coord before rotation
|
||||
xpre = w * cos(θ)
|
||||
ypre = h * sin(θ)
|
||||
|
||||
# rotate and translate
|
||||
r = rotate(xpre, ypre, rotθ)
|
||||
x + r[1], y + r[2]
|
||||
end
|
||||
|
||||
function getRecipeXY(ep::EllipseRecipe)
|
||||
x, y = unzip([rotatedEllipse(ep.w, ep.h, ep.x, ep.y, u, ep.θ) for u in linspace(0,2π,100)])
|
||||
top = rotate(0, ep.h, ep.θ)
|
||||
right = rotate(ep.w, 0, ep.θ)
|
||||
linex = Float64[top[1], 0, right[1]] + ep.x
|
||||
liney = Float64[top[2], 0, right[2]] + ep.y
|
||||
Any[x, linex], Any[y, liney]
|
||||
end
|
||||
|
||||
function getRecipeArgs(ep::EllipseRecipe)
|
||||
[(:line, (3, [:dot :solid], [:red :blue], :path))]
|
||||
end
|
||||
|
||||
# # -------------------------------------------------
|
||||
|
||||
|
||||
"Sparsity plot... heatmap of non-zero values of a matrix"
|
||||
function spy{T<:Real}(z::AMat{T}; kw...)
|
||||
mat = map(zi->float(zi!=0), z)'
|
||||
xn, yn = size(mat)
|
||||
heatmap(mat; leg=false, yflip=true, aspect_ratio=:equal,
|
||||
xlim=(0.5, xn+0.5), ylim=(0.5, yn+0.5),
|
||||
kw...)
|
||||
end
|
||||
|
||||
"Adds a+bx... straight line over the current plot"
|
||||
function abline!(plt::Plot, a, b; kw...)
|
||||
plot!(plt, [extrema(plt)...], x -> b + a*x; kw...)
|
||||
end
|
||||
|
||||
abline!(args...; kw...) = abline!(current(), args...; kw...)
|
||||
|
||||
# =================================================
|
||||
# Arc and chord diagrams
|
||||
|
||||
"Takes an adjacency matrix and returns source, destiny and weight lists"
|
||||
function mat2list{T}(mat::AbstractArray{T,2})
|
||||
nrow, ncol = size(mat) # rows are sources and columns are destinies
|
||||
|
||||
nosymmetric = !issym(mat) # plots only triu for symmetric matrices
|
||||
nosparse = !issparse(mat) # doesn't plot zeros from a sparse matrix
|
||||
|
||||
L = length(mat)
|
||||
|
||||
source = Array(Int, L)
|
||||
destiny = Array(Int, L)
|
||||
weight = Array(T, L)
|
||||
|
||||
idx = 1
|
||||
for i in 1:nrow, j in 1:ncol
|
||||
value = mat[i, j]
|
||||
if !isnan(value) && ( nosparse || value != zero(T) ) # TODO: deal with Nullable
|
||||
|
||||
if i < j
|
||||
source[idx] = i
|
||||
destiny[idx] = j
|
||||
weight[idx] = value
|
||||
idx += 1
|
||||
elseif nosymmetric && (i > j)
|
||||
source[idx] = i
|
||||
destiny[idx] = j
|
||||
weight[idx] = value
|
||||
idx += 1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
resize!(source, idx-1), resize!(destiny, idx-1), resize!(weight, idx-1)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Arc Diagram
|
||||
|
||||
curvecolor(value, min, max, grad) = getColorZ(grad, (value-min)/(max-min))
|
||||
|
||||
"Plots a clockwise arc, from source to destiny, colored by weight"
|
||||
function arc!(source, destiny, weight, min, max, grad)
|
||||
radius = (destiny - source) / 2
|
||||
arc = Plots.partialcircle(0, π, 30, radius)
|
||||
x, y = Plots.unzip(arc)
|
||||
plot!(x .+ radius .+ source, y, line = (curvecolor(weight, min, max, grad), 0.5, 2), legend=false)
|
||||
end
|
||||
|
||||
"""
|
||||
`arcdiagram(source, destiny, weight[, grad])`
|
||||
|
||||
Plots an arc diagram, form `source` to `destiny` (clockwise), using `weight` to determine the colors.
|
||||
"""
|
||||
function arcdiagram(source, destiny, weight; kargs...)
|
||||
|
||||
args = KW(kargs)
|
||||
grad = pop!(args, :grad, ColorGradient([colorant"darkred", colorant"darkblue"]))
|
||||
|
||||
if length(source) == length(destiny) == length(weight)
|
||||
|
||||
vertices = unique(vcat(source, destiny))
|
||||
sort!(vertices)
|
||||
|
||||
xmin, xmax = extrema(vertices)
|
||||
plot(xlim=(xmin - 0.5, xmax + 0.5), legend=false)
|
||||
|
||||
wmin,wmax = extrema(weight)
|
||||
|
||||
for (i, j, value) in zip(source,destiny,weight)
|
||||
arc!(i, j, value, wmin, wmax, grad)
|
||||
end
|
||||
|
||||
scatter!(vertices, zeros(length(vertices)); legend=false, args...)
|
||||
|
||||
else
|
||||
|
||||
throw(ArgumentError("source, destiny and weight should have the same length"))
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
`arcdiagram(mat[, grad])`
|
||||
|
||||
Plots an arc diagram from an adjacency matrix, form rows to columns (clockwise),
|
||||
using the values on the matrix as weights to determine the colors.
|
||||
Doesn't show edges with value zero if the input is sparse.
|
||||
For simmetric matrices, only the upper triangular values are used.
|
||||
"""
|
||||
arcdiagram{T}(mat::AbstractArray{T,2}; kargs...) = arcdiagram(mat2list(mat)...; kargs...)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Chord diagram
|
||||
|
||||
arcshape(θ1, θ2) = Shape(vcat(Plots.partialcircle(θ1, θ2, 15, 1.1),
|
||||
reverse(Plots.partialcircle(θ1, θ2, 15, 0.9))))
|
||||
|
||||
colorlist(grad, ::Void) = :darkgray
|
||||
|
||||
function colorlist(grad, z)
|
||||
zmin, zmax = extrema(z)
|
||||
RGBA{Float64}[getColorZ(grad, (zi-zmin)/(zmax-zmin)) for zi in z]'
|
||||
end
|
||||
|
||||
"""
|
||||
`chorddiagram(source, destiny, weight[, grad, zcolor, group])`
|
||||
|
||||
Plots a chord diagram, form `source` to `destiny`,
|
||||
using `weight` to determine the edge colors using `grad`.
|
||||
`zcolor` or `group` can be used to determine the node colors.
|
||||
"""
|
||||
function chorddiagram(source, destiny, weight; kargs...)
|
||||
|
||||
args = KW(kargs)
|
||||
grad = pop!(args, :grad, ColorGradient([colorant"darkred", colorant"darkblue"]))
|
||||
zcolor= pop!(args, :zcolor, nothing)
|
||||
group = pop!(args, :group, nothing)
|
||||
|
||||
if zcolor !== nothing && group !== nothing
|
||||
throw(ErrorException("group and zcolor can not be used together."))
|
||||
end
|
||||
|
||||
if length(source) == length(destiny) == length(weight)
|
||||
|
||||
plt = plot(xlim=(-2,2), ylim=(-2,2), legend=false, grid=false,
|
||||
xticks=nothing, yticks=nothing,
|
||||
xlim=(-1.2,1.2), ylim=(-1.2,1.2))
|
||||
|
||||
nodemin, nodemax = extrema(vcat(source, destiny))
|
||||
|
||||
weightmin, weightmax = extrema(weight)
|
||||
|
||||
A = 1.5π # Filled space
|
||||
B = 0.5π # White space (empirical)
|
||||
|
||||
Δα = A / nodemax
|
||||
Δβ = B / nodemax
|
||||
|
||||
δ = Δα + Δβ
|
||||
|
||||
for i in 1:length(source)
|
||||
curve = BezierCurve(P2[ (cos((source[i ]-1)*δ + 0.5Δα), sin((source[i ]-1)*δ + 0.5Δα)), (0,0),
|
||||
(cos((destiny[i]-1)*δ + 0.5Δα), sin((destiny[i]-1)*δ + 0.5Δα)) ])
|
||||
plot!(curve_points(curve), line = (Plots.curvecolor(weight[i], weightmin, weightmax, grad), 1, 1))
|
||||
end
|
||||
|
||||
if group === nothing
|
||||
c = colorlist(grad, zcolor)
|
||||
elseif length(group) == nodemax
|
||||
|
||||
idx = collect(0:(nodemax-1))
|
||||
|
||||
for g in group
|
||||
plot!([arcshape(n*δ, n*δ + Δα) for n in idx[group .== g]]; args...)
|
||||
end
|
||||
|
||||
return plt
|
||||
|
||||
else
|
||||
throw(ErrorException("group should the ", nodemax, " elements."))
|
||||
end
|
||||
|
||||
plot!([arcshape(n*δ, n*δ + Δα) for n in 0:(nodemax-1)]; mc=c, args...)
|
||||
|
||||
return plt
|
||||
|
||||
else
|
||||
throw(ArgumentError("source, destiny and weight should have the same length"))
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
`chorddiagram(mat[, grad, zcolor, group])`
|
||||
|
||||
Plots a chord diagram from an adjacency matrix,
|
||||
using the values on the matrix as weights to determine edge colors.
|
||||
Doesn't show edges with value zero if the input is sparse.
|
||||
For simmetric matrices, only the upper triangular values are used.
|
||||
`zcolor` or `group` can be used to determine the node colors.
|
||||
"""
|
||||
chorddiagram(mat::AbstractMatrix; kargs...) = chorddiagram(mat2list(mat)...; kargs...)
|
||||
@@ -0,0 +1,467 @@
|
||||
|
||||
# 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(lt -> lt in (:contour, :heatmap, :surface, :wireframe, :contour3d), get(d, :linetype, :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
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# 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)
|
||||
|
||||
@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)
|
||||
x, y, 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, getplotargs(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)])
|
||||
lt = d[:linetype]
|
||||
|
||||
# for linetype `line`, need to sort by x values
|
||||
if lt == :line
|
||||
# order by x
|
||||
indices = sortperm(d[:x])
|
||||
d[:x] = d[:x][indices]
|
||||
d[:y] = d[:y][indices]
|
||||
d[:linetype] = :path
|
||||
end
|
||||
|
||||
# special handling for missing x in box plot... all the same category
|
||||
if lt == :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
|
||||
if lt == :quiver
|
||||
d[:linetype] = lt = :path
|
||||
d[:linewidth] = 0
|
||||
end
|
||||
if get(d, :quiver, nothing) != nothing
|
||||
append!(ret, apply_series_recipe(copy(d), Val{:quiver}))
|
||||
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 linetype
|
||||
kwlist = apply_series_recipe(d, Val{lt})
|
||||
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
|
||||
|
||||
|
||||
# plotting arbitrary shapes/polygons
|
||||
function process_inputs(plt::AbstractPlot, d::KW, shape::Shape)
|
||||
d[:x], d[:y] = shape_coords(shape)
|
||||
d[:linetype] = :shape
|
||||
end
|
||||
function process_inputs(plt::AbstractPlot, d::KW, shapes::AVec{Shape})
|
||||
d[:x], d[:y] = shape_coords(shapes)
|
||||
d[:linetype] = :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[:linetype] = :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 linetype
|
||||
lt = get(d, :linetype, :none)
|
||||
if lt == :scatter
|
||||
d[:linetype] = :scatter3d
|
||||
elseif !(lt in _3dTypes)
|
||||
d[:linetype] = :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, :linetype, :none))
|
||||
d[:linetype] = :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, :linetype, :none))
|
||||
d[:linetype] = :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.plotargs, :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
|
||||
|
||||
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,52 +1,31 @@
|
||||
|
||||
|
||||
# create a layout directly
|
||||
SubplotLayout(rowcounts::AbstractVector{Int}) = SubplotLayout(sum(rowcounts), rowcounts)
|
||||
|
||||
# create a layout given counts... nr/nc == -1 implies we figure out a good number automatically
|
||||
function SubplotLayout(numplts::Int, nr::Int, nc::Int)
|
||||
|
||||
# figure out how many rows/columns we need
|
||||
if nr == -1
|
||||
if nc == -1
|
||||
nr = round(Int, sqrt(numplts))
|
||||
nc = ceil(Int, numplts / nr)
|
||||
else
|
||||
nr = ceil(Int, numplts / nc)
|
||||
end
|
||||
else
|
||||
nc = ceil(Int, numplts / nr)
|
||||
end
|
||||
|
||||
# create the rowcounts vector
|
||||
i = 0
|
||||
rowcounts = Int[]
|
||||
for r in 1:nr
|
||||
cnt = min(nc, numplts - i)
|
||||
push!(rowcounts, cnt)
|
||||
i += cnt
|
||||
end
|
||||
|
||||
SubplotLayout(numplts, rowcounts)
|
||||
end
|
||||
|
||||
|
||||
Base.length(layout::SubplotLayout) = layout.numplts
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
Base.string(subplt::Subplot) = "Subplot{$(subplt.plotter) p=$(subplt.p) n=$(subplt.n)}"
|
||||
Base.string(subplt::Subplot) = "Subplot{$(subplt.backend) p=$(subplt.p) n=$(subplt.n)}"
|
||||
Base.print(io::IO, subplt::Subplot) = print(io, string(subplt))
|
||||
Base.show(io::IO, subplt::Subplot) = print(io, string(subplt))
|
||||
|
||||
getplot(subplt::Subplot) = subplt.plts[mod1(subplt.n, subplt.p)]
|
||||
function Base.copy(subplt::Subplot)
|
||||
subplot(subplt.plts, subplt.layout, subplt.plotargs)
|
||||
end
|
||||
|
||||
Base.getindex(subplt::Subplot, args...) = subplt.plts[subplt.layout[args...]]
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
getplot(subplt::Subplot, idx::Int = subplt.n) = subplt.plts[mod1(idx, subplt.p)]
|
||||
getplotargs(subplt::Subplot, idx::Int) = getplot(subplt, idx).plotargs
|
||||
convertSeriesIndex(subplt::Subplot, n::Int) = ceil(Int, n / subplt.p)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
function validateSubplotSupported()
|
||||
if !subplotSupported()
|
||||
error(CURRENT_BACKEND.sym, " does not support the subplot/subplot! commands at this time. Try one of: ", join(filter(pkg->subplotSupported(_backend_instance(pkg)), backends()),", "))
|
||||
end
|
||||
end
|
||||
|
||||
doc"""
|
||||
"""
|
||||
Create a series of plots:
|
||||
```
|
||||
y = rand(100,3)
|
||||
@@ -54,40 +33,154 @@ Create a series of plots:
|
||||
subplot(y; n = 3, nr = 1) # create an automatic grid, but fix the number of rows to 1 (so there are n columns)
|
||||
subplot(y; n = 3, nc = 1) # create an automatic grid, but fix the number of columns to 1 (so there are n rows)
|
||||
subplot(y; layout = [1, 2]) # explicit layout by row... plot #1 goes by itself in the first row, plots 2 and 3 split the 2nd row (note the n kw is unnecessary)
|
||||
subplot(plts, n; nr = -1, nc = -1) # build a layout from existing plots
|
||||
subplot(plts, layout) # build a layout from existing plots
|
||||
```
|
||||
"""
|
||||
function subplot(args...; kw...)
|
||||
d = Dict(kw)
|
||||
validateSubplotSupported()
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
args = _apply_recipe(d, args...; kw..., issubplot=true)
|
||||
|
||||
# figure out the layout
|
||||
if haskey(d, :layout)
|
||||
layout = SubplotLayout(d[:layout])
|
||||
layoutarg = get(d, :layout, nothing)
|
||||
if layoutarg != nothing
|
||||
layout = subplotlayout(layoutarg)
|
||||
else
|
||||
if !haskey(d, :n)
|
||||
n = get(d, :n, -1)
|
||||
if n < 0
|
||||
error("You must specify either layout or n when creating a subplot: ", d)
|
||||
end
|
||||
layout = SubplotLayout(d[:n], get(d, :nr, -1), get(d, :nc, -1))
|
||||
layout = subplotlayout(n, get(d, :nr, -1), get(d, :nc, -1))
|
||||
end
|
||||
|
||||
# initialize the individual plots
|
||||
pkg = plotter()
|
||||
kw0 = getPlotKeywordArgs(kw, 1, 0)
|
||||
plts = Plot[plot(pkg; kw0..., show=false) for i in 1:length(layout)]
|
||||
pkg = backend()
|
||||
plts = Plot{typeof(pkg)}[]
|
||||
for i in 1:length(layout)
|
||||
di = getPlotArgs(pkg, d, i)
|
||||
di[:subplot] = true
|
||||
dumpdict(di, "Plot args (subplot $i)")
|
||||
push!(plts, _create_plot(pkg, di))
|
||||
end
|
||||
|
||||
# create the object and do the plotting
|
||||
subplt = Subplot(nothing, plts, pkg, length(layout), 0, layout)
|
||||
subplot!(subplt, args...; kw...)
|
||||
subplt = Subplot(nothing, plts, pkg, length(layout), 0, layout, d, false, false, false, (r,c) -> (nothing,nothing))
|
||||
subplot!(subplt, args...; d...)
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
doc"""
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
# NOTE: for the subplot calls building from existing plots, we need the first plot to be separate to ensure dispatch calls this instead of the more general subplot(args...; kw...)
|
||||
|
||||
# grid layout
|
||||
function subplot{P}(plt1::Plot{P}, plts::Plot{P}...; kw...)
|
||||
d = KW(kw)
|
||||
layout = if haskey(d, :layout)
|
||||
subplotlayout(d[:layout])
|
||||
else
|
||||
subplotlayout(length(plts)+1, get(d, :nr, -1), get(d, :nc, -1))
|
||||
end
|
||||
# layout = subplotlayout(length(plts)+1, get(d, :nr, -1), get(d, :nc, -1))
|
||||
subplot(vcat(plt1, plts...), layout, d)
|
||||
end
|
||||
|
||||
# explicit layout
|
||||
function subplot{P,I<:Integer}(pltsPerRow::AVec{I}, plt1::Plot{P}, plts::Plot{P}...; kw...)
|
||||
layout = subplotlayout(pltsPerRow)
|
||||
subplot(vcat(plt1, plts...), layout, KW(kw))
|
||||
end
|
||||
|
||||
# this will be called internally
|
||||
function subplot{P<:AbstractBackend}(plts::AVec{Plot{P}}, layout::SubplotLayout, d::KW)
|
||||
validateSubplotSupported()
|
||||
p = length(layout)
|
||||
n = sum([plt.n for plt in plts])
|
||||
subplt = Subplot(nothing, collect(plts), P(), p, n, layout, KW(), false, false, false, (r,c) -> (nothing,nothing))
|
||||
|
||||
_preprocess_subplot(subplt, d)
|
||||
_postprocess_subplot(subplt, d)
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
# TODO: hcat/vcat subplots and plots together arbitrarily
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _preprocess_subplot(subplt::Subplot, d::KW, args = ())
|
||||
validateSubplotSupported()
|
||||
preprocessArgs!(d)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
args = _apply_recipe(d, args...; d..., issubplot=true)
|
||||
|
||||
dumpdict(d, "After subplot! preprocessing")
|
||||
|
||||
# get the full plotargs, overriding any new settings
|
||||
# TODO: subplt.plotargs should probably be merged sooner and actually used
|
||||
# for color selection, etc. (i.e. if we overwrite the subplot palettes to [:heat :rainbow])
|
||||
# then we need to overwrite plt[1].plotargs[:color_palette] to :heat before it's actually used
|
||||
# for color selection!
|
||||
|
||||
# first merge the new args into the subplot's plotargs. then process the plot args and merge
|
||||
# those into the plot's plotargs. (example... `palette = [:blues :reds]` goes into subplt.plotargs,
|
||||
# then the ColorGradient for :blues/:reds is merged into plot 1/2 plotargs, which is then used for color selection)
|
||||
for i in 1:length(subplt.layout)
|
||||
subplt.plts[i].plotargs = getPlotArgs(backend(), merge(subplt.plts[i].plotargs, d), i)
|
||||
end
|
||||
merge!(subplt.plotargs, d)
|
||||
|
||||
# process links. TODO: extract to separate function
|
||||
for s in (:linkx, :linky, :linkfunc)
|
||||
if haskey(d, s)
|
||||
setfield!(subplt, s, d[s])
|
||||
delete!(d, s)
|
||||
end
|
||||
end
|
||||
|
||||
args
|
||||
end
|
||||
|
||||
function _postprocess_subplot(subplt::Subplot, d::KW)
|
||||
# init (after plot creation)
|
||||
if !subplt.initialized
|
||||
subplt.initialized = _create_subplot(subplt, false)
|
||||
end
|
||||
|
||||
# add title, axis labels, ticks, etc
|
||||
for (i,plt) in enumerate(subplt.plts)
|
||||
di = plt.plotargs
|
||||
dumpdict(di, "Updating sp $i")
|
||||
_update_plot(plt, di)
|
||||
end
|
||||
|
||||
_update_plot_pos_size(subplt, d)
|
||||
|
||||
# handle links
|
||||
subplt.linkx && link_axis(subplt, true)
|
||||
subplt.linky && link_axis(subplt, false)
|
||||
|
||||
# set this to be current
|
||||
current(subplt)
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
Adds to a subplot.
|
||||
"""
|
||||
|
||||
# current subplot
|
||||
function subplot!(args...; kw...)
|
||||
subplot!(currentPlot(), args...; kw...)
|
||||
validateSubplotSupported()
|
||||
subplot!(current(), args...; kw...)
|
||||
end
|
||||
|
||||
|
||||
@@ -99,26 +192,140 @@ end
|
||||
|
||||
# # this adds to a specific subplot... most plot commands will flow through here
|
||||
function subplot!(subplt::Subplot, args...; kw...)
|
||||
kwList = createKWargsList(subplt, args...; kw...)
|
||||
for (i,d) in enumerate(kwList)
|
||||
subplt.n += 1
|
||||
plt = getplot(subplt) # get the Plot object where this series will be drawn
|
||||
plot!(plt; d...)
|
||||
end
|
||||
# validateSubplotSupported()
|
||||
|
||||
d = KW(kw)
|
||||
args = _preprocess_subplot(subplt, d, args)
|
||||
|
||||
# create the underlying object (each backend will do this differently)
|
||||
buildSubplotObject!(subplt.plotter, subplt)
|
||||
# note: we call it once before doing the individual plots, and once after
|
||||
# this is because some backends need to set up the subplots and then plot,
|
||||
# and others need to do it the other way around
|
||||
if !subplt.initialized
|
||||
subplt.initialized = _create_subplot(subplt, true)
|
||||
end
|
||||
|
||||
# set this to be current
|
||||
currentPlot!(subplt)
|
||||
groupby = if haskey(d, :group)
|
||||
extractGroupArgs(d[:group], args...)
|
||||
else
|
||||
nothing
|
||||
end
|
||||
|
||||
# do we want to show it?
|
||||
d = Dict(kw)
|
||||
_add_series_subplot(subplt, d, groupby, args...)
|
||||
_postprocess_subplot(subplt, d)
|
||||
|
||||
# show it automatically?
|
||||
if haskey(d, :show) && d[:show]
|
||||
draw(subplt)
|
||||
gui()
|
||||
end
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
|
||||
# not allowed:
|
||||
function plot!(subplt::Subplot, args...; kw...)
|
||||
error("Can't call plot! on a Subplot!")
|
||||
end
|
||||
|
||||
# given a fully processed KW, add the series to the Plot
|
||||
function _add_series_subplot(plt::Plot, d::KW)
|
||||
setTicksFromStringVector(d, d, :x, :xticks)
|
||||
setTicksFromStringVector(d, d, :y, :yticks)
|
||||
|
||||
# this is the actual call to the backend
|
||||
_add_series(plt.backend, plt, d)
|
||||
|
||||
_add_annotations(plt, d)
|
||||
warnOnUnsupportedScales(plt.backend, d)
|
||||
end
|
||||
|
||||
|
||||
# handle the grouping... add a series for each group
|
||||
function _add_series_subplot(subplt::Subplot, d::KW, groupby::GroupBy, args...)
|
||||
starting_n = subplt.n
|
||||
for (i, glab) in enumerate(groupby.groupLabels)
|
||||
tmpd = copy(d)
|
||||
tmpd[:numUncounted] = subplt.n - starting_n
|
||||
_add_series_subplot(subplt, tmpd, nothing, args...;
|
||||
idxfilter = groupby.groupIds[i],
|
||||
grouplabel = string(glab))
|
||||
end
|
||||
end
|
||||
|
||||
# process, filter, and add to the correct plot
|
||||
function _add_series_subplot(subplt::Subplot, d::KW, ::Void, args...;
|
||||
idxfilter = nothing,
|
||||
grouplabel = "")
|
||||
process_inputs(subplt, d, args...)
|
||||
|
||||
if idxfilter != nothing
|
||||
# add the group name as the label if there isn't one passed in
|
||||
get!(d, :label, grouplabel)
|
||||
# filter the data
|
||||
filter_data!(d, idxfilter)
|
||||
end
|
||||
|
||||
kwList, xmeta, ymeta = build_series_args(subplt, d)
|
||||
|
||||
# TODO: something useful with meta info?
|
||||
|
||||
for (i,di) in enumerate(kwList)
|
||||
|
||||
subplt.n += 1
|
||||
plt = getplot(subplt)
|
||||
plt.n += 1
|
||||
|
||||
# cleanup the dictionary that we pass into the plot! command
|
||||
di[:show] = false
|
||||
di[:subplot] = true
|
||||
for k in (:title, :xlabel, :xticks, :xlims, :xscale, :xflip,
|
||||
:ylabel, :yticks, :ylims, :yscale, :yflip)
|
||||
delete!(di, k)
|
||||
end
|
||||
dumpdict(di, "subplot! kwList $i")
|
||||
dumpdict(plt.plotargs, "plt.plotargs before plotting")
|
||||
|
||||
_add_series_subplot(plt, di)
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# handle "linking" the subplot axes together
|
||||
# each backend should implement the _remove_axis and _expand_limits methods
|
||||
function link_axis(subplt::Subplot, isx::Bool)
|
||||
|
||||
# collect the list of plots and the expanded limits for those plots that should be linked on this axis
|
||||
includedPlots = Any[]
|
||||
# lims = [Inf, -Inf]
|
||||
lims = Dict{Int,Any}() # maps column to xlim
|
||||
for (i,(r,c)) in enumerate(subplt.layout)
|
||||
|
||||
# shouldlink will be a bool or nothing. if nothing, then use linkx/y (which is true if we get to this code)
|
||||
shouldlink = subplt.linkfunc(r,c)[isx ? 1 : 2]
|
||||
if shouldlink == nothing || shouldlink
|
||||
plt = subplt.plts[i]
|
||||
|
||||
# if we don't have this
|
||||
k = isx ? c : r
|
||||
if (firstone = !haskey(lims, k))
|
||||
lims[k] = [Inf, -Inf]
|
||||
end
|
||||
|
||||
isinner = (isx && r < nrows(subplt.layout)) || (!isx && !firstone)
|
||||
push!(includedPlots, (plt, isinner, k))
|
||||
|
||||
_expand_limits(lims[k], plt, isx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# do the axis adjustments
|
||||
for (plt, isinner, k) in includedPlots
|
||||
if isinner
|
||||
_remove_axis(plt, isx)
|
||||
end
|
||||
(isx ? xlims! : ylims!)(plt, lims[k]...)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,27 +2,54 @@
|
||||
typealias AVec AbstractVector
|
||||
typealias AMat AbstractMatrix
|
||||
|
||||
abstract PlottingObject
|
||||
abstract PlottingPackage
|
||||
immutable PlotsDisplay <: Display end
|
||||
|
||||
type Plot <: PlottingObject
|
||||
o # the underlying object
|
||||
plotter::PlottingPackage
|
||||
n::Int # number of series
|
||||
abstract AbstractBackend
|
||||
abstract AbstractPlot{T<:AbstractBackend}
|
||||
|
||||
typealias KW Dict{Symbol,Any}
|
||||
|
||||
immutable InputWrapper{T}
|
||||
obj::T
|
||||
end
|
||||
|
||||
wrap{T}(obj::T) = InputWrapper{T}(obj)
|
||||
Base.isempty(wrapper::InputWrapper) = false
|
||||
|
||||
type SubplotLayout
|
||||
numplts::Int
|
||||
rowcounts::AbstractVector{Int}
|
||||
# -----------------------------------------------------------
|
||||
# Plot
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
o # the backend's plot object
|
||||
backend::T # the backend type
|
||||
n::Int # number of series
|
||||
plotargs::KW # arguments for the whole plot
|
||||
seriesargs::Vector{KW} # arguments for each series
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Layout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Subplot <: PlottingObject
|
||||
o # the underlying object
|
||||
plts::Vector{Plot} # the individual plots
|
||||
plotter::PlottingPackage
|
||||
p::Int # number of plots
|
||||
n::Int # number of series
|
||||
layout::SubplotLayout
|
||||
end
|
||||
abstract SubplotLayout
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Subplot
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Subplot{T<:AbstractBackend, L<:SubplotLayout} <: AbstractPlot{T}
|
||||
o # the underlying object
|
||||
plts::Vector{Plot{T}} # the individual plots
|
||||
backend::T
|
||||
p::Int # number of plots
|
||||
n::Int # number of series
|
||||
layout::L
|
||||
plotargs::KW
|
||||
initialized::Bool
|
||||
linkx::Bool
|
||||
linky::Bool
|
||||
linkfunc::Function # maps (row,column) -> (BoolOrNothing, BoolOrNothing)... if xlink/ylink are nothing, then use subplt.linkx/y
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
calcMidpoints(edges::AbstractVector) = Float64[0.5 * (edges[i] + edges[i+1]) for i in 1:length(edges)-1]
|
||||
|
||||
doc"Make histogram-like bins of data"
|
||||
"Make histogram-like bins of data"
|
||||
function binData(data, nbins)
|
||||
lo, hi = extrema(data)
|
||||
edges = collect(linspace(lo, hi, nbins+1))
|
||||
@@ -14,31 +14,31 @@ function binData(data, nbins)
|
||||
edges, midpoints, buckets, counts
|
||||
end
|
||||
|
||||
doc"""
|
||||
"""
|
||||
A hacky replacement for a histogram when the backend doesn't support histograms directly.
|
||||
Convert it into a bar chart with the appropriate x/y values.
|
||||
"""
|
||||
function histogramHack(; kw...)
|
||||
d = Dict(kw)
|
||||
d = KW(kw)
|
||||
|
||||
# we assume that the y kwarg is set with the data to be binned, and nbins is also defined
|
||||
edges, midpoints, buckets, counts = binData(d[:y], d[:nbins])
|
||||
edges, midpoints, buckets, counts = binData(d[:y], d[:bins])
|
||||
d[:x] = midpoints
|
||||
d[:y] = float(counts)
|
||||
d[:linetype] = :bar
|
||||
d[:fillto] = d[:fillto] == nothing ? 0.0 : d[:fillto]
|
||||
d[:fillrange] = d[:fillrange] == nothing ? 0.0 : d[:fillrange]
|
||||
d
|
||||
end
|
||||
|
||||
doc"""
|
||||
"""
|
||||
A hacky replacement for a bar graph when the backend doesn't support bars directly.
|
||||
Convert it into a line chart with fillto set.
|
||||
Convert it into a line chart with fillrange set.
|
||||
"""
|
||||
function barHack(; kw...)
|
||||
d = Dict(kw)
|
||||
d = KW(kw)
|
||||
midpoints = d[:x]
|
||||
heights = d[:y]
|
||||
fillto = d[:fillto] == nothing ? 0.0 : d[:fillto]
|
||||
fillrange = d[:fillrange] == nothing ? 0.0 : d[:fillrange]
|
||||
|
||||
# estimate the edges
|
||||
dists = diff(midpoints) * 0.5
|
||||
@@ -59,13 +59,543 @@ function barHack(; kw...)
|
||||
for i in 1:length(heights)
|
||||
e1, e2 = edges[i:i+1]
|
||||
append!(x, [e1, e1, e2, e2])
|
||||
append!(y, [fillto, heights[i], heights[i], fillto])
|
||||
append!(y, [fillrange, heights[i], heights[i], fillrange])
|
||||
end
|
||||
|
||||
d[:x] = x
|
||||
d[:y] = y
|
||||
d[:linetype] = :line
|
||||
d[:fillto] = fillto
|
||||
d[:linetype] = :path
|
||||
d[:fillrange] = fillrange
|
||||
d
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
A hacky replacement for a sticks graph when the backend doesn't support sticks directly.
|
||||
Convert it into a line chart that traces the sticks, and a scatter that sets markers at the points.
|
||||
"""
|
||||
function sticksHack(; kw...)
|
||||
dLine = KW(kw)
|
||||
dScatter = copy(dLine)
|
||||
|
||||
# these are the line vertices
|
||||
x = Float64[]
|
||||
y = Float64[]
|
||||
fillrange = dLine[:fillrange] == nothing ? 0.0 : dLine[:fillrange]
|
||||
|
||||
# calculate the vertices
|
||||
yScatter = dScatter[:y]
|
||||
for (i,xi) in enumerate(dScatter[:x])
|
||||
yi = yScatter[i]
|
||||
for j in 1:3 push!(x, xi) end
|
||||
append!(y, [fillrange, yScatter[i], fillrange])
|
||||
end
|
||||
|
||||
# change the line args
|
||||
dLine[:x] = x
|
||||
dLine[:y] = y
|
||||
dLine[:linetype] = :path
|
||||
dLine[:markershape] = :none
|
||||
dLine[:fillrange] = nothing
|
||||
|
||||
# change the scatter args
|
||||
dScatter[:linetype] = :none
|
||||
|
||||
dLine, dScatter
|
||||
end
|
||||
|
||||
function regressionXY(x, y)
|
||||
# regress
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
|
||||
# make a line segment
|
||||
regx = [minimum(x), maximum(x)]
|
||||
regy = β * regx + α
|
||||
regx, regy
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
nop() = nothing
|
||||
notimpl() = error("This has not been implemented yet")
|
||||
|
||||
get_mod(v::AVec, idx::Int) = v[mod1(idx, length(v))]
|
||||
get_mod(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
|
||||
get_mod(v, idx::Int) = v
|
||||
|
||||
makevec(v::AVec) = v
|
||||
makevec{T}(v::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
|
||||
|
||||
mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
|
||||
mapFuncOrFuncs(fs::AVec{Function}, u::AVec) = [map(f, u) for f in fs]
|
||||
|
||||
unzip{T,S}(xy::AVec{Tuple{T,S}}) = [x[1] for x in xy], [y[2] for y in xy]
|
||||
unzip{T,S,R}(xyz::AVec{Tuple{T,S,R}}) = [x[1] for x in xyz], [y[2] for y in xyz], [z[3] for z in xyz]
|
||||
unzip{T}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = T[x[1] for x in xy], T[y[2] for y in xy]
|
||||
unzip{T}(xy::FixedSizeArrays.Vec{2,T}) = T[xy[1]], T[xy[2]]
|
||||
unzip{T}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = T[x[1] for x in xyz], T[y[2] for y in xyz], T[z[3] for z in xyz]
|
||||
unzip{T}(xyz::FixedSizeArrays.Vec{3,T}) = T[xyz[1]], T[xyz[2]], T[xyz[3]]
|
||||
|
||||
# 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)
|
||||
# catch err
|
||||
# warn(err)
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
|
||||
# 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...)
|
||||
for (i,vi) in enumerate(v)
|
||||
if isa(vi, t)
|
||||
v[i] = t(args...; kw...)
|
||||
return
|
||||
end
|
||||
end
|
||||
push!(v, t(args...; kw...))
|
||||
return
|
||||
end
|
||||
|
||||
function replaceType(vec, val)
|
||||
filter!(x -> !isa(x, typeof(val)), vec)
|
||||
push!(vec, val)
|
||||
end
|
||||
|
||||
function replaceAliases!(d::KW, aliases::KW)
|
||||
ks = collect(keys(d))
|
||||
for k in ks
|
||||
if haskey(aliases, k)
|
||||
d[aliases[k]] = d[k]
|
||||
delete!(d, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
createSegments(z) = collect(repmat(z',2,1))[2:end]
|
||||
|
||||
Base.first(c::Colorant) = c
|
||||
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)
|
||||
end
|
||||
|
||||
|
||||
function fakedata(sz...)
|
||||
y = zeros(sz...)
|
||||
for r in 2:size(y,1)
|
||||
y[r,:] = 0.95 * y[r-1,:] + randn(size(y,2))'
|
||||
end
|
||||
y
|
||||
end
|
||||
|
||||
isijulia() = isdefined(Main, :IJulia) && Main.IJulia.inited
|
||||
isatom() = isdefined(Main, :Atom) && Main.Atom.isconnected()
|
||||
|
||||
istuple(::Tuple) = true
|
||||
istuple(::Any) = false
|
||||
isvector(::AVec) = true
|
||||
isvector(::Any) = false
|
||||
ismatrix(::AMat) = true
|
||||
ismatrix(::Any) = false
|
||||
isscalar(::Real) = true
|
||||
isscalar(::Any) = false
|
||||
|
||||
|
||||
isvertical(d::KW) = get(d, :orientation, :vertical) in (:vertical, :v, :vert)
|
||||
|
||||
|
||||
# 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) = :invalid
|
||||
|
||||
limsType{T<:Real,S<:Real}(lims::@compat(Tuple{T,S})) = :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.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b)))
|
||||
|
||||
nanpush!(a::AbstractVector, b) = (push!(a, NaN); push!(a, b))
|
||||
nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b))
|
||||
|
||||
# given an array of discrete values, turn it into an array of indices of the unique values
|
||||
# returns the array of indices (znew) and a vector of unique values (vals)
|
||||
function indices_and_unique_values(z::AbstractArray)
|
||||
vals = sort(unique(z))
|
||||
vmap = Dict([(v,i) for (i,v) in enumerate(vals)])
|
||||
newz = map(zi -> vmap[zi], z)
|
||||
newz, vals
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
wraptuple(x::@compat(Tuple)) = x
|
||||
wraptuple(x) = (x,)
|
||||
|
||||
trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x)
|
||||
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) ||
|
||||
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)
|
||||
allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg)
|
||||
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=:hist) do
|
||||
plot(rand(10))
|
||||
plot(rand(10))
|
||||
end
|
||||
```
|
||||
"""
|
||||
function with(f::Function, args...; kw...)
|
||||
newdefs = KW(kw)
|
||||
|
||||
if :canvas in args
|
||||
newdefs[:xticks] = nothing
|
||||
newdefs[:yticks] = nothing
|
||||
newdefs[:grid] = false
|
||||
newdefs[:legend] = false
|
||||
end
|
||||
|
||||
# dict to store old and new keyword args for anything that changes
|
||||
olddefs = KW()
|
||||
for k in keys(newdefs)
|
||||
olddefs[k] = default(k)
|
||||
end
|
||||
|
||||
# save the backend
|
||||
if CURRENT_BACKEND.sym == :none
|
||||
pickDefaultBackend()
|
||||
end
|
||||
oldbackend = CURRENT_BACKEND.sym
|
||||
|
||||
for arg in args
|
||||
|
||||
# change backend?
|
||||
if arg in backends()
|
||||
backend(arg)
|
||||
end
|
||||
|
||||
# # TODO: generalize this strategy to allow args as much as possible
|
||||
# # as in: with(:gadfly, :scatter, :legend, :grid) do; ...; end
|
||||
# # TODO: can we generalize this enough to also do something similar in the plot commands??
|
||||
|
||||
# k = :linetype
|
||||
# if arg in _allTypes
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = arg
|
||||
# elseif haskey(_typeAliases, arg)
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = _typeAliases[arg]
|
||||
# end
|
||||
|
||||
k = :legend
|
||||
if arg in (k, :leg)
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
|
||||
k = :grid
|
||||
if arg == k
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
end
|
||||
|
||||
# display(olddefs)
|
||||
# display(newdefs)
|
||||
|
||||
# now set all those defaults
|
||||
default(; newdefs...)
|
||||
|
||||
# call the function
|
||||
ret = f()
|
||||
|
||||
# put the defaults back
|
||||
default(; olddefs...)
|
||||
|
||||
# revert the backend
|
||||
if CURRENT_BACKEND.sym != oldbackend
|
||||
backend(oldbackend)
|
||||
end
|
||||
|
||||
# return the result of the function
|
||||
ret
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
type DebugMode
|
||||
on::Bool
|
||||
end
|
||||
const _debugMode = DebugMode(false)
|
||||
|
||||
function debugplots(on = true)
|
||||
_debugMode.on = on
|
||||
end
|
||||
|
||||
debugshow(x) = show(x)
|
||||
debugshow(x::AbstractArray) = print(summary(x))
|
||||
|
||||
function dumpdict(d::KW, prefix = "", alwaysshow = false)
|
||||
_debugMode.on || alwaysshow || return
|
||||
println()
|
||||
if prefix != ""
|
||||
println(prefix, ":")
|
||||
end
|
||||
for k in sort(collect(keys(d)))
|
||||
@printf("%14s: ", k)
|
||||
debugshow(d[k])
|
||||
println()
|
||||
end
|
||||
println()
|
||||
end
|
||||
|
||||
|
||||
function dumpcallstack()
|
||||
error() # well... you wanted the stacktrace, didn't you?!?
|
||||
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)
|
||||
|
||||
|
||||
# -------------------------------------------------------
|
||||
# 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)
|
||||
|
||||
function getxy(plt::Plot, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
tovec(d[:x]), tovec(d[:y])
|
||||
end
|
||||
function getxyz(plt::Plot, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
tovec(d[:x]), tovec(d[:y]), tovec(d[:z])
|
||||
end
|
||||
|
||||
function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
d[:x], d[:y] = xy
|
||||
end
|
||||
function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer)
|
||||
d = plt.seriesargs[i]
|
||||
d[:x], d[:y], d[:z] = xyz
|
||||
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)
|
||||
|
||||
# -------------------------------------------------------
|
||||
# push/append for one series
|
||||
|
||||
# push value to first series
|
||||
Base.push!(plt::Plot, y::Real) = push!(plt, 1, y)
|
||||
Base.push!(plt::Plot, x::Real, y::Real) = push!(plt, 1, x, y)
|
||||
Base.push!(plt::Plot, x::Real, y::Real, z::Real) = push!(plt, 1, x, y, z)
|
||||
|
||||
# y only
|
||||
function Base.push!(plt::Plot, i::Integer, y::Real)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesByOne(xdata), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, y::AVec)
|
||||
xdata, ydata = plt[i]
|
||||
if !isa(xdata, UnitRange{Int})
|
||||
error("Expected x is a UnitRange since you're trying to push a y value only")
|
||||
end
|
||||
plt[i] = (extendSeriesByOne(xdata, length(y)), extendSeriesData(ydata, y))
|
||||
plt
|
||||
end
|
||||
|
||||
# x and y
|
||||
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec)
|
||||
@assert length(x) == length(y)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
|
||||
# x, y, and z
|
||||
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real, z::Real)
|
||||
# @show i, x, y, z
|
||||
xdata, ydata, zdata = getxyz(plt, i)
|
||||
# @show xdata, ydata, zdata
|
||||
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec, z::AVec)
|
||||
@assert length(x) == length(y) == length(z)
|
||||
xdata, ydata, zdata = getxyz(plt, i)
|
||||
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
|
||||
plt
|
||||
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...)
|
||||
|
||||
# -------------------------------------------------------
|
||||
# push/append for all series
|
||||
|
||||
# push y[i] to the ith series
|
||||
function Base.push!(plt::Plot, y::AVec)
|
||||
ny = length(y)
|
||||
for i in 1:plt.n
|
||||
push!(plt, i, y[mod1(i,ny)])
|
||||
end
|
||||
plt
|
||||
end
|
||||
|
||||
# push y[i] to the ith series
|
||||
# same x for each series
|
||||
function Base.push!(plt::Plot, x::Real, y::AVec)
|
||||
push!(plt, [x], y)
|
||||
end
|
||||
|
||||
# push (x[i], y[i]) to the ith series
|
||||
function Base.push!(plt::Plot, x::AVec, y::AVec)
|
||||
nx = length(x)
|
||||
ny = length(y)
|
||||
for i in 1:plt.n
|
||||
push!(plt, i, x[mod1(i,nx)], y[mod1(i,ny)])
|
||||
end
|
||||
plt
|
||||
end
|
||||
|
||||
# push (x[i], y[i], z[i]) to the ith series
|
||||
function Base.push!(plt::Plot, x::AVec, y::AVec, z::AVec)
|
||||
nx = length(x)
|
||||
ny = length(y)
|
||||
nz = length(z)
|
||||
for i in 1:plt.n
|
||||
push!(plt, i, x[mod1(i,nx)], y[mod1(i,ny)], z[mod1(i,nz)])
|
||||
end
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
# graphs detailing the features that each backend supports
|
||||
|
||||
function supportGraph(allvals, func)
|
||||
vals = reverse(sort(allvals))
|
||||
bs = sort(backends())
|
||||
x = ASCIIString[]
|
||||
y = ASCIIString[]
|
||||
for val in vals
|
||||
for b in bs
|
||||
supported = func(Plots._backend_instance(b))
|
||||
if val in supported
|
||||
push!(x, string(b))
|
||||
push!(y, string(val))
|
||||
end
|
||||
end
|
||||
end
|
||||
n = length(vals)
|
||||
scatter(x, y, m=:rect, ms=10, size=(300,100+18*n), leg=false)
|
||||
end
|
||||
|
||||
supportGraphArgs() = supportGraph(_allArgs, supportedArgs)
|
||||
supportGraphTypes() = supportGraph(_allTypes, supportedTypes)
|
||||
supportGraphStyles() = supportGraph(_allStyles, supportedStyles)
|
||||
supportGraphMarkers() = supportGraph(_allMarkers, supportedMarkers)
|
||||
supportGraphScales() = supportGraph(_allScales, supportedScales)
|
||||
supportGraphAxes() = supportGraph(_allAxes, supportedAxes)
|
||||
|
||||
function dumpSupportGraphs()
|
||||
for func in (supportGraphArgs, supportGraphTypes, supportGraphStyles,
|
||||
supportGraphMarkers, supportGraphScales, supportGraphAxes)
|
||||
plt = func()
|
||||
png(joinpath(Pkg.dir("ExamplePlots"), "docs", "examples", "img", "supported", "$(string(func))"))
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
|
||||
# Some conversion functions
|
||||
# note: I borrowed these conversion constants from Compose.jl's Measure
|
||||
|
||||
const PX_PER_INCH = 100
|
||||
const DPI = PX_PER_INCH
|
||||
const MM_PER_INCH = 25.4
|
||||
const MM_PER_PX = MM_PER_INCH / PX_PER_INCH
|
||||
|
||||
inch2px(inches::Real) = float(inches * PX_PER_INCH)
|
||||
px2inch(px::Real) = float(px / PX_PER_INCH)
|
||||
inch2mm(inches::Real) = float(inches * MM_PER_INCH)
|
||||
mm2inch(mm::Real) = float(mm / MM_PER_INCH)
|
||||
px2mm(px::Real) = float(px * MM_PER_PX)
|
||||
mm2px(mm::Real) = float(px / MM_PER_PX)
|
||||
|
||||
|
||||
"Smallest x in plot"
|
||||
xmin(plt::Plot) = minimum([minimum(d[:x]) for d in plt.seriesargs])
|
||||
"Largest x in plot"
|
||||
xmax(plt::Plot) = maximum([maximum(d[:x]) for d in plt.seriesargs])
|
||||
|
||||
"Extrema of x-values in plot"
|
||||
Base.extrema(plt::Plot) = (xmin(plt), xmax(plt))
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
julia 0.4
|
||||
|
||||
Colors
|
||||
Reexport
|
||||
Requires
|
||||
FactCheck
|
||||
Cairo
|
||||
Gadfly
|
||||
Images
|
||||
PyPlot
|
||||
@osx QuartzImageIO
|
||||
GR
|
||||
DataFrames
|
||||
RDatasets
|
||||
VisualRegressionTests
|
||||
UnicodePlots
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
using VisualRegressionTests
|
||||
using ExamplePlots
|
||||
|
||||
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
|
||||
|
||||
|
||||
using Plots, FactCheck
|
||||
|
||||
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)
|
||||
|
||||
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
|
||||
|
||||
# first
|
||||
Plots._debugMode.on = debug
|
||||
example = ExamplePlots._examples[idx]
|
||||
info("Testing plot: $pkg:$idx:$(example.header)")
|
||||
backend(pkg)
|
||||
backend()
|
||||
|
||||
# ensure consistent results
|
||||
srand(1234)
|
||||
|
||||
# reference image directory setup
|
||||
refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg))
|
||||
|
||||
# test function
|
||||
func = (fn, idx) -> begin
|
||||
map(eval, example.exprs)
|
||||
png(fn)
|
||||
end
|
||||
|
||||
try
|
||||
run(`mkdir -p $refdir`)
|
||||
catch err
|
||||
display(err)
|
||||
end
|
||||
reffn = joinpath(refdir, "ref$idx.png")
|
||||
|
||||
# the test
|
||||
vtest = VisualTest(func, reffn, idx)
|
||||
test_images(vtest, popup=popup, sigma=sigma, eps=eps)
|
||||
end
|
||||
|
||||
function image_comparison_facts(pkg::Symbol;
|
||||
skip = [], # skip these examples (int index)
|
||||
only = nothing, # limit to these examples (int index)
|
||||
debug = false, # print debug information?
|
||||
sigma = [1,1], # number of pixels to "blur"
|
||||
eps = 1e-2) # acceptable error (percent)
|
||||
for i in 1:length(ExamplePlots._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
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# sudo add-apt-repository -y ppa:pov/wkhtmltopdf
|
||||
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
|
||||
wkhtmltoimage http://www.google.com test.png
|
||||
ls
|
||||
|
||||
wkhtmltopdf -V
|
||||
wkhtmltoimage -V
|
||||
|
||||
# echo 'exec xvfb-run -a -s "-screen 0 640x480x16" wkhtmltoimage "$@"' | sudo tee /usr/local/bin/wkhtmltoimage.sh >/dev/null
|
||||
# sudo chmod a+x /usr/local/bin/wkhtmltoimage.sh
|
||||
|
||||
# export DISPLAY=:99.0
|
||||
# sh -e /etc/init.d/xvfb start
|
||||
# sleep 3 # give xvfb some time to start
|
||||
@@ -1,78 +1,76 @@
|
||||
module PlotsTests
|
||||
|
||||
using Plots
|
||||
using FactCheck
|
||||
include("imgcomp.jl")
|
||||
|
||||
# don't actually show the plots
|
||||
plotDefault!(:show, false)
|
||||
srand(1234)
|
||||
default(show=false, reuse=true)
|
||||
img_eps = 5e-2
|
||||
|
||||
facts("Gadfly") do
|
||||
@fact gadfly() --> Plots.GadflyBackend()
|
||||
@fact backend() --> Plots.GadflyBackend()
|
||||
|
||||
# note: we wrap in a try block so that the tests only run if we have the backend installed
|
||||
try
|
||||
Pkg.installed("Gadfly")
|
||||
facts("Gadfly") do
|
||||
@fact plotter!(:gadfly) --> Plots.GadflyPackage()
|
||||
@fact plotter() --> Plots.GadflyPackage()
|
||||
@fact typeof(plot(1:10)) --> Plot
|
||||
|
||||
|
||||
# plot(x::AVec, y::AVec; kw...) # one line (will assert length(x) == length(y))
|
||||
@fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyBackend}
|
||||
@fact plot(Int[1,2,3], rand(3)) --> not(nothing)
|
||||
@fact_throws plot(1:5, 1:4)
|
||||
|
||||
# plot(x::AVec, y::AMat; kw...) # multiple lines (one per column of x), all sharing x (will assert length(x) == size(y,1))
|
||||
@fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing)
|
||||
@fact_throws(plot!(rand(10), rand(9,2)))
|
||||
|
||||
# plot(x::AMat, y::AMat; kw...) # multiple lines (one per column of x/y... will assert size(x) == size(y))
|
||||
@fact plot!(rand(10,3), rand(10,3)) --> not(nothing)
|
||||
@fact_throws plot!(rand(10,3), rand(10,2))
|
||||
|
||||
|
||||
# plot(x::AVec, y::AVec{AVec}; kw...) # multiple lines, will assert length(x) == length(y[i])
|
||||
|
||||
|
||||
# plot(x::AVec{AVec}, y::AVec{AVec}; kw...) # multiple lines, will assert length(x[i]) == length(y[i])
|
||||
# plot(n::Integer; kw...) # n lines, all empty (for updating plots)
|
||||
end
|
||||
image_comparison_facts(:gadfly, skip=[4,6,19,23,24,27], eps=img_eps)
|
||||
end
|
||||
|
||||
# note: we wrap in a try block so that the tests only run if we have the backend installed
|
||||
try
|
||||
Pkg.installed("Qwt")
|
||||
facts("Qwt") do
|
||||
@fact plotter!(:qwt) --> Plots.QwtPackage()
|
||||
@fact plotter() --> Plots.QwtPackage()
|
||||
@fact typeof(plot(1:10)) --> Plot
|
||||
facts("PyPlot") do
|
||||
@fact pyplot() --> Plots.PyPlotBackend()
|
||||
@fact backend() --> Plots.PyPlotBackend()
|
||||
|
||||
# plot(y::AVec; kw...) # one line... x = 1:length(y)
|
||||
@fact plot(1:10) --> not(nothing)
|
||||
@fact length(currentPlot().o.lines) --> 1
|
||||
|
||||
# plot(x::AVec, f::Function; kw...) # one line, y = f(x)
|
||||
@fact plot(1:10, sin) --> not(nothing)
|
||||
@fact currentPlot().o.lines[1].y --> sin(collect(1:10))
|
||||
|
||||
# plot(x::AMat, f::Function; kw...) # multiple lines, yᵢⱼ = f(xᵢⱼ)
|
||||
@fact plot(rand(10,2), sin) --> not(nothing)
|
||||
@fact length(currentPlot().o.lines) --> 2
|
||||
|
||||
# plot(y::AMat; kw...) # multiple lines (one per column of x), all sharing x = 1:size(y,1)
|
||||
@fact plot!(rand(10,2)) --> not(nothing)
|
||||
@fact length(currentPlot().o.lines) --> 4
|
||||
|
||||
# plot(x::AVec, fs::AVec{Function}; kw...) # multiple lines, yᵢⱼ = fⱼ(xᵢ)
|
||||
@fact plot(1:10, Function[sin,cos]) --> not(nothing)
|
||||
@fact currentPlot().o.lines[1].y --> sin(collect(1:10))
|
||||
@fact currentPlot().o.lines[2].y --> cos(collect(1:10))
|
||||
|
||||
# plot(y::AVec{AVec}; kw...) # multiple lines, each with x = 1:length(y[i])
|
||||
@fact plot([11:20 ; rand(10)]) --> not(nothing)
|
||||
@fact currentPlot().o.lines[1].x[4] --> 4
|
||||
@fact currentPlot().o.lines[1].y[4] --> 14
|
||||
end
|
||||
image_comparison_facts(:pyplot, skip=[19], eps=img_eps)
|
||||
end
|
||||
|
||||
facts("GR") do
|
||||
@fact gr() --> Plots.GRBackend()
|
||||
@fact backend() --> Plots.GRBackend()
|
||||
|
||||
@linux_only image_comparison_facts(:gr, skip=[24], 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
|
||||
|
||||
|
||||
# facts("Immerse") do
|
||||
# @fact immerse() --> Plots.ImmerseBackend()
|
||||
# @fact 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()
|
||||
#
|
||||
# # 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
FactCheck.exitstatus()
|
||||
end # module
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
|
||||
function testplot_line1()
|
||||
plot(rand(100,3))
|
||||
end
|
||||
|
||||
function testplot_fn1()
|
||||
plot(0:0.01:4π, [sin,cos])
|
||||
end
|
||||
|
||||
function testplot_guides1()
|
||||
plot(rand(10); title="TITLE", xlabel="XLABEL", ylabel="YLABEL", background_color=:red)
|
||||
end
|
||||
|
||||
function testplot_points1()
|
||||
plot(Vector[rand(10), rand(20)]; marker=:ellipse, markersize=8)
|
||||
end
|
||||
|
||||
function testplot_points2()
|
||||
plot(Vector[rand(10), rand(20)]; marker=:ellipse, markersize=8, markercolors=[:red,:blue])
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# Pkg.clone(pwd())
|
||||
# Pkg.build("Plots")
|
||||
|
||||
Pkg.clone("ImageMagick")
|
||||
Pkg.build("ImageMagick")
|
||||
|
||||
Pkg.clone("GR")
|
||||
Pkg.build("GR")
|
||||
|
||||
Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");
|
||||
|
||||
# Pkg.clone("https://github.com/JunoLab/Blink.jl.git")
|
||||
# Pkg.build("Blink")
|
||||
# import Blink
|
||||
# Blink.AtomShell.install()
|
||||
# Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git")
|
||||
|
||||
ENV["PYTHON"] = ""
|
||||
Pkg.add("PyPlot")
|
||||
Pkg.build("PyPlot")
|
||||
|
||||
Pkg.test("Plots"; coverage=false)
|
||||